Add achievements menu

This commit is contained in:
fed 2023-04-09 19:11:54 +02:00
parent 00050def13
commit 74455d3eb3
61 changed files with 611 additions and 225 deletions

View File

@ -1,196 +1,2 @@
if (Engine.InFrontend()) then
return
end
local hud = LUI.UIElement.new({
topAnchor = true,
leftAnchor = true,
width = 500,
height = 500,
left = 0
})
local hidetimer = LUI.UITimer.new(5000, "hide_toast")
local finishtimer = LUI.UITimer.new(6000, "notification_finished")
local animtime = 250
hud:setPriority(1000)
local isshowingachievement = false
local notificationqueue = {}
local shownotificationinternal = nil
local shownextnotification = function()
isshowingachievement = false
local data = notificationqueue[1]
if (data == nil) then
return
end
table.remove(notificationqueue, 1)
shownotificationinternal(data)
end
local createtoast = function()
local height = 80
local width = 400
local padding = 10
local leftoffset = 10
local titlefont = CoD.TextSettings.Font27
local descfont = CoD.TextSettings.Font21
local imagesize = height - padding * 2
local textoffset = padding * 2 + imagesize
local textyoffset = height / 2 - (titlefont.Height + descfont.Height) / 2
local text2yoffset = 5
local container = LUI.UIElement.new({
topAnchor = true,
leftAnchor = true,
top = 100,
left = 0,
width = width,
height = height
})
container:registerAnimationState("hide", {
topAnchor = true,
leftAnchor = true,
top = 100,
left = -1000,
width = width,
height = height
})
container:registerAnimationState("show", {
topAnchor = true,
leftAnchor = true,
top = 100,
left = 0,
width = width,
height = height
})
container:registerAnimationState("show_right", {
topAnchor = true,
leftAnchor = true,
top = 100,
left = 40,
width = width,
height = height
})
local bg = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
rightAnchor = true,
bottomAnchor = true,
color = {
r = 0.1,
g = 0.1,
b = 0.1,
},
alpha = 0.55
})
local image = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
top = padding,
left = padding + leftoffset,
width = imagesize,
height = imagesize,
})
local title = LUI.UIText.new({
topAnchor = true,
leftAnchor = true,
top = textyoffset,
left = textoffset + leftoffset,
font = titlefont.Font,
height = titlefont.Height
})
local desc = LUI.UIText.new({
topAnchor = true,
leftAnchor = true,
width = width - textoffset - leftoffset - padding,
alignment = LUI.Alignment.Left,
top = textyoffset + titlefont.Height + text2yoffset,
left = textoffset + leftoffset,
font = descfont.Font,
height = descfont.Height
})
container:addElement(bg)
container:addElement(image)
container:addElement(title)
container:addElement(desc)
container:addElement(LUI.DecoFrame.new(nil, LUI.DecoFrame.Grey))
container:animateToState("hide")
container:addElement(hidetimer)
container:addElement(finishtimer)
container:registerEventHandler("hide_toast", function()
LUI.UITimer.Stop(hidetimer)
container:animateToState("hide", animtime)
end)
container:registerEventHandler("notification_finished", function()
LUI.UITimer.Stop(finishtimer)
shownextnotification()
end)
hud:addElement(container)
container.image = image
container.title = title
container.desc = desc
return container
end
local toast = createtoast()
shownotificationinternal = function(data)
isshowingachievement = true
LUI.UITimer.Reset(hidetimer)
LUI.UITimer.Reset(finishtimer)
toast:animateToState("hide")
toast:animateToState("show", animtime)
toast.title:setText(data.title)
toast.desc:setText(data.description)
toast.image:setImage(data.image)
if (data.sound) then
Engine.PlaySound(data.sound)
end
end
function addnotification(data)
table.insert(notificationqueue, data)
if (not isshowingachievement) then
shownextnotification()
end
end
local addachievementnotification = function(id)
addnotification({
title = Engine.Localize("ACHIEVEMENT_EARNED"),
description = Engine.ToUpperCase(Engine.Localize("ACHIEVEMENT_NAME_" .. id)),
image = RegisterMaterial("trophy_" .. id),
sound = "ui_achievement_unlocked"
})
end
hud:registerEventHandler("earned_achievement", function(element, event)
addachievementnotification(event.id)
end)
LUI.roots.UIRoot0:addElement(hud)
require("toast")
require("menu")

