Add motd & featured tab + fixes

This commit is contained in:
fed 2023-03-01 23:53:58 +01:00
parent 8404feba32
commit 6023e619b9
62 changed files with 1467 additions and 19 deletions

View File

@ -1,2 +1 @@
require("credits")
require("mainmenu")

View File

@ -1,4 +0,0 @@
LUI.onmenuopen("main_campaign", function(menu)
local headertext = menu:getFirstDescendentById("header_text")
headertext:setText("H2-MOD")
end)

View File

@ -0,0 +1,2 @@
require("motd")
require("featured")

View File

@ -0,0 +1,493 @@
motd.getfeaturedtabtitle = function(index)
return motd.getfeaturedtab(index).tab_title or ""
end
local animmsfull = 150
local animms = animmsfull / 2
LUI.onmenuopen("main_campaign", function(menu)
if (mods.getloaded() ~= nil or motd.getnumfeaturedtabs() <= 0) then
return
end
local left = 866
local featuredcontainer = LUI.UIElement.new({
leftAnchor = true,
topAnchor = true,
left = left,
top = 420,
})
featuredcontainer:registerAnimationState("hide", {
leftAnchor = true,
topAnchor = true,
left = left,
top = 570,
})
featuredcontainer:registerAnimationState("show", {
leftAnchor = true,
topAnchor = true,
left = left,
top = 420,
})
local featuredstencil = LUI.UIStencilText.new({
leftAnchor = true,
topAnchor = true,
height = 200,
width = GenericMenuDims.menu_width_standard + (10 + 12) * 2,
})
local featured = LUI.UIElement.new({
left = 20,
leftAnchor = true,
topAnchor = true,
bottomAnchor = true,
rightAnchor = true
})
local arrowcontainer = LUI.UIElement.new({
leftAnchor = true,
topAnchor = true,
bottomAnchor = true,
rightAnchor = true
})
arrowcontainer:registerAnimationState("hide", {
alpha = 0
})
arrowcontainer:registerAnimationState("show", {
alpha = 1
})
featured:addElement(arrowcontainer)
featuredstencil:registerAnimationState("hide", {
leftAnchor = true,
topAnchor = true,
height = GenericButtonSettings.Styles.FlatButton.height + 2,
width = GenericMenuDims.menu_width_standard + (10 + 12) * 2,
})
local istopmost = function()
return LUI.FlowManager.IsMenuTopmost(Engine.GetLuiRoot(), "main_campaign")
end
featuredstencil:registerAnimationState("show", {
leftAnchor = true,
topAnchor = true,
height = 200,
width = GenericMenuDims.menu_width_standard + (10 + 12) * 2,
})
local hoverelem = LUI.UIElement.new({
leftAnchor = true,
topAnchor = true,
top = -10,
height = 150 + GenericButtonSettings.Styles.FlatButton.height + 2 + 10,
width = GenericMenuDims.menu_width_standard + 50,
left = -25
})
local header = LUI.UIElement.new({
topAnchor = true,
leftAnchor = true,
width = GenericMenuDims.menu_width_standard,
height = GenericButtonSettings.Styles.FlatButton.height,
})
local headerbg = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
bottomAnchor = true,
rightAnchor = true,
alpha = 0.55,
color = Colors.grey_14,
material = RegisterMaterial("white")
})
local headerbutton = LUI.UIImage.new({
leftAnchor = true,
topAnchor = true,
height = 10,
width = 10,
top = 10.5,
left = 10,
zRot = -90,
color = {
r = 0.6,
g = 0.6,
b = 0.6,
},
material = RegisterMaterial("widg_lobby_arrow")
})
headerbutton:registerEventHandler("mouseenter", function()
if (not istopmost()) then
return
end
Engine.PlaySound(CoD.SFX.MouseOver)
headerbutton:animateToState("focused")
end)
headerbutton:registerEventHandler("mouseleave", function()
headerbutton:animateToState("unfocused")
end)
headerbutton:registerAnimationState("down", {
zRot = -90
})
headerbutton:registerAnimationState("right", {
zRot = 0
})
headerbutton:registerAnimationState("focused", {
color = Colors.h2.yellow
})
headerbutton:registerAnimationState("unfocused", {
color = {
r = 0.6,
g = 0.6,
b = 0.6,
}
})
local tabcount = motd.getnumfeaturedtabs()
headerbutton:setHandleMouse(true)
local minimized = false
headerbutton:registerEventHandler("leftmousedown", function()
minimized = not minimized
if (minimized) then
featuredstencil:animateToState("hide", animms)
featuredcontainer:animateToState("hide", animms)
headerbutton:animateToState("right", animms)
arrowcontainer:animateToState("hide", animms)
else
featuredstencil:animateToState("show", animms)
featuredcontainer:animateToState("show", animms)
headerbutton:animateToState("down", animms)
if (tabcount > 1) then
arrowcontainer:animateToState("show", animms)
end
end
end)
header:addElement(headerbg)
header:addElement(headerbutton)
local headertext = LUI.UIText.new({
leftAnchor = true,
left = GenericButtonSettings.Styles.FlatButton.text_padding_with_content + 10,
top = -CoD.TextSettings.Font19.Height / 2 + 1.5,
font = CoD.TextSettings.Font19.Font,
height = CoD.TextSettings.Font19.Height
})
local rightoffset = -15
local pips = {}
for i = 1, tabcount do
local pipmat = RegisterMaterial("h1_ui_featured_pip_unfocused")
local ratio = Engine.GetMaterialAspectRatio(pipmat)
local height = 14
local pip = LUI.UIImage.new({
topAnchor = true,
rightAnchor = true,
top = GenericButtonSettings.Styles.FlatButton.height / 2 - height / 2,
width = ratio * height,
height = height,
material = RegisterMaterial("h1_ui_featured_pip_unfocused"),
right = rightoffset
})
pip:setHandleMouse(true)
pip:registerEventHandler("mouseenter", function()
if (not istopmost()) then
return
end
Engine.PlaySound(CoD.SFX.MouseOver)
end)
pip:registerAnimationState("focused", {
material = RegisterMaterial("h2_ui_featured_pip_focused"),
})
pip:registerAnimationState("unfocused", {
material = RegisterMaterial("h1_ui_featured_pip_unfocused"),
})
table.insert(pips, pip)
header:addElement(pip)
rightoffset = rightoffset - height
end
pips[#pips]:animateToState("focused")
headertext:setText(Engine.ToUpperCase(Engine.Localize("@LUA_MENU_FEATURED")))
local content = LUI.UIElement.new({
topAnchor = true,
leftAnchor = true,
top = GenericButtonSettings.Styles.FlatButton.height + 3,
width = GenericMenuDims.menu_width_standard,
height = 150,
})
local stencil = LUI.UIStencilText.new({
topAnchor = true,
leftAnchor = true,
bottomAnchor = true,
rightAnchor = true,
left = 1,
})
local imagelist = LUI.UIHorizontalList.new( {
left = 0,
leftAnchor = true,
width = (GenericMenuDims.menu_width_standard) * tabcount,
height = 150,
spacing = 0
} )
stencil:addElement(imagelist)
local contentborder = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
bottomAnchor = true,
rightAnchor = true,
alpha = 0,
material = RegisterMaterial("h2_ui_btn_focused_stroke_square")
})
contentborder:registerAnimationState("unfocused", {
alpha = 0
})
contentborder:registerAnimationState("focused", {
alpha = 1
})
contentborder:setup9SliceImage()
for i = 1, tabcount do
local panel = LUI.UIElement.new({
topAnchor = true,
leftAnchor = true,
bottomAnchor = true,
width = GenericMenuDims.menu_width_standard,
})
local text = LUI.UIText.new({
bottomAnchor = true,
leftAnchor = true,
rightAnchor = true,
alignment = LUI.Alignment.Center,
bottom = (-CoD.TextSettings.Font21.Height / 2) + 2,
height = CoD.TextSettings.Font21.Height,
font = CoD.TextSettings.Font21.Font,
})
local textbg = LUI.UIImage.new({
bottomAnchor = true,
leftAnchor = true,
rightAnchor = true,
height = CoD.TextSettings.Font21.Height * 2,
alpha = 0.75,
material = RegisterMaterial("black")
})
text:setText(motd.getfeaturedtabtitle(i - 1))
local material = RegisterMaterial("featured_panel_thumbnail_" .. i)
local ratio = Engine.GetMaterialAspectRatio(material)
local width = GenericMenuDims.menu_width_standard
local height = width / ratio
local contentimage = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
width = width,
height = height,
material = material,
})
panel:addElement(contentimage)
panel:addElement(textbg)
panel:addElement(text)
imagelist:addElement(panel)
end
local focusindex = 0
local shiftright = function()
local prevfocus = focusindex
focusindex = (focusindex + 1) % tabcount
pips[#pips - (prevfocus)]:animateToState("unfocused")
pips[#pips - (focusindex)]:animateToState("focused")
imagelist:registerAnimationState("move", {
leftAnchor = true,
left = (focusindex) * -GenericMenuDims.menu_width_standard
})
imagelist:animateToState("move", animmsfull)
end
local shiftleft = function()
local prevfocus = focusindex
focusindex = (focusindex - 1) % tabcount
pips[#pips - (prevfocus)]:animateToState("unfocused")
pips[#pips - (focusindex)]:animateToState("focused")
imagelist:registerAnimationState("move", {
leftAnchor = true,
left = (focusindex) * -GenericMenuDims.menu_width_standard
})
imagelist:animateToState("move", animmsfull)
end
local autoscrolltimer = LUI.UITimer.new(3000, "autoscroll")
featured:addElement(autoscrolltimer)
featured:registerEventHandler("autoscroll", function()
if (not minimized) then
shiftright()
end
end)
local addarrows = function()
local arrowmat = RegisterMaterial("h1_prestige_leftright_arrow")
local arrowratio = Engine.GetMaterialAspectRatio(arrowmat)
local height = 20
local width = arrowratio * height
local arrowleft = LUI.UIImage.new({
leftAnchor = true,
left = -width - 10,
top = -10,
height = height,
width = width,
alpha = 0,
material = RegisterMaterial("h1_prestige_leftright_arrow")
})
local arrowright = LUI.UIImage.new({
rightAnchor = true,
right = 0,
top = -10,
height = height,
width = width,
zRot = 180,
alpha = 0,
material = RegisterMaterial("h1_prestige_leftright_arrow")
})
arrowleft:registerAnimationState("focused", {
alpha = 1
})
arrowleft:registerAnimationState("unfocused", {
alpha = 0
})
arrowright:registerAnimationState("focused", {
alpha = 1
})
arrowright:registerAnimationState("unfocused", {
alpha = 0
})
arrowleft:setHandleMouse(true)
arrowright:setHandleMouse(true)
arrowleft:registerEventHandler("leftmousedown", shiftleft)
arrowright:registerEventHandler("leftmousedown", shiftright)
arrowcontainer:addElement(arrowleft)
arrowcontainer:addElement(arrowright)
featured.arrowleft = arrowleft
featured.arrowright = arrowright
end
addarrows()
if (tabcount <= 1) then
arrowcontainer:animateToState("hide")
end
featured:registerEventHandler("focused", function()
contentborder:animateToState("focused", animms)
featured.arrowleft:animateToState("focused", animms)
featured.arrowright:animateToState("focused", animms)
LUI.UITimer.Stop(autoscrolltimer)
end)
featured:registerEventHandler("unfocused", function()
contentborder:animateToState("unfocused", animms)
featured.arrowleft:animateToState("unfocused", animms)
featured.arrowright:animateToState("unfocused", animms)
LUI.UITimer.Reset(autoscrolltimer)
end)
hoverelem:setHandleMouseMove(true)
hoverelem:registerEventHandler("mouseenter", function()
if (not istopmost()) then
return
end
featured:processEvent({
name = "focused",
dispatchChildren = true
})
end)
hoverelem:registerEventHandler("mouseleave", function()
featured:processEvent({
name = "unfocused",
dispatchChildren = true
})
end)
header:addElement(headertext)
header:addElement(LUI.DecoFrame.new(nil, LUI.DecoFrame.Grey))
featured:addElement(header)
content:addElement(stencil)
content:addElement(LUI.DecoFrame.new(nil, LUI.DecoFrame.Grey))
content:addElement(contentborder)
content:setHandleMouse(true)
content:registerEventHandler("leftmousedown", function()
local data = motd.getfeaturedtab(focusindex)
data.popup_image = "featured_panel_" .. (focusindex + 1)
LUI.FlowManager.RequestPopupMenu( nil, "motd_main", true, nil, false, {
popupDataQueue = {data}
})
end)
featured:addElement(content)
featured:addElement(hoverelem)
featuredstencil:addElement(featured)
featuredcontainer:addElement(featuredstencil)
menu:addElement(featuredcontainer)
end)

View File

@ -0,0 +1,76 @@
require("LUI.common_menus.MarketingComms")
require("LUI.common_menus.MarketingPopup")
LUI.CustomMarketingPopups = {ShowDepotOnboardingPopupIfPossible = function() end}
LUI.onmenuopen("main_campaign", function(menu)
if (not motd.hasseentoday()) then
motd.sethasseentoday()
local data = motd.getmotd()
LUI.FlowManager.RequestPopupMenu( self, "motd_main", true, nil, false, {
popupDataQueue = {data}
})
end
end)
local function makelink(element, link)
element:setHandleMouseMove(true)
element:setHandleMouseButton(true)
element:registerAnimationState("focused", {
color = {
r = 1,
g = 1,
b = 1
}
})
local entered = false
element:registerEventHandler("mouseenter", function()
if (not entered) then
Engine.PlaySound(CoD.SFX.MouseOver)
entered = true
end
element:animateToState("focused")
end)
element:registerEventHandler("mouseleave", function()
entered = false
element:animateToState("default")
end)
element:registerEventHandler("leftmousedown", function()
Engine.PlaySound(CoD.SFX.MouseClick)
game:openlink(link)
end)
end
local marketingbase = LUI.MarketingPopup.Base
LUI.MarketingPopup.Base = function(a1, data, a3)
local element = marketingbase(a1, data, a3)
local blur = element:getFirstDescendentById("generic_popup_screen_overlay_blur"):getNextSibling()
local parent = blur:getFirstChild():getNextSibling():getNextSibling():getNextSibling()
local image = parent:getFirstChild()
image:close()
local state = LUI.DeepCopy(image:getAnimationStateInC("default"))
local imagecontainer = LUI.UIStencilText.new(state)
local material = RegisterMaterial(data.image)
local ratio = Engine.GetMaterialAspectRatio(material)
local width = 525
local height = width / ratio
local image = LUI.UIImage.new({
leftAnchor = true,
topAnchor = true,
width = width,
height = height,
material = material
})
imagecontainer:addElement(image)
parent:addElement(imagecontainer)
return element
end

View File

@ -4,12 +4,14 @@ eng_h2_mod_common
eng_h2_mod_patch_af_caves
ens_h2_mod_common
ens_h2_mod_patch_af_caves
cze_h2_mod_common
fra_h2_mod_common
h2_mod_common
h2_mod_patch_af_caves
h2_mod_patch_dc_whitehouse
h2_mod_patch_ending
h2_mod_pre_gfx
h2_mod_ui
ita_h2_mod_common
jpf_h2_mod_common
jpp_h2_mod_common

View File

@ -0,0 +1,21 @@
material,h2_ui_featured_pip_focused
material,h1_ui_featured_pip_unfocused
material,h2_ui_btn_focused_stroke_square
material,h1_prestige_leftright_arrow
material,featured_panel_1
material,featured_panel_2
material,featured_panel_3
material,featured_panel_4
material,featured_panel_5
material,featured_panel_6
material,featured_panel_7
material,featured_panel_8
material,featured_panel_thumbnail_1
material,featured_panel_thumbnail_2
material,featured_panel_thumbnail_3
material,featured_panel_thumbnail_4
material,featured_panel_thumbnail_5
material,featured_panel_thumbnail_6
material,featured_panel_thumbnail_7
material,featured_panel_thumbnail_8
material,motd_image
1 material h2_ui_featured_pip_focused
2 material h1_ui_featured_pip_unfocused
3 material h2_ui_btn_focused_stroke_square
4 material h1_prestige_leftright_arrow
5 material featured_panel_1
6 material featured_panel_2
7 material featured_panel_3
8 material featured_panel_4
9 material featured_panel_5
10 material featured_panel_6
11 material featured_panel_7
12 material featured_panel_8
13 material featured_panel_thumbnail_1
14 material featured_panel_thumbnail_2
15 material featured_panel_thumbnail_3
16 material featured_panel_thumbnail_4
17 material featured_panel_thumbnail_5
18 material featured_panel_thumbnail_6
19 material featured_panel_thumbnail_7
20 material featured_panel_thumbnail_8
21 material motd_image

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_1",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_1",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_2",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_2",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_3",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_3",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_4",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_4",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_5",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_5",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_6",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_6",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_7",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_7",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_8",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_8",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_thumbnail_1",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_thumbnail_1",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_thumbnail_2",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_thumbnail_2",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_thumbnail_3",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_thumbnail_3",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_thumbnail_4",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_thumbnail_4",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_thumbnail_5",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_thumbnail_5",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_thumbnail_6",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_thumbnail_6",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_thumbnail_7",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_thumbnail_7",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "featured_panel_thumbnail_8",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "featured_panel_thumbnail_8",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "h1_prestige_leftright_arrow",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "h1_prestige_leftright-arrow",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "h1_ui_featured_pip_unfocused",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "h1_ui_featured_pip_unfocused",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "h2_ui_btn_focused_stroke_square",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "h2_btn_focused_rect_stroke",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "h2_ui_featured_pip_focused",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "h2_ui_featured_pip_focused",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -0,0 +1,26 @@
{
"name": "motd_image",
"techniqueSet->name": "2d",
"gameFlags": 0,
"sortKey": 60,
"renderFlags": 0,
"textureAtlasRowCount": 1,
"textureAtlasColumnCount": 1,
"textureAtlasFrameBlend": 0,
"textureAtlasAsArray": 0,
"surfaceTypeBits": 0,
"cameraRegion": 12,
"materialType": 0,
"assetFlags": 0,
"constantTable": null,
"textureTable": [
{
"image": "motd_image",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -1,4 +1,6 @@
{
"MENU_SP_CAMPAIGN": "H2-MOD",
"LOCALE_0": "English",
"LOCALE_1": "French",
"LOCALE_2": "German",

View File

@ -34,6 +34,7 @@ namespace config
{
{define_field("disable_custom_fonts", field_type::boolean, false)},
{define_field("language", field_type::string, language::get_default_language(), language::is_valid_language)},
{define_field("motd_last_seen", field_type::number_unsigned, 0)},
};
std::string get_config_file_path()

View File

@ -125,6 +125,29 @@ namespace fastfiles
return true;
}
bool try_add_zone(std::vector<game::XZoneInfo>& zones,
utils::memory::allocator& allocator, const std::string& name,
bool localized, bool game = false)
{
if (localized)
{
const auto language = game::SEH_GetCurrentLanguageCode();
try_add_zone(zones, allocator, language + "_"s + name, false);
}
if (!fastfiles::exists(name))
{
return false;
}
game::XZoneInfo info{};
info.name = allocator.duplicate_string(name);
info.allocFlags = (game ? game::DB_ZONE_GAME : game::DB_ZONE_COMMON) | game::DB_ZONE_CUSTOM;
info.freeFlags = 0;
zones.push_back(info);
return true;
}
void load_mod_zones()
{
try_load_zone("mod", true);
@ -138,28 +161,65 @@ namespace fastfiles
}
}
void load_pre_gfx_zones(game::XZoneInfo* zoneInfo,
unsigned int zoneCount, game::DBSyncMode syncMode)
void add_mod_zones(std::vector<game::XZoneInfo>& zones, utils::memory::allocator& allocator)
{
try_add_zone(zones, allocator, "mod", true);
const auto mod_zones = mods::get_mod_zones();
for (const auto& zone : mod_zones)
{
if (zone.alloc_flags & game::DB_ZONE_COMMON)
{
try_add_zone(zones, allocator, zone.name, true);
}
}
}
void push_zones(std::vector<game::XZoneInfo>& zones,
game::XZoneInfo* source, const size_t count)
{
for (auto i = 0; i < count; ++i)
{
zones.push_back(source[i]);
}
}
void load_pre_gfx_zones(game::XZoneInfo* zone_info,
unsigned int zone_count, game::DBSyncMode sync_mode)
{
// code_pre_gfx
try_load_zone("h2_mod_pre_gfx", true);
utils::memory::allocator allocator;
std::vector<game::XZoneInfo> zones;
try_add_zone(zones, allocator, "h2_mod_pre_gfx", true);
push_zones(zones, zone_info, zone_count);
game::DB_LoadXAssets(zoneInfo, zoneCount, syncMode);
game::DB_LoadXAssets(zones.data(), static_cast<int>(zones.size()), sync_mode);
}
void load_post_gfx_and_ui_and_common_zones(game::XZoneInfo* zoneInfo,
unsigned int zoneCount, game::DBSyncMode syncMode)
void load_post_gfx_and_ui_and_common_zones(game::XZoneInfo* zone_info,
unsigned int zone_count, game::DBSyncMode sync_mode)
{
// code_post_gfx_mp
// ui_mp
// common_mp
try_load_zone("h2_mod_common", true);
utils::memory::allocator allocator;
std::vector<game::XZoneInfo> zones;
game::DB_LoadXAssets(zoneInfo, zoneCount, syncMode);
try_add_zone(zones, allocator, "h2_mod_common", true);
for (auto i = 0u; i < zone_count; i++)
{
zones.push_back(zone_info[i]);
load_mod_zones();
if (zone_info[i].name == "code_post_gfx"s)
{
try_add_zone(zones, allocator, "h2_mod_ui", true);
}
}
add_mod_zones(zones, allocator);
game::DB_LoadXAssets(zones.data(), static_cast<int>(zones.size()), sync_mode);
}
constexpr unsigned int get_asset_type_size(const game::XAssetType type)
@ -432,7 +492,7 @@ namespace fastfiles
add_custom_level_load_zone(load, name, size_est);
}
void add_mod_zones(game::LevelLoad* load)
void add_mod_zones_to_load(game::LevelLoad* load)
{
const auto mod_zones = mods::get_mod_zones();
for (const auto& zone : mod_zones)
@ -450,7 +510,7 @@ namespace fastfiles
a.pushad64();
a.mov(rcx, rbx);
a.call_aligned(add_mod_zones);
a.call_aligned(add_mod_zones_to_load);
a.popad64();
a.mov(rcx, rdi);

View File

@ -12,6 +12,7 @@
#include <utils/image.hpp>
#include <utils/io.hpp>
#include <utils/concurrency.hpp>
#include <utils/http.hpp>
namespace images
{
@ -73,7 +74,7 @@ namespace images
return true;
}
void load_texture_stub(game::GfxImage* image, void* a2, int* a3)
void load_texture_stub(void* a1, game::GfxImage* image)
{
try
{
@ -87,7 +88,7 @@ namespace images
console::error("Failed to load image %s: %s\n", image->name, e.what());
}
load_texture_hook.invoke<void>(image, a2, a3);
load_texture_hook.invoke<void>(a1, image);
}
int setup_texture_stub(game::GfxImage* image, void* a2, void* a3)
@ -115,7 +116,7 @@ namespace images
void post_unpack() override
{
setup_texture_hook.create(0x1402A7940, setup_texture_stub);
load_texture_hook.create(0x1402A6690, load_texture_stub);
load_texture_hook.create(0x14074A390, load_texture_stub);
}
};
}

View File

@ -0,0 +1,203 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "motd.hpp"
#include "console.hpp"
#include "images.hpp"
#include "command.hpp"
#include <utils/string.hpp>
#include <utils/http.hpp>
#include <utils/concurrency.hpp>
#define MAX_FEATURED_TABS 8
namespace motd
{
namespace
{
utils::concurrency::container<nlohmann::json, std::recursive_mutex> marketing;
std::unordered_map<std::string, std::string> image_cache;
std::optional<std::string> download_image(const std::string& url)
{
if (image_cache.contains(url))
{
return {image_cache.at(url)};
}
const auto res = utils::http::get_data(url);
if (res.has_value())
{
image_cache[url] = res.value();
}
return res;
}
void download_motd_image(nlohmann::json& data)
{
if (!data["motd"].is_object() || !data["motd"]["image_url"].is_string())
{
return;
}
const auto url = data["motd"]["image_url"].get<std::string>();
const auto image_data = download_image(url);
if (image_data.has_value())
{
const auto& image = image_data.value();
console::debug("Downloaded motd image\n");
images::override_texture("motd_image", image);
}
}
void download_featured_tabs_images(nlohmann::json& data)
{
if (!data["featured"].is_array())
{
return;
}
auto index = 0;
for (const auto& [key, tab] : data["featured"].items())
{
index++;
if (index >= MAX_FEATURED_TABS + 1)
{
return;
}
if (!tab.is_object() || !tab["image_url"].is_string())
{
continue;
}
const auto download_image_ = [&](const std::string& field, const std::string& image_name)
{
const auto url = tab[field].get<std::string>();
const auto image_data = download_image(url);
if (image_data.has_value())
{
const auto& image = image_data.value();
console::debug("Downloaded featured tab image %i\n", index);
images::override_texture(image_name + std::format("_{}", index), image);
}
};
download_image_("image_url", "featured_panel");
download_image_("thumbnail_url", "featured_panel_thumbnail");
}
}
void download_images()
{
marketing.access([&](nlohmann::json& data)
{
if (!data.is_object())
{
return;
}
download_motd_image(data);
download_featured_tabs_images(data);
});
}
void init(bool load_images = true)
{
marketing.access([&](nlohmann::json& data)
{
image_cache.clear();
data.clear();
const auto marketing_data = utils::http::get_data("https://master.fed0001.xyz/h2-mod/marketing.json");
if (marketing_data.has_value())
{
try
{
const auto& value = marketing_data.value();
data = nlohmann::json::parse(value);
if (load_images)
{
download_images();
}
}
catch (const std::exception& e)
{
console::error("Failed to load marketing.json: %s\n", e.what());
}
}
});
}
}
int get_num_featured_tabs()
{
return marketing.access<nlohmann::json>([&](nlohmann::json& data)
-> nlohmann::json
{
if (!data.is_object() || !data["featured"].is_array())
{
return 0;
}
return std::min(MAX_FEATURED_TABS, static_cast<int>(data["featured"].size()));
});
}
nlohmann::json get_featured_tab(const int index)
{
return marketing.access<nlohmann::json>([&](nlohmann::json& data)
-> nlohmann::json
{
if (!data.is_object() || !data["featured"].is_array())
{
return {};
}
if (index >= data["featured"].size())
{
return {};
}
return data["featured"][index];
});
}
nlohmann::json get_motd()
{
return marketing.access<nlohmann::json>([](nlohmann::json& data)
-> nlohmann::json
{
if (!data.is_object() || !data["motd"].is_object())
{
return {};
}
return data["motd"];
});
}
class component final : public component_interface
{
public:
void post_unpack() override
{
init();
command::add("reloadmotd", []()
{
init(true);
});
command::add("reloadmotd_noimages", []()
{
init(false);
});
}
};
}
REGISTER_COMPONENT(motd::component)

View File

@ -0,0 +1,8 @@
#pragma once
namespace motd
{
int get_num_featured_tabs();
nlohmann::json get_motd();
nlohmann::json get_featured_tab(const int index);
}

View File

@ -15,6 +15,7 @@
#include "console.hpp"
#include "language.hpp"
#include "config.hpp"
#include "motd.hpp"
#include "game/ui_scripting/execution.hpp"
#include "game/scripting/execution.hpp"
@ -151,6 +152,7 @@ namespace ui_scripting
{
object[key] = json_to_lua(value);
}
return object;
}
if (json.is_array())
@ -161,6 +163,7 @@ namespace ui_scripting
{
array[index++] = json_to_lua(value);
}
return array;
}
if (json.is_boolean())
@ -604,6 +607,41 @@ namespace ui_scripting
{
config::set(key, lua_to_json(value));
};
auto motd_table = table();
lua["motd"] = motd_table;
motd_table["getnumfeaturedtabs"] = motd::get_num_featured_tabs;
motd_table["getmotd"] = []()
{
return json_to_lua(motd::get_motd());
};
motd_table["getfeaturedtab"] = [](const int index)
{
return json_to_lua(motd::get_featured_tab(index));
};
motd_table["hasseentoday"] = []()
{
const auto last_seen = config::get<uint64_t>("motd_last_seen");
if (!last_seen.has_value())
{
return false;
}
const auto value = static_cast<time_t>(last_seen.value());
const auto before = std::chrono::floor<std::chrono::days>(std::chrono::system_clock::from_time_t(value));
const auto now = std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now());
const auto diff = std::chrono::sys_days{now} - std::chrono::sys_days{before};
return diff.count() < 1;
};
motd_table["sethasseentoday"] = []()
{
const auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
config::set<uint64_t>("motd_last_seen", static_cast<uint64_t>(now));
};
}
void start()