View File

@ -0,0 +1,319 @@
local maincampaign = LUI.MenuBuilder.m_types_build["main_campaign"]
LUI.MenuBuilder.m_types_build["main_campaign"] = function(...)
local initlist = LUI.Options.InitScrollingList
LUI.Options.InitScrollingList = function(list)
initlist(list, nil ,{
rows = 10
})
end
local menu = maincampaign(...)
LUI.Options.InitScrollingList = initlist
return menu
end
LUI.MenuBuilder.registerType("achievements_menu", function(root, controller)
local menuwidth = 1129
local menu = LUI.MenuTemplate.new(root, {
menu_title = "@LUA_MENU_ACHIEVEMENTS",
exclusiveController = 0,
menu_width = menuwidth,
menu_top_indent = LUI.MenuTemplate.spMenuOffset,
showTopRightSmallBar = true,
uppercase_title = true
})
local itemwidth = 80
local itemspacing = 10
local maxrowelements = 1
local rowelements = 0
local newrow = function()
local currentrow = LUI.UIElement.new({
topAnchor = true,
leftAnchor = true,
width = itemwidth * maxrowelements,
height = itemwidth,
})
menu.list.currentrow = currentrow
menu.list:addElement(currentrow)
menu.list.currentrow:makeFocusable()
end
newrow()
local addelement = function(element)
local container = LUI.UIElement.new({
topAnchor = true,
leftAnchor = true,
width = itemwidth,
height = itemwidth,
left = rowelements * (itemwidth + itemspacing)
})
container:addElement(element)
container:makeFocusable()
menu.list.currentrow:addElement(container)
rowelements = rowelements + 1
if (rowelements == maxrowelements) then
newrow()
rowelements = 0
end
end
local achievementtable = achievements.table()
local unlockcount = 0
for i = 0, #achievementtable do
if (achievementtable[i]) then
unlockcount = unlockcount + 1
end
end
for i = 0, achievements.count() - 1 do
local trophyimage = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
width = menuwidth,
height = itemwidth,
material = RegisterMaterial("h2_btn_unfocused")
})
trophyimage:setup9SliceImage()
trophyimage:registerAnimationState("locked", {
material = RegisterMaterial("h2_btn_unfocused_locked")
})
local stroke = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
width = menuwidth,
height = itemwidth,
material = RegisterMaterial("h2_btn_focused_stroke")
})
stroke:registerAnimationState("hide", {
alpha = 0
})
stroke:registerAnimationState("show", {
alpha = 1
})
stroke:setup9SliceImage()
local glow = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
left = -20,
top = -20,
width = menuwidth + 40,
height = itemwidth + 40,
material = RegisterMaterial("h2_btn_focused_outerglow")
})
glow:registerAnimationState("hide", {
alpha = 0
})
glow:registerAnimationState("show", {
alpha = 1
})
glow:setup9SliceImage()
local pattern = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
width = menuwidth,
height = itemwidth,
material = RegisterMaterial("h2_btn_dot_pattern")
})
pattern:registerAnimationState("hide", {
alpha = 0
})
pattern:registerAnimationState("show", {
alpha = 1
})
stroke:animateToState("hide")
pattern:animateToState("hide")
glow:animateToState("hide")
local achievementcontainer = LUI.UIElement.new({
topAnchor = true,
leftAnchor = true,
width = menuwidth,
height = itemwidth,
})
local image = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
top = itemspacing / 2,
left = itemspacing / 2,
width = itemwidth - itemspacing,
height = itemwidth - itemspacing,
material = RegisterMaterial("trophy_" .. i)
})
image:registerAnimationState("locked", {
topAnchor = true,
leftAnchor = true,
top = itemspacing / 2 + 15,
left = itemspacing / 2 + 15,
width = itemwidth - itemspacing - 30,
height = itemwidth - itemspacing - 30,
material = RegisterMaterial("icon_lock_mini")
})
local textwidth = menuwidth - itemwidth - itemspacing * 2
local title = LUI.UIText.new({
topAnchor = true,
leftAnchor = true,
top = itemspacing + 5,
left = itemwidth + 5,
color = {
r = 0.7,
g = 0.7,
b = 0.7,
},
width = textwidth,
font = CoD.TextSettings.Font23.Font,
height = CoD.TextSettings.Font23.Height
})
local desc = LUI.UIText.new({
topAnchor = true,
leftAnchor = true,
top = itemspacing + CoD.TextSettings.Font23.Height + 10,
left = itemwidth + 5,
width = textwidth,
alignment = LUI.Alignment.Left,
color = {
r = 0.7,
g = 0.7,
b = 0.7,
},
font = CoD.TextSettings.Font21.Font,
height = CoD.TextSettings.Font21.Height
})
local focusedtext = {
color = {r = 1, g = 1, b = 1}
}
desc:registerAnimationState("focused", focusedtext)
title:registerAnimationState("focused", focusedtext)
title:setText(Engine.ToUpperCase(Engine.Localize(achievements.getname(i))))
desc:setText(Engine.Localize(achievements.getdetails(i)))
achievementcontainer:setHandleMouse(true)
local locked = not achievementtable[i]
achievementcontainer:registerEventHandler("mouseenter", function()
Engine.PlaySound(CoD.SFX.MouseOver)
if (locked) then
trophyimage:setImage(RegisterMaterial("h2_btn_focused_locked"))
else
stroke:animateToState("show")
pattern:animateToState("show")
title:animateToState("focused")
desc:animateToState("focused")
glow:animateToState("show")
end
end)
achievementcontainer:registerEventHandler("mouseleave", function()
if (locked) then
trophyimage:setImage(RegisterMaterial("h2_btn_unfocused_locked"))
else
stroke:animateToState("hide")
pattern:animateToState("hide")
title:animateToState("default")
desc:animateToState("default")
glow:animateToState("hide")
end
end)
achievementcontainer:addElement(trophyimage)
achievementcontainer:addElement(pattern)
achievementcontainer:addElement(glow)
achievementcontainer:addElement(stroke)
achievementcontainer:addElement(image)
achievementcontainer:addElement(title)
achievementcontainer:addElement(desc)
if (locked) then
trophyimage:animateToState("locked")
image:animateToState("locked")
title:setText(Engine.Localize("@LUA_MENU_UNKNOWN_ACHIEVEMENT"))
desc:setText("")
end
addelement(achievementcontainer)
end
if (rowelements == 0) then
menu.list.currentrow:close()
end
LUI.Options.InitScrollingList(menu.list, nil, {
rows = 5
})
local createprogressbar = function()
local barwidth = menuwidth + 17
local progressbar = LUI.UIElement.new({
bottomAnchor = true,
leftAnchor = true,
width = barwidth,
bottom = -90,
height = 15
})
local bg = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
rightAnchor = true,
bottomAnchor = true,
alpha = 0.55,
color = Colors.grey_14,
})
local bar = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
bottomAnchor = true,
width = 0,
material = RegisterMaterial("h1_ui_progressbar_green")
})
progressbar.setprogress = function(fraction)
bar:registerAnimationState("progress", {
topAnchor = true,
leftAnchor = true,
bottomAnchor = true,
width = fraction * (barwidth),
})
bar:animateToState("progress", 300)
end
progressbar.setprogress(unlockcount / achievements.count())
progressbar:addElement(bg)
progressbar:addElement(bar)
progressbar:addElement(LUI.DecoFrame.new(nil, LUI.DecoFrame.Grey))
return progressbar
end
menu:addElement(createprogressbar())
menu:AddBackButton()
return menu
end)

View File

@ -0,0 +1,203 @@
if (Engine.InFrontend()) then
return
end
local hud = LUI.UIElement.new({
topAnchor = true,
leftAnchor = true,
width = 500,
height = 500,
left = 0
})
local hidetimer = LUI.UITimer.new(5000, "hide_toast")
local finishtimer = LUI.UITimer.new(6000, "notification_finished")
local animtime = 250
hud:setPriority(1000)
local isshowingachievement = false
local notificationqueue = {}
local shownotificationinternal = nil
local shownextnotification = function()
isshowingachievement = false
local data = notificationqueue[1]
if (data == nil) then
return
end
table.remove(notificationqueue, 1)
shownotificationinternal(data)
end
local createtoast = function()
local height = 80
local width = 400
local padding = 10
local leftoffset = 10
local titlefont = CoD.TextSettings.Font27
local descfont = CoD.TextSettings.Font21
local imagesize = height - padding * 2
local textoffset = padding * 2 + imagesize
local textyoffset = height / 2 - (titlefont.Height + descfont.Height) / 2
local text2yoffset = 5
local container = LUI.UIElement.new({
topAnchor = true,
leftAnchor = true,
top = 100,
left = 0,
width = width,
height = height
})
container:registerAnimationState("hide", {
topAnchor = true,
leftAnchor = true,
top = 100,
left = -1000,
width = width,
height = height
})
container:registerAnimationState("show", {
topAnchor = true,
leftAnchor = true,
top = 100,
left = 0,
width = width,
height = height
})
container:registerAnimationState("show_right", {
topAnchor = true,
leftAnchor = true,
top = 100,
left = 40,
width = width,
height = height
})
local bg = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
rightAnchor = true,
bottomAnchor = true,
alpha = 0.55,
color = Colors.grey_14,
})
local imagebg = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
top = padding,
left = padding + leftoffset,
width = imagesize,
height = imagesize,
material = RegisterMaterial("trophy_border")
})
local image = LUI.UIImage.new({
topAnchor = true,
leftAnchor = true,
top = padding,
left = padding + leftoffset,
width = imagesize,
height = imagesize,
})
local title = LUI.UIText.new({
topAnchor = true,
leftAnchor = true,
top = textyoffset,
left = textoffset + leftoffset,
font = titlefont.Font,
height = titlefont.Height
})
local desc = LUI.UIText.new({
topAnchor = true,
leftAnchor = true,
width = width - textoffset - leftoffset - padding,
alignment = LUI.Alignment.Left,
top = textyoffset + titlefont.Height + text2yoffset,
left = textoffset + leftoffset,
font = descfont.Font,
height = descfont.Height
})
container:addElement(bg)
container:addElement(imagebg)
container:addElement(image)
container:addElement(title)
container:addElement(desc)
container:addElement(LUI.DecoFrame.new(nil, LUI.DecoFrame.Grey))
container:animateToState("hide")
container:addElement(hidetimer)
container:addElement(finishtimer)
container:registerEventHandler("hide_toast", function()
LUI.UITimer.Stop(hidetimer)
container:animateToState("hide", animtime)
end)
container:registerEventHandler("notification_finished", function()
LUI.UITimer.Stop(finishtimer)
shownextnotification()
end)
hud:addElement(container)
container.image = image
container.title = title
container.desc = desc
return container
end
local toast = createtoast()
shownotificationinternal = function(data)
isshowingachievement = true
LUI.UITimer.Reset(hidetimer)
LUI.UITimer.Reset(finishtimer)
toast:animateToState("hide")
toast:animateToState("show", animtime)
toast.title:setText(data.title)
toast.desc:setText(data.description)
toast.image:setImage(data.image)
if (data.sound) then
Engine.PlaySound(data.sound)
end
end
function addnotification(data)
table.insert(notificationqueue, data)
if (not isshowingachievement) then
shownextnotification()
end
end
local addachievementnotification = function(id)
addnotification({
title = Engine.Localize("ACHIEVEMENT_EARNED"),
description = Engine.ToUpperCase(Engine.Localize("ACHIEVEMENT_NAME_" .. id)),
image = RegisterMaterial("trophy_" .. id),
sound = "ui_achievement_unlocked"
})
end
hud:registerEventHandler("earned_achievement", function(element, event)
addachievementnotification(event.id)
end)
LUI.roots.UIRoot0:addElement(hud)

View File

@ -29,6 +29,15 @@ function string:truncate(length)
return self:sub(1, length - 3) .. "..."
end
LUI.addmenubutton("main_campaign", {
index = 6,
text = "@LUA_MENU_ACHIEVEMENTS",
description = Engine.Localize("@LUA_MENU_ACHIEVEMENTS_DESC"),
callback = function()
LUI.FlowManager.RequestAddMenu(nil, "achievements_menu")
end
})
LUI.addmenubutton("main_campaign", {
index = 6,
text = "@MENU_MODS",

Binary file not shown.

View File

@ -0,0 +1,26 @@
{
"name": "trophy_border",
"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": "trophy_border",
"semantic": 0,
"samplerState": 226,
"lastCharacter": 112,
"firstCharacter": 99,
"typeHash": 2695565377
}
]
}

View File

@ -121,6 +121,10 @@
"DEPOT_GO_TO_THE_DEPOT": "Open link",
"MENU_OPEN_MOTD": "Open Message of the Day",
"LUA_MENU_ACHIEVEMENTS": "ACHIEVEMENTS",
"LUA_MENU_ACHIEVEMENTS_DESC": "View achievements.",
"LUA_MENU_UNKNOWN_ACHIEVEMENT": "???",
"ACHIEVEMENT_EARNED": "Achievement Unlocked!",
"ACHIEVEMENT_NAME_0": "Is That All You Got?",
"ACHIEVEMENT_DETAIL_0": "Earn all available trophies for Call of Duty®: Modern Warfare® 2 Campaign Remastered.",

View File

@ -78,40 +78,12 @@ namespace achievements
achievement_t(ACHIEVEMENT_50, "HEADBANGER", ACHIEVEMENT_RARITY_0),
};
struct achievement_file_t
{
std::uint32_t signature;
std::uint8_t version;
bool achievements[ACHIEVEMENT_COUNT];
};
std::filesystem::path get_achievements_path()
{
return utils::properties::get_appdata_path() / "player/achievements.bin";
}
void get_achievements(achievement_file_t* file)
{
std::lock_guard _0(file_mutex);
const auto path = get_achievements_path().generic_string();
if (!utils::io::file_exists(path))
{
return;
}
const auto data = utils::io::read_file(path);
if (data.size() < sizeof(achievement_file_t))
{
return;
}
std::memcpy(file, data.data(), sizeof(achievement_file_t));
if (file->signature != ACHIEVEMENT_FILE_SIGNATURE)
{
std::memset(file, 0, sizeof(achievement_file_t));
}
}
void write_achievements(achievement_file_t* data)
{
@ -177,7 +149,7 @@ namespace achievements
void give_achievement_id_internal(achievement_file_t* file, int id)
{
get_achievements(file);
achievements::get_achievements(file);
if (has_achievement(file, id))
{
return;
@ -216,6 +188,29 @@ namespace achievements
}
}
void get_achievements(achievement_file_t* file)
{
std::lock_guard _0(file_mutex);
const auto path = get_achievements_path().generic_string();
if (!utils::io::file_exists(path))
{
return;
}
const auto data = utils::io::read_file(path);
if (data.size() < sizeof(achievement_file_t))
{
return;
}
std::memcpy(file, data.data(), sizeof(achievement_file_t));
if (file->signature != ACHIEVEMENT_FILE_SIGNATURE)
{
std::memset(file, 0, sizeof(achievement_file_t));
}
}
int get_count()
{
return ACHIEVEMENT_COUNT;

View File

@ -76,6 +76,15 @@ namespace achievements
achievement_rarity rarity;
};
struct achievement_file_t
{
std::uint32_t signature;
std::uint8_t version;
bool achievements[ACHIEVEMENT_COUNT];
};
void get_achievements(achievement_file_t* file);
int get_count();
bool has_achievement(int id);
std::string get_name(int id);

View File

@ -682,6 +682,21 @@ namespace ui_scripting
achievements_table["getname"] = achievements::get_name;
achievements_table["getdetails"] = achievements::get_details;
achievements_table["count"] = achievements::get_count;
achievements_table["table"] = []()
{
table table{};
achievements::achievement_file_t file{};
achievements::get_achievements(&file);
for (auto i = 0; i < achievements::ACHIEVEMENT_COUNT; i++)
{
table[i] = file.achievements[i];
}
return table;
};
}
void start()