From 53bef0628269ea94f7ad6ac3cb24762b5460cdd7 Mon Sep 17 00:00:00 2001 From: Jakub Audykowicz Date: Sat, 13 Jul 2024 14:50:01 +0200 Subject: [PATCH 1/6] Add support for campaign status --- data/RTTR/campaigns/roman/MISS200.lua | 2 +- data/RTTR/campaigns/roman/MISS201.lua | 4 +- data/RTTR/campaigns/roman/MISS202.lua | 4 +- data/RTTR/campaigns/roman/MISS203.lua | 4 +- data/RTTR/campaigns/roman/MISS204.lua | 4 +- data/RTTR/campaigns/roman/MISS205.lua | 4 +- data/RTTR/campaigns/roman/MISS206.lua | 4 +- data/RTTR/campaigns/roman/MISS207.lua | 4 +- data/RTTR/campaigns/roman/MISS208.lua | 4 +- data/RTTR/campaigns/roman/MISS209.lua | 4 +- data/RTTR/campaigns/roman/campaign.lua | 1 + data/RTTR/campaigns/world/AFRICA.lua | 6 ++ data/RTTR/campaigns/world/AUSTRA.lua | 7 ++ data/RTTR/campaigns/world/EUROPE.lua | 8 ++ data/RTTR/campaigns/world/GREEN.lua | 6 ++ data/RTTR/campaigns/world/JAPAN.lua | 7 ++ data/RTTR/campaigns/world/NAMERICA.lua | 7 ++ data/RTTR/campaigns/world/NASIA.lua | 7 ++ data/RTTR/campaigns/world/SAMERICA.lua | 6 ++ data/RTTR/campaigns/world/SASIA.lua | 8 ++ data/RTTR/campaigns/world/campaign.lua | 22 ++-- doc/lua/events.md | 3 + .../gameData/CampaignDescription.cpp | 4 + .../gameData/CampaignDescription.h | 2 + libs/libGamedata/gameData/CampaignSaveCodes.h | 12 +++ libs/s25main/CampaignSaveData.cpp | 45 ++++++++ libs/s25main/CampaignSaveData.h | 12 +++ libs/s25main/Game.cpp | 15 +++ libs/s25main/Game.h | 1 + libs/s25main/GameManager.cpp | 5 + libs/s25main/Settings.cpp | 32 +++++- libs/s25main/Settings.h | 7 +- .../dskCampaignMissionMapSelection.cpp | 6 +- .../desktops/dskCampaignMissionSelection.cpp | 6 +- .../s25main/desktops/dskCampaignSelection.cpp | 4 +- libs/s25main/desktops/dskCampaignVictory.cpp | 39 +++++++ libs/s25main/desktops/dskCampaignVictory.h | 21 ++++ libs/s25main/lua/LuaInterfaceGame.cpp | 78 +++++++++++--- libs/s25main/lua/LuaInterfaceGame.h | 6 ++ libs/s25main/network/GameClient.h | 8 ++ .../s25Main/campaign/testCampaignLuaFile.cpp | 13 ++- .../s25Main/campaign/testCampaignSaveData.cpp | 102 ++++++++++++++++++ tests/s25Main/lua/testLua.cpp | 15 +++ 43 files changed, 500 insertions(+), 59 deletions(-) create mode 100644 libs/libGamedata/gameData/CampaignSaveCodes.h create mode 100644 libs/s25main/CampaignSaveData.cpp create mode 100644 libs/s25main/CampaignSaveData.h create mode 100644 libs/s25main/desktops/dskCampaignVictory.cpp create mode 100644 libs/s25main/desktops/dskCampaignVictory.h create mode 100644 tests/s25Main/campaign/testCampaignSaveData.cpp diff --git a/data/RTTR/campaigns/roman/MISS200.lua b/data/RTTR/campaigns/roman/MISS200.lua index 64ea3b4cb5..402f2c9a73 100644 --- a/data/RTTR/campaigns/roman/MISS200.lua +++ b/data/RTTR/campaigns/roman/MISS200.lua @@ -508,9 +508,9 @@ function MissionEvent(e, onLoad) rttr:GetPlayer(0):EnableBuilding(BLD_BAKERY, not onLoad) elseif(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(14, 8, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 1) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS201.lua b/data/RTTR/campaigns/roman/MISS201.lua index 2f75fe0623..f8dde050a7 100644 --- a/data/RTTR/campaigns/roman/MISS201.lua +++ b/data/RTTR/campaigns/roman/MISS201.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level -- RttR: AI doesn't go south @@ -514,9 +513,10 @@ function MissionEvent(e, onLoad) rttr:GetPlayer(1):DisableBuilding(BLD_CATAPULT, false) elseif(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(48, 9, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 2) + rttr:EnableCampaignChapter("roman", 3) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS202.lua b/data/RTTR/campaigns/roman/MISS202.lua index e870a952dc..f997378c66 100644 --- a/data/RTTR/campaigns/roman/MISS202.lua +++ b/data/RTTR/campaigns/roman/MISS202.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -521,9 +520,10 @@ function MissionEvent(e, onLoad) rttr:GetPlayer(2):SetRestrictedArea() elseif(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(89, 20, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 3) + rttr:EnableCampaignChapter("roman", 4) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS203.lua b/data/RTTR/campaigns/roman/MISS203.lua index 7ab2e5aa65..3b6146c212 100644 --- a/data/RTTR/campaigns/roman/MISS203.lua +++ b/data/RTTR/campaigns/roman/MISS203.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -513,9 +512,10 @@ function MissionEvent(e, onLoad) rttr:GetPlayer(2):SetRestrictedArea() elseif(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(97, 68, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 4) + rttr:EnableCampaignChapter("roman", 5) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS204.lua b/data/RTTR/campaigns/roman/MISS204.lua index 1556ba7362..2d74fc06c1 100644 --- a/data/RTTR/campaigns/roman/MISS204.lua +++ b/data/RTTR/campaigns/roman/MISS204.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -433,9 +432,10 @@ function MissionEvent(e, onLoad) -- call side effects for active events, check "eState[e] == 1" for multiple call events! if(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(19, 37, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 5) + rttr:EnableCampaignChapter("roman", 6) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS205.lua b/data/RTTR/campaigns/roman/MISS205.lua index 2c1fe3d0bc..8caabfbe72 100644 --- a/data/RTTR/campaigns/roman/MISS205.lua +++ b/data/RTTR/campaigns/roman/MISS205.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -559,9 +558,10 @@ function MissionEvent(e, onLoad) -- call side effects for active events, check "eState[e] == 1" for multiple call events! elseif(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(148, 50, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 6) + rttr:EnableCampaignChapter("roman", 7) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS206.lua b/data/RTTR/campaigns/roman/MISS206.lua index 4c3ab21df1..195087603c 100644 --- a/data/RTTR/campaigns/roman/MISS206.lua +++ b/data/RTTR/campaigns/roman/MISS206.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -490,9 +489,10 @@ function MissionEvent(e, onLoad) -- call side effects for active events, check "eState[e] == 1" for multiple call events! elseif(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(13, 66, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 7) + rttr:EnableCampaignChapter("roman", 8) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS207.lua b/data/RTTR/campaigns/roman/MISS207.lua index 792e8c1d9b..c4a938d5e4 100644 --- a/data/RTTR/campaigns/roman/MISS207.lua +++ b/data/RTTR/campaigns/roman/MISS207.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -441,9 +440,10 @@ function MissionEvent(e, onLoad) rttr:GetPlayer(2):SetRestrictedArea() elseif(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(11, 125, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 8) + rttr:EnableCampaignChapter("roman", 9) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS208.lua b/data/RTTR/campaigns/roman/MISS208.lua index 2804237061..0c37c9b635 100644 --- a/data/RTTR/campaigns/roman/MISS208.lua +++ b/data/RTTR/campaigns/roman/MISS208.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -441,9 +440,10 @@ function MissionEvent(e, onLoad) rttr:GetPlayer(2):SetRestrictedArea() elseif(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc - Done rttr:GetWorld():AddStaticObject(127, 48, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 9) + rttr:EnableCampaignChapter("roman", 10) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS209.lua b/data/RTTR/campaigns/roman/MISS209.lua index 7fbced25d2..36967a8028 100644 --- a/data/RTTR/campaigns/roman/MISS209.lua +++ b/data/RTTR/campaigns/roman/MISS209.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -469,9 +468,10 @@ function MissionEvent(e, onLoad) rttr:GetPlayer(2):SetRestrictedArea() elseif(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(75, 40, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 10) + rttr:SetCampaignCompleted("roman") end -- update event state diff --git a/data/RTTR/campaigns/roman/campaign.lua b/data/RTTR/campaigns/roman/campaign.lua index b797324a2b..42c2e16f7f 100644 --- a/data/RTTR/campaigns/roman/campaign.lua +++ b/data/RTTR/campaigns/roman/campaign.lua @@ -21,6 +21,7 @@ rttr:RegisterTranslations( campaign = { version = 1, + uid = "roman", author = "Bluebyte", name = _"name", shortDescription = _"shortDescription", diff --git a/data/RTTR/campaigns/world/AFRICA.lua b/data/RTTR/campaigns/world/AFRICA.lua index 7f6a487085..5fe46d762f 100644 --- a/data/RTTR/campaigns/world/AFRICA.lua +++ b/data/RTTR/campaigns/world/AFRICA.lua @@ -66,3 +66,9 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 2) + rttr:EnableCampaignChapter("world", 8) -- sasia +end diff --git a/data/RTTR/campaigns/world/AUSTRA.lua b/data/RTTR/campaigns/world/AUSTRA.lua index 6cfbbdf515..ad89c1c3ac 100644 --- a/data/RTTR/campaigns/world/AUSTRA.lua +++ b/data/RTTR/campaigns/world/AUSTRA.lua @@ -54,3 +54,10 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 6) + rttr:EnableCampaignChapter("world", 8) -- sasia + rttr:EnableCampaignChapter("world", 9) -- japan +end diff --git a/data/RTTR/campaigns/world/EUROPE.lua b/data/RTTR/campaigns/world/EUROPE.lua index d18f7ecb06..0393c8cf37 100644 --- a/data/RTTR/campaigns/world/EUROPE.lua +++ b/data/RTTR/campaigns/world/EUROPE.lua @@ -78,3 +78,11 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 1) + rttr:EnableCampaignChapter("world", 2) -- africa + rttr:EnableCampaignChapter("world", 5) -- green + rttr:EnableCampaignChapter("world", 7) -- nasia +end diff --git a/data/RTTR/campaigns/world/GREEN.lua b/data/RTTR/campaigns/world/GREEN.lua index ef14c6e500..ae2e3a793d 100644 --- a/data/RTTR/campaigns/world/GREEN.lua +++ b/data/RTTR/campaigns/world/GREEN.lua @@ -54,3 +54,9 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 5) + rttr:EnableCampaignChapter("world", 3) -- namerica +end diff --git a/data/RTTR/campaigns/world/JAPAN.lua b/data/RTTR/campaigns/world/JAPAN.lua index 7777bfabea..9e1fb70a83 100644 --- a/data/RTTR/campaigns/world/JAPAN.lua +++ b/data/RTTR/campaigns/world/JAPAN.lua @@ -54,3 +54,10 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 9) + rttr:EnableCampaignChapter("world", 6) -- austra + rttr:EnableCampaignChapter("world", 7) -- nasia +end diff --git a/data/RTTR/campaigns/world/NAMERICA.lua b/data/RTTR/campaigns/world/NAMERICA.lua index 52c8264fe6..643a2a855d 100644 --- a/data/RTTR/campaigns/world/NAMERICA.lua +++ b/data/RTTR/campaigns/world/NAMERICA.lua @@ -60,3 +60,10 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 3) + rttr:EnableCampaignChapter("world", 4) -- samerica + rttr:EnableCampaignChapter("world", 5) -- green +end diff --git a/data/RTTR/campaigns/world/NASIA.lua b/data/RTTR/campaigns/world/NASIA.lua index 29c88c73f7..5e94155bc3 100644 --- a/data/RTTR/campaigns/world/NASIA.lua +++ b/data/RTTR/campaigns/world/NASIA.lua @@ -66,3 +66,10 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 7) + rttr:EnableCampaignChapter("world", 8) -- sasia + rttr:EnableCampaignChapter("world", 9) -- japan +end diff --git a/data/RTTR/campaigns/world/SAMERICA.lua b/data/RTTR/campaigns/world/SAMERICA.lua index bc98c65f77..057381dd61 100644 --- a/data/RTTR/campaigns/world/SAMERICA.lua +++ b/data/RTTR/campaigns/world/SAMERICA.lua @@ -60,3 +60,9 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 4) + rttr:EnableCampaignChapter("world", 3) -- namerica +end diff --git a/data/RTTR/campaigns/world/SASIA.lua b/data/RTTR/campaigns/world/SASIA.lua index f45868ba3f..cb7cf10233 100644 --- a/data/RTTR/campaigns/world/SASIA.lua +++ b/data/RTTR/campaigns/world/SASIA.lua @@ -60,3 +60,11 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 8) + rttr:EnableCampaignChapter("world", 2) -- africa + rttr:EnableCampaignChapter("world", 6) -- austra + rttr:EnableCampaignChapter("world", 7) -- nasia +end diff --git a/data/RTTR/campaigns/world/campaign.lua b/data/RTTR/campaigns/world/campaign.lua index aa9ceb7ddf..ceb3e89dad 100644 --- a/data/RTTR/campaigns/world/campaign.lua +++ b/data/RTTR/campaigns/world/campaign.lua @@ -21,6 +21,7 @@ rttr:RegisterTranslations( campaign = { version = 1, + uid = "world", author = "Bluebyte", name = _"name", shortDescription = _"shortDescription", @@ -30,7 +31,8 @@ campaign = { difficulty = "easy", mapFolder = "/DATA/MAPS2", luaFolder = "/CAMPAIGNS/WORLD", - maps = { "EUROPE.WLD","NAMERICA.WLD","SAMERICA.WLD","GREEN.WLD","AFRICA.WLD","NASIA.WLD","SASIA.WLD","JAPAN.WLD","AUSTRA.WLD"}, + maps = { "EUROPE.WLD","AFRICA.WLD","NAMERICA.WLD","SAMERICA.WLD","GREEN.WLD","AUSTRA.WLD","NASIA.WLD","SASIA.WLD","JAPAN.WLD"}, + defaultChaptersEnabled = "100000000", selectionMap = { background = {"/GFX/PICS/SETUP990.LBM", 0}, map = {"/GFX/PICS/WORLD.LBM", 0}, @@ -40,15 +42,15 @@ campaign = { backgroundOffset = {64, 70}, disabledColor = 0x70000000, missionSelectionInfos = { - {0xffffff00, 243, 97}, - {0xffaf73cb, 55,78}, - {0xff008fc3, 122, 193}, - {0xff43c373, 166, 36}, - {0xff27871b, 241,176}, - {0xffc32323, 366,87}, - {0xff573327, 375,145}, - {0xffcfaf4b, 486, 136}, - {0xffbb6313, 441, 264} + {0xffffff00, 243, 97}, -- europe + {0xff27871b, 241,176}, -- africa + {0xffaf73cb, 55,78}, -- namerica + {0xff008fc3, 122, 193}, -- samerica + {0xff43c373, 166, 36}, -- green + {0xffbb6313, 441, 264}, -- austra + {0xffc32323, 366,87}, -- nasia + {0xff573327, 375,145}, -- sasia + {0xffcfaf4b, 486, 136} -- japan } } } diff --git a/doc/lua/events.md b/doc/lua/events.md index 61f842fa10..598827c5e4 100644 --- a/doc/lua/events.md +++ b/doc/lua/events.md @@ -107,3 +107,6 @@ Called when a pact has been canceled. **onPactCreated(PactType, suggestedByPlayerIdx, targetPlayerIdx, duration)** Called when a pact has been confirmed. + +**onHumanWinner()** +Called when the game is won by a human. diff --git a/libs/libGamedata/gameData/CampaignDescription.cpp b/libs/libGamedata/gameData/CampaignDescription.cpp index 2c653db788..0f8e7b1e06 100644 --- a/libs/libGamedata/gameData/CampaignDescription.cpp +++ b/libs/libGamedata/gameData/CampaignDescription.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "CampaignDescription.h" +#include "CampaignSaveCodes.h" #include "RttrConfig.h" #include "helpers/format.hpp" #include "lua/CheckedLuaTable.h" @@ -12,6 +13,7 @@ CampaignDescription::CampaignDescription(const kaguya::LuaRef& table) { CheckedLuaTable luaData(table); + luaData.getOrThrow(uid, "uid"); luaData.getOrThrow(version, "version"); luaData.getOrThrow(author, "author"); luaData.getOrThrow(name, "name"); @@ -33,6 +35,8 @@ CampaignDescription::CampaignDescription(const kaguya::LuaRef& table) lua::validatePath(mapFolder); lua::validatePath(luaFolder); mapNames = luaData.getOrDefault("maps", std::vector()); + defaultChaptersEnabled = + luaData.getOrDefault("defaultChaptersEnabled", std::string{CampaignSaveCodes::defaultChaptersEnabled}); selectionMapData = luaData.getOptional("selectionMap"); luaData.checkUnused(); } diff --git a/libs/libGamedata/gameData/CampaignDescription.h b/libs/libGamedata/gameData/CampaignDescription.h index 327db6552e..add6a38bb6 100644 --- a/libs/libGamedata/gameData/CampaignDescription.h +++ b/libs/libGamedata/gameData/CampaignDescription.h @@ -15,6 +15,7 @@ class LuaRef; struct CampaignDescription { + std::string uid; std::string version; std::string author; std::string name; @@ -23,6 +24,7 @@ struct CampaignDescription std::string image; unsigned maxHumanPlayers = 0; std::string difficulty; + std::string defaultChaptersEnabled; std::optional selectionMapData; CampaignDescription() = default; diff --git a/libs/libGamedata/gameData/CampaignSaveCodes.h b/libs/libGamedata/gameData/CampaignSaveCodes.h new file mode 100644 index 0000000000..e9f2dfb2e3 --- /dev/null +++ b/libs/libGamedata/gameData/CampaignSaveCodes.h @@ -0,0 +1,12 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace CampaignSaveCodes { +constexpr auto chapterDisabled = '0'; +constexpr auto chapterEnabled = '1'; +constexpr auto chapterCompleted = '2'; +constexpr auto defaultChaptersEnabled = "1100000000"; +} // namespace CampaignSaveCodes diff --git a/libs/s25main/CampaignSaveData.cpp b/libs/s25main/CampaignSaveData.cpp new file mode 100644 index 0000000000..0b4ec04fe7 --- /dev/null +++ b/libs/s25main/CampaignSaveData.cpp @@ -0,0 +1,45 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "CampaignSaveData.h" +#include "Settings.h" +#include "gameData/CampaignDescription.h" +#include "gameData/CampaignSaveCodes.h" + +namespace { +auto getCampaignSaveData(const CampaignDescription& campaignDesc) +{ + auto& saveData = SETTINGS.campaigns.saveData; + + if(!saveData.count(campaignDesc.uid)) + saveData[campaignDesc.uid] = campaignDesc.defaultChaptersEnabled; + + return saveData[campaignDesc.uid]; +} +} // namespace + +bool isChapterEnabled(const CampaignDescription& campaignDesc, unsigned char chapter) +{ + const auto& saveData = getCampaignSaveData(campaignDesc); + + if(chapter >= saveData.length()) + return false; + + return saveData[chapter] != CampaignSaveCodes::chapterDisabled; +} + +std::vector getMissionsStatus(const CampaignDescription& campaignDesc) +{ + const auto& saveData = getCampaignSaveData(campaignDesc); + + std::vector ret(saveData.length()); + for(auto i = 0u; i < saveData.length(); ++i) + { + ret[i].playable = saveData[i] != CampaignSaveCodes::chapterDisabled; + ret[i].conquered = saveData[i] == CampaignSaveCodes::chapterCompleted; + } + + ret.resize(campaignDesc.getNumMaps()); + return ret; +} diff --git a/libs/s25main/CampaignSaveData.h b/libs/s25main/CampaignSaveData.h new file mode 100644 index 0000000000..fa1fa3de03 --- /dev/null +++ b/libs/s25main/CampaignSaveData.h @@ -0,0 +1,12 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "controls/ctrlMapSelection.h" + +struct CampaignDescription; + +bool isChapterEnabled(const CampaignDescription& campaignDesc, unsigned char chapter); +std::vector getMissionsStatus(const CampaignDescription& campaignDesc); diff --git a/libs/s25main/Game.cpp b/libs/s25main/Game.cpp index 20e37c6bdb..da4cc9d779 100644 --- a/libs/s25main/Game.cpp +++ b/libs/s25main/Game.cpp @@ -178,9 +178,24 @@ void Game::CheckObjective() world_.GetGameInterface()->GI_TeamWinner(*bestTeam); else world_.GetGameInterface()->GI_Winner(bestPlayer); + + if(world_.HasLua() && IsWinnerHuman(bestTeam.value_or(0), bestPlayer)) + world_.GetLua().EventHumanWinner(); } } +bool Game::IsWinnerHuman(unsigned bestTeam, unsigned bestPlayer) const +{ + if(world_.GetPlayer(bestPlayer).isHuman()) + return true; + + for(auto i = 0u; i < world_.GetNumPlayers(); ++i) + if(world_.GetPlayer(bestTeam & (1 << i)).isHuman()) + return true; + + return false; +} + AIPlayer* Game::GetAIPlayer(unsigned id) { for(AIPlayer& ai : aiPlayers_) diff --git a/libs/s25main/Game.h b/libs/s25main/Game.h index 45b3c0c954..a114927906 100644 --- a/libs/s25main/Game.h +++ b/libs/s25main/Game.h @@ -38,6 +38,7 @@ class Game void StatisticStep(); /// Check if the objective was reached (if set) void CheckObjective(); + bool IsWinnerHuman(unsigned bestTeam, unsigned bestPlayer) const; bool started_, finished_; std::unique_ptr lua; diff --git a/libs/s25main/GameManager.cpp b/libs/s25main/GameManager.cpp index 4c33ee0552..be118ed48e 100644 --- a/libs/s25main/GameManager.cpp +++ b/libs/s25main/GameManager.cpp @@ -9,6 +9,7 @@ #include "RttrConfig.h" #include "Settings.h" #include "WindowManager.h" +#include "desktops/dskCampaignVictory.h" #include "desktops/dskLobby.h" #include "desktops/dskMainMenu.h" #include "desktops/dskSplash.h" @@ -183,6 +184,10 @@ bool GameManager::ShowMenu() if(LOBBYCLIENT.IsLoggedIn()) // Lobby zeigen windowManager_.Switch(std::make_unique()); + else if(GAMECLIENT.IsCampaignCompleted()) + WINDOWMANAGER.Switch(std::make_unique(0)); + else if(const auto chapter = GAMECLIENT.GetCampaignChapterCompleted()) + WINDOWMANAGER.Switch(std::make_unique(chapter)); else // Hauptmenü zeigen windowManager_.Switch(std::make_unique()); diff --git a/libs/s25main/Settings.cpp b/libs/s25main/Settings.cpp index 499af15c08..76c4ec3c78 100644 --- a/libs/s25main/Settings.cpp +++ b/libs/s25main/Settings.cpp @@ -21,8 +21,8 @@ #include const int Settings::VERSION = 13; -const std::array Settings::SECTION_NAMES = { - {"global", "video", "language", "driver", "sound", "lobby", "server", "proxy", "interface", "addons"}}; +const std::array Settings::SECTION_NAMES = { + {"global", "video", "language", "driver", "sound", "lobby", "server", "proxy", "interface", "addons", "campaigns"}}; const std::array Settings::SCREEN_REFRESH_RATES = { {-1, 25, 30, 50, 60, 75, 80, 100, 120, 150, 180, 200, 240}}; @@ -149,6 +149,11 @@ void Settings::LoadDefaults() addons.configuration.clear(); // } + // campaigns + // { + campaigns.saveData.clear(); + // } + LoadIngameDefaults(); } @@ -197,10 +202,12 @@ void Settings::Load() static_cast(settings.find("interface")); const libsiedler2::ArchivItem_Ini* iniAddons = static_cast(settings.find("addons")); + const libsiedler2::ArchivItem_Ini* iniCampaigns = + static_cast(settings.find("campaigns")); // ist eine der Kategorien nicht vorhanden? if(!iniGlobal || !iniVideo || !iniLanguage || !iniDriver || !iniSound || !iniLobby || !iniServer || !iniProxy - || !iniInterface || !iniAddons) + || !iniInterface || !iniAddons || !iniCampaigns) { throw std::runtime_error("Missing section"); } @@ -315,6 +322,15 @@ void Settings::Load() s25util::fromStringClassic(item->getText()))); } + // campaigns + // { + for(unsigned campaign = 0; campaign < iniCampaigns->size(); ++campaign) + { + if(const auto* item = dynamic_cast(iniCampaigns->get(campaign))) + campaigns.saveData.emplace(item->getName(), item->getText()); + } + // } + LoadIngame(); // } } catch(std::runtime_error& e) @@ -390,10 +406,11 @@ void Settings::Save() libsiedler2::ArchivItem_Ini* iniProxy = static_cast(settings.find("proxy")); libsiedler2::ArchivItem_Ini* iniInterface = static_cast(settings.find("interface")); libsiedler2::ArchivItem_Ini* iniAddons = static_cast(settings.find("addons")); + libsiedler2::ArchivItem_Ini* iniCampaigns = static_cast(settings.find("campaigns")); // ist eine der Kategorien nicht vorhanden? RTTR_Assert(iniGlobal && iniVideo && iniLanguage && iniDriver && iniSound && iniLobby && iniServer && iniProxy - && iniInterface && iniAddons); + && iniInterface && iniAddons && iniCampaigns); // global // { @@ -475,6 +492,13 @@ void Settings::Save() iniAddons->setValue(s25util::toStringClassic(it.first), s25util::toStringClassic(it.second)); // } + // campaigns + // { + iniCampaigns->clear(); + for(const auto& it : campaigns.saveData) + iniCampaigns->setValue(it.first, it.second); + // } + bfs::path settingsPath = RTTRCONFIG.ExpandPath(s25::resources::config); if(libsiedler2::Write(settingsPath, settings) == 0) bfs::permissions(settingsPath, bfs::owner_read | bfs::owner_write); diff --git a/libs/s25main/Settings.h b/libs/s25main/Settings.h index 2674fb5871..a8a02a51cf 100644 --- a/libs/s25main/Settings.h +++ b/libs/s25main/Settings.h @@ -129,11 +129,16 @@ class Settings : public Singleton std::map configuration; } addons; + struct + { + std::map saveData; + } campaigns; + static const std::array SCREEN_REFRESH_RATES; private: static const int VERSION; - static const std::array SECTION_NAMES; + static const std::array SECTION_NAMES; }; #define SETTINGS Settings::inst() diff --git a/libs/s25main/desktops/dskCampaignMissionMapSelection.cpp b/libs/s25main/desktops/dskCampaignMissionMapSelection.cpp index 2cd7b7312e..247c155f5c 100644 --- a/libs/s25main/desktops/dskCampaignMissionMapSelection.cpp +++ b/libs/s25main/desktops/dskCampaignMissionMapSelection.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "dskCampaignMissionMapSelection.h" +#include "CampaignSaveData.h" #include "Loader.h" #include "WindowManager.h" #include "controls/ctrlMapSelection.h" @@ -45,9 +46,8 @@ dskCampaignMissionMapSelection::dskCampaignMissionMapSelection(CreateServerInfo if(settings_->selectionMapData.has_value()) { - auto* mapSelection = - AddMapSelection(ID_MapSelection, DrawPoint(0, 0), Extent(800, 508), settings_->selectionMapData.value()); - mapSelection->setMissionsStatus(std::vector(settings_->getNumMaps(), {true, true})); + AddMapSelection(ID_MapSelection, DrawPoint(0, 0), Extent(800, 508), settings_->selectionMapData.value()) + ->setMissionsStatus(getMissionsStatus(*settings_)); } } diff --git a/libs/s25main/desktops/dskCampaignMissionSelection.cpp b/libs/s25main/desktops/dskCampaignMissionSelection.cpp index a3f2e24bbb..873e573b56 100644 --- a/libs/s25main/desktops/dskCampaignMissionSelection.cpp +++ b/libs/s25main/desktops/dskCampaignMissionSelection.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "dskCampaignMissionSelection.h" +#include "CampaignSaveData.h" #include "Loader.h" #include "WindowManager.h" #include "commonDefines.h" @@ -114,8 +115,9 @@ void dskCampaignMissionSelection::UpdateMissionPage() const libsiedler2::ArchivItem_Map_Header& header = checkedCast(map[0])->getHeader(); - group->AddTextButton(i, curBtPos, catBtSize, TextureColor::Grey, s25util::ansiToUTF8(header.getName()), - NormalFont); + group + ->AddTextButton(i, curBtPos, catBtSize, TextureColor::Grey, s25util::ansiToUTF8(header.getName()), NormalFont) + ->SetEnabled(isChapterEnabled(*settings_, i)); curBtPos.y += catBtSize.y + distanceBetweenMissionButtonsY; } diff --git a/libs/s25main/desktops/dskCampaignSelection.cpp b/libs/s25main/desktops/dskCampaignSelection.cpp index dbb89c7e42..35b943ee53 100644 --- a/libs/s25main/desktops/dskCampaignSelection.cpp +++ b/libs/s25main/desktops/dskCampaignSelection.cpp @@ -87,7 +87,8 @@ dskCampaignSelection::dskCampaignSelection(CreateServerInfo csi) Extent(secondColumnExtentX, mutlilineExtentY), TextureColor::Grey, NormalFont); AddTextButton(ID_Back, DrawPoint(380, 560), Extent(200, 22), TextureColor::Red1, _("Back"), NormalFont); - AddTextButton(ID_Next, DrawPoint(590, 560), Extent(200, 22), TextureColor::Green2, _("Continue"), NormalFont); + AddTextButton(ID_Next, DrawPoint(590, 560), Extent(200, 22), TextureColor::Green2, _("Continue"), NormalFont) + ->SetEnabled(false); showCampaignInfo(false); @@ -107,7 +108,6 @@ void dskCampaignSelection::Msg_TableSelectItem(const unsigned ctrl_id, const boo return; ctrlButton& btContinue = *GetCtrl(ID_Next); - btContinue.SetEnabled(false); showCampaignInfo(false); campaignImage_ = nullptr; diff --git a/libs/s25main/desktops/dskCampaignVictory.cpp b/libs/s25main/desktops/dskCampaignVictory.cpp new file mode 100644 index 0000000000..052f14ef62 --- /dev/null +++ b/libs/s25main/desktops/dskCampaignVictory.cpp @@ -0,0 +1,39 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "dskCampaignVictory.h" +#include "Loader.h" +#include "WindowManager.h" +#include "desktops/dskMainMenu.h" +#include "network/GameClient.h" + +dskCampaignVictory::dskCampaignVictory(unsigned char chapter) + : Desktop(LOADER.GetImageN(ResourceId{chapter ? "setup896" : "setup895"}, 0)) +{ + GAMECLIENT.SetCampaignChapterCompleted(0); + GAMECLIENT.SetCampaignCompleted(false); + + if(!chapter) + return; + + AddText(10, DrawPoint{800 / 2, 600 - 50}, + _("You have successfully completed chapter") + std::string{" "} + std::to_string(chapter) + ".", + COLOR_YELLOW, FontStyle::CENTER, LargeFont); +} + +bool dskCampaignVictory::Msg_LeftDown(const MouseCoords&) +{ + return ShowMenu(); +} + +bool dskCampaignVictory::Msg_KeyDown(const KeyEvent&) +{ + return ShowMenu(); +} + +bool dskCampaignVictory::ShowMenu() +{ + WINDOWMANAGER.Switch(std::make_unique()); + return true; +} diff --git a/libs/s25main/desktops/dskCampaignVictory.h b/libs/s25main/desktops/dskCampaignVictory.h new file mode 100644 index 0000000000..5520422744 --- /dev/null +++ b/libs/s25main/desktops/dskCampaignVictory.h @@ -0,0 +1,21 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Desktop.h" + +class MouseCoords; + +class dskCampaignVictory : public Desktop +{ +public: + dskCampaignVictory(unsigned char chapter); + +private: + bool Msg_LeftDown(const MouseCoords&) override; + bool Msg_KeyDown(const KeyEvent&) override; + + bool ShowMenu(); +}; diff --git a/libs/s25main/lua/LuaInterfaceGame.cpp b/libs/s25main/lua/LuaInterfaceGame.cpp index 6845a89705..7456a06b4b 100644 --- a/libs/s25main/lua/LuaInterfaceGame.cpp +++ b/libs/s25main/lua/LuaInterfaceGame.cpp @@ -5,6 +5,7 @@ #include "LuaInterfaceGame.h" #include "EventManager.h" #include "Game.h" +#include "Settings.h" #include "WindowManager.h" #include "ai/AIInterface.h" #include "ai/AIPlayer.h" @@ -12,9 +13,11 @@ #include "lua/LuaHelpers.h" #include "lua/LuaPlayer.h" #include "lua/LuaWorld.h" +#include "network/GameClient.h" #include "postSystem/PostMsg.h" #include "world/GameWorld.h" #include "gameTypes/Resource.h" +#include "gameData/CampaignSaveCodes.h" #include "s25util/Serializer.h" #include "s25util/strAlgos.h" @@ -188,23 +191,26 @@ KAGUYA_MEMBER_FUNCTION_OVERLOADS(SetMissionGoalWrapper, LuaInterfaceGame, SetMis void LuaInterfaceGame::Register(kaguya::State& state) { - state["RTTRGame"].setClass(kaguya::UserdataMetatable() - .addFunction("ClearResources", &LuaInterfaceGame::ClearResources) - .addFunction("GetGF", &LuaInterfaceGame::GetGF) - .addFunction("FormatNumGFs", &LuaInterfaceGame::FormatNumGFs) - .addFunction("GetGameFrame", &LuaInterfaceGame::GetGF) - .addFunction("GetNumPlayers", &LuaInterfaceGame::GetNumPlayers) - .addFunction("Chat", &LuaInterfaceGame::Chat) - .addOverloadedFunctions("MissionStatement", &LuaInterfaceGame::MissionStatement, - &LuaInterfaceGame::MissionStatement2, - &LuaInterfaceGame::MissionStatement3) - .addFunction("SetMissionGoal", SetMissionGoalWrapper()) - .addFunction("PostMessage", &LuaInterfaceGame::PostMessageLua) - .addFunction("PostMessageWithLocation", &LuaInterfaceGame::PostMessageWithLocation) - .addFunction("GetPlayer", &LuaInterfaceGame::GetPlayer) - .addFunction("GetWorld", &LuaInterfaceGame::GetWorld) - // Old name - .addFunction("GetPlayerCount", &LuaInterfaceGame::GetNumPlayers)); + state["RTTRGame"].setClass( + kaguya::UserdataMetatable() + .addFunction("ClearResources", &LuaInterfaceGame::ClearResources) + .addFunction("GetGF", &LuaInterfaceGame::GetGF) + .addFunction("FormatNumGFs", &LuaInterfaceGame::FormatNumGFs) + .addFunction("GetGameFrame", &LuaInterfaceGame::GetGF) + .addFunction("GetNumPlayers", &LuaInterfaceGame::GetNumPlayers) + .addFunction("Chat", &LuaInterfaceGame::Chat) + .addOverloadedFunctions("MissionStatement", &LuaInterfaceGame::MissionStatement, + &LuaInterfaceGame::MissionStatement2, &LuaInterfaceGame::MissionStatement3) + .addFunction("SetMissionGoal", SetMissionGoalWrapper()) + .addFunction("PostMessage", &LuaInterfaceGame::PostMessageLua) + .addFunction("PostMessageWithLocation", &LuaInterfaceGame::PostMessageWithLocation) + .addFunction("EnableCampaignChapter", &LuaInterfaceGame::EnableCampaignChapter) + .addFunction("SetCampaignChapterCompleted", &LuaInterfaceGame::SetCampaignChapterCompleted) + .addFunction("SetCampaignCompleted", &LuaInterfaceGame::SetCampaignCompleted) + .addFunction("GetPlayer", &LuaInterfaceGame::GetPlayer) + .addFunction("GetWorld", &LuaInterfaceGame::GetWorld) + // Old name + .addFunction("GetPlayerCount", &LuaInterfaceGame::GetNumPlayers)); state["RTTR_Serializer"].setClass(kaguya::UserdataMetatable() .addFunction("PushInt", &Serializer::PushSignedInt) .addFunction("PopInt", &Serializer::PopSignedInt) @@ -313,6 +319,37 @@ void LuaInterfaceGame::PostMessageWithLocation(int playerIdx, const std::string& gw.MakeMapPoint(Position(x, y)))); } +namespace { +void MarkCampaignChapter(const std::string& campaignUid, unsigned char chapter, char code) +{ + if(chapter < 1) + return; + + auto& saveData = SETTINGS.campaigns.saveData[campaignUid]; + + if(saveData.length() < chapter) + saveData.resize(chapter); + + saveData[chapter - 1] = code; +} +} // namespace + +void LuaInterfaceGame::EnableCampaignChapter(const std::string& campaignUid, unsigned char chapter) +{ + MarkCampaignChapter(campaignUid, chapter, CampaignSaveCodes::chapterEnabled); +} + +void LuaInterfaceGame::SetCampaignChapterCompleted(const std::string& campaignUid, unsigned char chapter) +{ + MarkCampaignChapter(campaignUid, chapter, CampaignSaveCodes::chapterCompleted); + GAMECLIENT.SetCampaignChapterCompleted(chapter); +} + +void LuaInterfaceGame::SetCampaignCompleted(const std::string& /* campaignUid */) +{ + GAMECLIENT.SetCampaignCompleted(true); +} + LuaPlayer LuaInterfaceGame::GetPlayer(int playerIdx) { lua::assertTrue(playerIdx >= 0 && static_cast(playerIdx) < gw.GetNumPlayers(), "Invalid player idx"); @@ -363,6 +400,13 @@ void LuaInterfaceGame::EventStart(bool isFirstStart) onStart.call(isFirstStart); } +void LuaInterfaceGame::EventHumanWinner() +{ + kaguya::LuaRef onStart = lua["onHumanWinner"]; + if(onStart.type() == LUA_TFUNCTION) + onStart.call(); +} + void LuaInterfaceGame::EventGameFrame(unsigned nr) { kaguya::LuaRef onGameFrame = lua["onGameFrame"]; diff --git a/libs/s25main/lua/LuaInterfaceGame.h b/libs/s25main/lua/LuaInterfaceGame.h index e71758adcb..1fba80d6e1 100644 --- a/libs/s25main/lua/LuaInterfaceGame.h +++ b/libs/s25main/lua/LuaInterfaceGame.h @@ -33,6 +33,7 @@ class LuaInterfaceGame : public LuaInterfaceGameBase void EventOccupied(unsigned player, MapPoint pt); void EventAttack(unsigned char attackerPlayerId, unsigned char defenderPlayerId, unsigned attackerCount); void EventStart(bool isFirstStart); + void EventHumanWinner(); void EventGameFrame(unsigned nr); void EventResourceFound(unsigned char player, MapPoint pt, ResourceType type, unsigned char quantity); // Called if player wants to cancel a pact @@ -45,6 +46,7 @@ class LuaInterfaceGame : public LuaInterfaceGameBase // called if pact was created void EventPactCreated(PactType pt, unsigned char suggestedByPlayerId, unsigned char targetPlayerId, unsigned duration); + // Callable from Lua void ClearResources(); unsigned GetGF() const; @@ -59,6 +61,10 @@ class LuaInterfaceGame : public LuaInterfaceGameBase void PostMessageLua(int playerIdx, const std::string& msg); void PostMessageWithLocation(int playerIdx, const std::string& msg, int x, int y); + void EnableCampaignChapter(const std::string& campaignUid, unsigned char chapter); + void SetCampaignChapterCompleted(const std::string& campaignUid, unsigned char chapter); + void SetCampaignCompleted(const std::string& campaignUid); + private: ILocalGameState& localGameState; GameWorld& gw; diff --git a/libs/s25main/network/GameClient.h b/libs/s25main/network/GameClient.h index 9d921da996..5aa5617b2f 100644 --- a/libs/s25main/network/GameClient.h +++ b/libs/s25main/network/GameClient.h @@ -102,6 +102,11 @@ class GameClient final : /// Beendet das Spiel, zerstört die Spielstrukturen void ExitGame(); + void SetCampaignChapterCompleted(unsigned char chapter) { chapterCompleted = chapter; } + void SetCampaignCompleted(bool state) { campaignCompleted = state; } + unsigned char GetCampaignChapterCompleted() const { return chapterCompleted; } + bool IsCampaignCompleted() const { return campaignCompleted; } + ClientState GetState() const { return state; } Replay* GetReplay(); std::shared_ptr GetNWFInfo() const; @@ -308,6 +313,9 @@ class GameClient final : /// GameCommands, die vom Client noch an den Server gesendet werden müssen std::vector gameCommands_; + unsigned char chapterCompleted = 0; + bool campaignCompleted = false; + std::unique_ptr replayinfo; bool replayMode; diff --git a/tests/s25Main/campaign/testCampaignLuaFile.cpp b/tests/s25Main/campaign/testCampaignLuaFile.cpp index 072b80638d..ff5fb0d59f 100644 --- a/tests/s25Main/campaign/testCampaignLuaFile.cpp +++ b/tests/s25Main/campaign/testCampaignLuaFile.cpp @@ -29,6 +29,7 @@ BOOST_AUTO_TEST_CASE(ScriptVersion) file << "campaign ={\ version = \"1\",\ + uid = \"roman\",\ author = \"Max Meier\",\ name = \"Meine Kampagne\",\ shortDescription = \"Sehr kurze Beschreibung\",\ @@ -114,6 +115,7 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionWithoutTranslation) file << "campaign ={\ version = \"1\",\ + uid = \"roman\",\ author = \"Max Meier\",\ name = \"Meine Kampagne\",\ shortDescription = \"Sehr kurze Beschreibung\",\ @@ -123,7 +125,8 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionWithoutTranslation) difficulty = \"easy\",\ mapFolder = \"/DATA/MAPS\",\ luaFolder = \"/CAMPAIGNS/ROMAN\",\ - maps = { \"dessert0.WLD\", \"dessert1.WLD\", \"dessert2.WLD\"}\ + maps = { \"dessert0.WLD\", \"dessert1.WLD\", \"dessert2.WLD\"},\ + defaultChaptersEnabled = \"100000000\"\ }"; file << "function getRequiredLuaVersion() return 1 end"; @@ -135,6 +138,7 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionWithoutTranslation) // campaign description BOOST_TEST(desc.version == "1"); + BOOST_TEST(desc.uid == "roman"); BOOST_TEST(desc.author == "Max Meier"); BOOST_TEST(desc.name == "Meine Kampagne"); BOOST_TEST(desc.shortDescription == "Sehr kurze Beschreibung"); @@ -142,6 +146,7 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionWithoutTranslation) BOOST_TEST(desc.image == "/GFX/PICS/WORLD.LBM"); BOOST_TEST(desc.maxHumanPlayers == 1u); BOOST_TEST(desc.difficulty == "easy"); + BOOST_TEST(desc.defaultChaptersEnabled == "100000000"); // maps BOOST_TEST(desc.getNumMaps() == 3u); @@ -181,6 +186,7 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionFailsDueToIncorrectDifficulty) file << "campaign ={\ version = \"1\",\ + uid = \"roman\",\ author = \"Max Meier\",\ name = \"Meine Kampagne\",\ shortDescription = \"Sehr kurze Beschreibung\",\ @@ -211,6 +217,7 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionFailsDueToMissingField) file << "campaign ={\ version = \"1\",\ + uid = \"roman\",\ author = \"Max Meier\",\ name = \"Meine Kampagne\",\ shortDescription = \"Sehr kurze Beschreibung\",\ @@ -257,6 +264,7 @@ BOOST_AUTO_TEST_CASE(CampaignDescriptionLoadWithTranslation) file << "campaign = {\ version = \"1\",\ + uid = \"roman\",\ author = \"Max Meier\",\ name = _\"name\",\ shortDescription = _\"shortDescription\",\ @@ -280,6 +288,7 @@ BOOST_AUTO_TEST_CASE(CampaignDescriptionLoadWithTranslation) // campaign description BOOST_TEST(desc.version == "1"); + BOOST_TEST(desc.uid == "roman"); BOOST_TEST(desc.author == "Max Meier"); BOOST_TEST(desc.name == "Meine Kampagne"); BOOST_TEST(desc.shortDescription == "Sehr kurze Beschreibung"); @@ -309,6 +318,7 @@ BOOST_AUTO_TEST_CASE(OptionalSelectionMapLoadTest) file << "campaign = {\ version = \"1\",\ + uid = \"roman\",\ author = \"Max Meier\",\ name = \"Meine Kampagne\",\ shortDescription = \"Sehr kurze Beschreibung\",\ @@ -344,6 +354,7 @@ BOOST_AUTO_TEST_CASE(OptionalSelectionMapLoadTest) // campaign description BOOST_TEST(desc.version == "1"); + BOOST_TEST(desc.uid == "roman"); BOOST_TEST(desc.author == "Max Meier"); BOOST_TEST(desc.name == "Meine Kampagne"); BOOST_TEST(desc.shortDescription == "Sehr kurze Beschreibung"); diff --git a/tests/s25Main/campaign/testCampaignSaveData.cpp b/tests/s25Main/campaign/testCampaignSaveData.cpp new file mode 100644 index 0000000000..2e2ca89c6c --- /dev/null +++ b/tests/s25Main/campaign/testCampaignSaveData.cpp @@ -0,0 +1,102 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "CampaignSaveData.h" +#include "Settings.h" +#include "lua/CampaignDataLoader.h" +#include "gameData/CampaignDescription.h" +#include "gameData/CampaignSaveCodes.h" +#include "rttr/test/TmpFolder.hpp" +#include +#include + +namespace { +constexpr auto uid = "uniqueID"; +struct CampaignSaveDataFixture +{ + CampaignSaveDataFixture() + { + desc.uid = uid; + saveData.clear(); + } + + CampaignDescription desc; + std::map& saveData = SETTINGS.campaigns.saveData; +}; +} // namespace + +BOOST_FIXTURE_TEST_CASE(isChapterEnabled_ReturnsFalse_WhenChapterIsDisabled, CampaignSaveDataFixture) +{ + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 0) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 1) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 2) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 9) == false); +} + +BOOST_FIXTURE_TEST_CASE(isChapterEnabled_ReturnsTrue_WhenChapterIsEnabled, CampaignSaveDataFixture) +{ + saveData[uid] = "011"; + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 0) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 1) == true); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 2) == true); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 9) == false); +} + +BOOST_FIXTURE_TEST_CASE(Chapters0and1AreEnabledByDefault, CampaignSaveDataFixture) +{ + saveData[uid] = CampaignSaveCodes::defaultChaptersEnabled; + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 0) == true); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 1) == true); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 2) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 9) == false); +} + +BOOST_FIXTURE_TEST_CASE(DefaultChaptersCanBeSetPerCampaign, CampaignSaveDataFixture) +{ + desc.defaultChaptersEnabled = "100"; + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 0) == true); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 1) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 2) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 9) == false); +} + +BOOST_FIXTURE_TEST_CASE( + getMissionsStatus_ReturnsPlayableForChaptersWhichAreEnabled_AndConqueredForChaptersWhichAreCompleted, + CampaignSaveDataFixture) +{ + rttr::test::TmpFolder tmp; + { + boost::nowide::ofstream file(tmp / "campaign.lua"); + + file << "campaign ={\ + version = \"1\",\ + uid = \"roman\",\ + author = \"Max Meier\",\ + name = \"Meine Kampagne\",\ + shortDescription = \"Sehr kurze Beschreibung\",\ + longDescription = \"Das ist die lange Beschreibung\",\ + image = \"/GFX/PICS/WORLD.LBM\",\ + maxHumanPlayers = 1,\ + difficulty = \"easy\",\ + mapFolder = \"/DATA/MAPS\",\ + luaFolder = \"/CAMPAIGNS/ROMAN\",\ + maps = { \"dessert0.WLD\", \"dessert1.WLD\", \"dessert2.WLD\"}\ + }"; + + file << "function getRequiredLuaVersion() return 1 end"; + } + + CampaignDescription dsc; + CampaignDataLoader loader{dsc, tmp}; + BOOST_TEST_REQUIRE(loader.Load()); + + saveData["roman"] = "210"; + const auto& ms = getMissionsStatus(dsc); + BOOST_TEST_REQUIRE(ms[0].playable == true); + BOOST_TEST_REQUIRE(ms[0].conquered == true); + BOOST_TEST_REQUIRE(ms[1].playable == true); + BOOST_TEST_REQUIRE(ms[1].conquered == false); + BOOST_TEST_REQUIRE(ms[2].playable == false); + BOOST_TEST_REQUIRE(ms[2].conquered == false); +} diff --git a/tests/s25Main/lua/testLua.cpp b/tests/s25Main/lua/testLua.cpp index 34fe0d42fb..8693f92c10 100644 --- a/tests/s25Main/lua/testLua.cpp +++ b/tests/s25Main/lua/testLua.cpp @@ -6,6 +6,7 @@ #include "Loader.h" #include "PointOutput.h" #include "RttrForeachPt.h" +#include "Settings.h" #include "buildings/noBuildingSite.h" #include "buildings/nobHQ.h" #include "enum_cast.hpp" @@ -843,6 +844,20 @@ BOOST_AUTO_TEST_CASE(onExplored) } } +BOOST_AUTO_TEST_CASE(CampaignStatusCanBeChangedFromLua) +{ + initWorld(); + + SETTINGS.campaigns.saveData["campaign_id"] = "110"; + executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 2)"); + executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 4)"); + executeLua("rttr:EnableCampaignChapter('campaign_id', 5)"); + executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 0)"); // noop + executeLua("rttr:EnableCampaignChapter('campaign_id', 0)"); // noop + game.executeAICommands(); + BOOST_TEST_REQUIRE(SETTINGS.campaigns.saveData["campaign_id"] == "12021"); +} + BOOST_AUTO_TEST_CASE(LuaPacts) { initWorld(); From 2acb96115041b8e25460930b69ea545e378a1e64 Mon Sep 17 00:00:00 2001 From: Jakub Audykowicz Date: Sun, 28 Jul 2024 21:22:10 +0200 Subject: [PATCH 2/6] Do not enable a campaign chapter that is already completed --- libs/s25main/lua/LuaInterfaceGame.cpp | 7 ++++++- tests/s25Main/lua/testLua.cpp | 5 +++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/libs/s25main/lua/LuaInterfaceGame.cpp b/libs/s25main/lua/LuaInterfaceGame.cpp index 7456a06b4b..01512de3d7 100644 --- a/libs/s25main/lua/LuaInterfaceGame.cpp +++ b/libs/s25main/lua/LuaInterfaceGame.cpp @@ -330,7 +330,12 @@ void MarkCampaignChapter(const std::string& campaignUid, unsigned char chapter, if(saveData.length() < chapter) saveData.resize(chapter); - saveData[chapter - 1] = code; + auto& chapterSaveData = saveData[chapter - 1]; + + if(chapterSaveData == CampaignSaveCodes::chapterCompleted) + return; + + chapterSaveData = code; } } // namespace diff --git a/tests/s25Main/lua/testLua.cpp b/tests/s25Main/lua/testLua.cpp index 8693f92c10..0c5e452f83 100644 --- a/tests/s25Main/lua/testLua.cpp +++ b/tests/s25Main/lua/testLua.cpp @@ -852,8 +852,9 @@ BOOST_AUTO_TEST_CASE(CampaignStatusCanBeChangedFromLua) executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 2)"); executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 4)"); executeLua("rttr:EnableCampaignChapter('campaign_id', 5)"); - executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 0)"); // noop - executeLua("rttr:EnableCampaignChapter('campaign_id', 0)"); // noop + executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 0)"); // noop - chapters start from 1 + executeLua("rttr:EnableCampaignChapter('campaign_id', 0)"); // noop - chapters start from 1 + executeLua("rttr:EnableCampaignChapter('campaign_id', 2)"); // noop - already completed game.executeAICommands(); BOOST_TEST_REQUIRE(SETTINGS.campaigns.saveData["campaign_id"] == "12021"); } From 679851ad086bf34501e3c697a46b5816ce74fe9e Mon Sep 17 00:00:00 2001 From: Jakub Audykowicz Date: Sun, 22 Dec 2024 14:32:40 +0100 Subject: [PATCH 3/6] Add support for campaign status --- data/RTTR/campaigns/roman/MISS200.lua | 4 +- data/RTTR/campaigns/roman/MISS201.lua | 4 +- data/RTTR/campaigns/roman/MISS202.lua | 4 +- data/RTTR/campaigns/roman/MISS203.lua | 4 +- data/RTTR/campaigns/roman/MISS204.lua | 4 +- data/RTTR/campaigns/roman/MISS205.lua | 4 +- data/RTTR/campaigns/roman/MISS206.lua | 4 +- data/RTTR/campaigns/roman/MISS207.lua | 4 +- data/RTTR/campaigns/roman/MISS208.lua | 4 +- data/RTTR/campaigns/roman/MISS209.lua | 4 +- data/RTTR/campaigns/roman/campaign.lua | 1 + data/RTTR/campaigns/world/AFRICA.lua | 6 ++ data/RTTR/campaigns/world/AUSTRA.lua | 7 ++ data/RTTR/campaigns/world/EUROPE.lua | 8 ++ data/RTTR/campaigns/world/GREEN.lua | 6 ++ data/RTTR/campaigns/world/JAPAN.lua | 7 ++ data/RTTR/campaigns/world/NAMERICA.lua | 7 ++ data/RTTR/campaigns/world/NASIA.lua | 7 ++ data/RTTR/campaigns/world/SAMERICA.lua | 6 ++ data/RTTR/campaigns/world/SASIA.lua | 8 ++ data/RTTR/campaigns/world/campaign.lua | 22 ++-- doc/lua/events.md | 3 + .../gameData/CampaignDescription.cpp | 4 + .../gameData/CampaignDescription.h | 2 + libs/libGamedata/gameData/CampaignSaveCodes.h | 12 +++ libs/s25main/CampaignSaveData.cpp | 45 ++++++++ libs/s25main/CampaignSaveData.h | 12 +++ libs/s25main/Game.cpp | 15 +++ libs/s25main/Game.h | 1 + libs/s25main/GameManager.cpp | 5 + libs/s25main/Settings.cpp | 32 +++++- libs/s25main/Settings.h | 7 +- .../desktops/dskCampaignMissionSelection.cpp | 9 +- .../s25main/desktops/dskCampaignSelection.cpp | 3 +- libs/s25main/desktops/dskCampaignVictory.cpp | 39 +++++++ libs/s25main/desktops/dskCampaignVictory.h | 21 ++++ libs/s25main/lua/LuaInterfaceGame.cpp | 83 +++++++++++--- libs/s25main/lua/LuaInterfaceGame.h | 6 ++ libs/s25main/network/GameClient.h | 8 ++ tests/libGameData/testCampaignLuaFile.cpp | 15 ++- .../integration/testCampaignSaveData.cpp | 102 ++++++++++++++++++ tests/s25Main/lua/testLua.cpp | 16 +++ 42 files changed, 507 insertions(+), 58 deletions(-) create mode 100644 libs/libGamedata/gameData/CampaignSaveCodes.h create mode 100644 libs/s25main/CampaignSaveData.cpp create mode 100644 libs/s25main/CampaignSaveData.h create mode 100644 libs/s25main/desktops/dskCampaignVictory.cpp create mode 100644 libs/s25main/desktops/dskCampaignVictory.h create mode 100644 tests/s25Main/integration/testCampaignSaveData.cpp diff --git a/data/RTTR/campaigns/roman/MISS200.lua b/data/RTTR/campaigns/roman/MISS200.lua index 64ea3b4cb5..bdfea827ee 100644 --- a/data/RTTR/campaigns/roman/MISS200.lua +++ b/data/RTTR/campaigns/roman/MISS200.lua @@ -6,9 +6,7 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits --- Set AI Agression Level ------------------------------------------------------------------------------- @@ -508,9 +506,9 @@ function MissionEvent(e, onLoad) rttr:GetPlayer(0):EnableBuilding(BLD_BAKERY, not onLoad) elseif(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(14, 8, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 1) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS201.lua b/data/RTTR/campaigns/roman/MISS201.lua index 2f75fe0623..f8dde050a7 100644 --- a/data/RTTR/campaigns/roman/MISS201.lua +++ b/data/RTTR/campaigns/roman/MISS201.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level -- RttR: AI doesn't go south @@ -514,9 +513,10 @@ function MissionEvent(e, onLoad) rttr:GetPlayer(1):DisableBuilding(BLD_CATAPULT, false) elseif(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(48, 9, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 2) + rttr:EnableCampaignChapter("roman", 3) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS202.lua b/data/RTTR/campaigns/roman/MISS202.lua index 01a8a19c24..def471c7c2 100644 --- a/data/RTTR/campaigns/roman/MISS202.lua +++ b/data/RTTR/campaigns/roman/MISS202.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -496,9 +495,10 @@ function MissionEvent(e, onLoad) rttr:GetPlayer(0):EnableBuilding(BLD_LOOKOUTTOWER, not onLoad) elseif(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(89, 20, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 3) + rttr:EnableCampaignChapter("roman", 4) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS203.lua b/data/RTTR/campaigns/roman/MISS203.lua index 727ac32db5..b34159d768 100644 --- a/data/RTTR/campaigns/roman/MISS203.lua +++ b/data/RTTR/campaigns/roman/MISS203.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -477,9 +476,10 @@ function MissionEvent(e, onLoad) rttr:GetPlayer(0):EnableBuilding(BLD_SHIPYARD, not onLoad) elseif(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(97, 68, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 4) + rttr:EnableCampaignChapter("roman", 5) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS204.lua b/data/RTTR/campaigns/roman/MISS204.lua index 1556ba7362..2d74fc06c1 100644 --- a/data/RTTR/campaigns/roman/MISS204.lua +++ b/data/RTTR/campaigns/roman/MISS204.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -433,9 +432,10 @@ function MissionEvent(e, onLoad) -- call side effects for active events, check "eState[e] == 1" for multiple call events! if(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(19, 37, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 5) + rttr:EnableCampaignChapter("roman", 6) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS205.lua b/data/RTTR/campaigns/roman/MISS205.lua index a6ec52edf1..4ced4c3071 100644 --- a/data/RTTR/campaigns/roman/MISS205.lua +++ b/data/RTTR/campaigns/roman/MISS205.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -491,9 +490,10 @@ function MissionEvent(e, onLoad) -- call side effects for active events, check "eState[e] == 1" for multiple call events! if(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(148, 50, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 6) + rttr:EnableCampaignChapter("roman", 7) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS206.lua b/data/RTTR/campaigns/roman/MISS206.lua index 7e878824db..dfae05cbf0 100644 --- a/data/RTTR/campaigns/roman/MISS206.lua +++ b/data/RTTR/campaigns/roman/MISS206.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -451,9 +450,10 @@ function MissionEvent(e, onLoad) -- call side effects for active events, check "eState[e] == 1" for multiple call events! if(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(13, 66, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 7) + rttr:EnableCampaignChapter("roman", 8) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS207.lua b/data/RTTR/campaigns/roman/MISS207.lua index 24f66c450d..3fd28a5345 100644 --- a/data/RTTR/campaigns/roman/MISS207.lua +++ b/data/RTTR/campaigns/roman/MISS207.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -422,9 +421,10 @@ function MissionEvent(e, onLoad) -- call side effects for active events, check "eState[e] == 1" for multiple call events! if(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(11, 125, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 8) + rttr:EnableCampaignChapter("roman", 9) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS208.lua b/data/RTTR/campaigns/roman/MISS208.lua index 269f256d4f..45f0ba9887 100644 --- a/data/RTTR/campaigns/roman/MISS208.lua +++ b/data/RTTR/campaigns/roman/MISS208.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -419,9 +418,10 @@ function MissionEvent(e, onLoad) -- call side effects for active events, check "eState[e] == 1" for multiple call events! if(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc - Done rttr:GetWorld():AddStaticObject(127, 48, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 9) + rttr:EnableCampaignChapter("roman", 10) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS209.lua b/data/RTTR/campaigns/roman/MISS209.lua index f9cd271574..cc4a2c6f13 100644 --- a/data/RTTR/campaigns/roman/MISS209.lua +++ b/data/RTTR/campaigns/roman/MISS209.lua @@ -6,7 +6,6 @@ -------------------------------- TODO ----------------------------------------- --- EnableNextMissions() -- Set Portraits -- Set AI Agression Level ------------------------------------------------------------------------------- @@ -440,9 +439,10 @@ function MissionEvent(e, onLoad) -- call side effects for active events, check "eState[e] == 1" for multiple call events! if(e == 99) then - -- TODO: EnableNextMissions() -- Show opened arc rttr:GetWorld():AddStaticObject(75, 40, 561, 0xFFFF, 2) + rttr:SetCampaignChapterCompleted("roman", 10) + rttr:SetCampaignCompleted("roman") end -- update event state diff --git a/data/RTTR/campaigns/roman/campaign.lua b/data/RTTR/campaigns/roman/campaign.lua index b797324a2b..42c2e16f7f 100644 --- a/data/RTTR/campaigns/roman/campaign.lua +++ b/data/RTTR/campaigns/roman/campaign.lua @@ -21,6 +21,7 @@ rttr:RegisterTranslations( campaign = { version = 1, + uid = "roman", author = "Bluebyte", name = _"name", shortDescription = _"shortDescription", diff --git a/data/RTTR/campaigns/world/AFRICA.lua b/data/RTTR/campaigns/world/AFRICA.lua index 7f6a487085..5fe46d762f 100644 --- a/data/RTTR/campaigns/world/AFRICA.lua +++ b/data/RTTR/campaigns/world/AFRICA.lua @@ -66,3 +66,9 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 2) + rttr:EnableCampaignChapter("world", 8) -- sasia +end diff --git a/data/RTTR/campaigns/world/AUSTRA.lua b/data/RTTR/campaigns/world/AUSTRA.lua index 6cfbbdf515..ad89c1c3ac 100644 --- a/data/RTTR/campaigns/world/AUSTRA.lua +++ b/data/RTTR/campaigns/world/AUSTRA.lua @@ -54,3 +54,10 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 6) + rttr:EnableCampaignChapter("world", 8) -- sasia + rttr:EnableCampaignChapter("world", 9) -- japan +end diff --git a/data/RTTR/campaigns/world/EUROPE.lua b/data/RTTR/campaigns/world/EUROPE.lua index d18f7ecb06..0393c8cf37 100644 --- a/data/RTTR/campaigns/world/EUROPE.lua +++ b/data/RTTR/campaigns/world/EUROPE.lua @@ -78,3 +78,11 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 1) + rttr:EnableCampaignChapter("world", 2) -- africa + rttr:EnableCampaignChapter("world", 5) -- green + rttr:EnableCampaignChapter("world", 7) -- nasia +end diff --git a/data/RTTR/campaigns/world/GREEN.lua b/data/RTTR/campaigns/world/GREEN.lua index ef14c6e500..ae2e3a793d 100644 --- a/data/RTTR/campaigns/world/GREEN.lua +++ b/data/RTTR/campaigns/world/GREEN.lua @@ -54,3 +54,9 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 5) + rttr:EnableCampaignChapter("world", 3) -- namerica +end diff --git a/data/RTTR/campaigns/world/JAPAN.lua b/data/RTTR/campaigns/world/JAPAN.lua index 7777bfabea..9e1fb70a83 100644 --- a/data/RTTR/campaigns/world/JAPAN.lua +++ b/data/RTTR/campaigns/world/JAPAN.lua @@ -54,3 +54,10 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 9) + rttr:EnableCampaignChapter("world", 6) -- austra + rttr:EnableCampaignChapter("world", 7) -- nasia +end diff --git a/data/RTTR/campaigns/world/NAMERICA.lua b/data/RTTR/campaigns/world/NAMERICA.lua index 52c8264fe6..643a2a855d 100644 --- a/data/RTTR/campaigns/world/NAMERICA.lua +++ b/data/RTTR/campaigns/world/NAMERICA.lua @@ -60,3 +60,10 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 3) + rttr:EnableCampaignChapter("world", 4) -- samerica + rttr:EnableCampaignChapter("world", 5) -- green +end diff --git a/data/RTTR/campaigns/world/NASIA.lua b/data/RTTR/campaigns/world/NASIA.lua index 29c88c73f7..5e94155bc3 100644 --- a/data/RTTR/campaigns/world/NASIA.lua +++ b/data/RTTR/campaigns/world/NASIA.lua @@ -66,3 +66,10 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 7) + rttr:EnableCampaignChapter("world", 8) -- sasia + rttr:EnableCampaignChapter("world", 9) -- japan +end diff --git a/data/RTTR/campaigns/world/SAMERICA.lua b/data/RTTR/campaigns/world/SAMERICA.lua index bc98c65f77..057381dd61 100644 --- a/data/RTTR/campaigns/world/SAMERICA.lua +++ b/data/RTTR/campaigns/world/SAMERICA.lua @@ -60,3 +60,9 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 4) + rttr:EnableCampaignChapter("world", 3) -- namerica +end diff --git a/data/RTTR/campaigns/world/SASIA.lua b/data/RTTR/campaigns/world/SASIA.lua index f45868ba3f..cb7cf10233 100644 --- a/data/RTTR/campaigns/world/SASIA.lua +++ b/data/RTTR/campaigns/world/SASIA.lua @@ -60,3 +60,11 @@ function getAllowedChanges() ["aiTeam"] = false } end + +-------------------------------- mission events ------------------------------- +function onHumanWinner() + rttr:SetCampaignChapterCompleted("world", 8) + rttr:EnableCampaignChapter("world", 2) -- africa + rttr:EnableCampaignChapter("world", 6) -- austra + rttr:EnableCampaignChapter("world", 7) -- nasia +end diff --git a/data/RTTR/campaigns/world/campaign.lua b/data/RTTR/campaigns/world/campaign.lua index aa9ceb7ddf..ce1af9d1c7 100644 --- a/data/RTTR/campaigns/world/campaign.lua +++ b/data/RTTR/campaigns/world/campaign.lua @@ -21,6 +21,7 @@ rttr:RegisterTranslations( campaign = { version = 1, + uid = "world", author = "Bluebyte", name = _"name", shortDescription = _"shortDescription", @@ -30,7 +31,8 @@ campaign = { difficulty = "easy", mapFolder = "/DATA/MAPS2", luaFolder = "/CAMPAIGNS/WORLD", - maps = { "EUROPE.WLD","NAMERICA.WLD","SAMERICA.WLD","GREEN.WLD","AFRICA.WLD","NASIA.WLD","SASIA.WLD","JAPAN.WLD","AUSTRA.WLD"}, + maps = {"EUROPE.WLD", "AFRICA.WLD", "NAMERICA.WLD", "SAMERICA.WLD", "GREEN.WLD", "AUSTRA.WLD", "NASIA.WLD", "SASIA.WLD", "JAPAN.WLD"}, + defaultChaptersEnabled = "100000000", selectionMap = { background = {"/GFX/PICS/SETUP990.LBM", 0}, map = {"/GFX/PICS/WORLD.LBM", 0}, @@ -40,15 +42,15 @@ campaign = { backgroundOffset = {64, 70}, disabledColor = 0x70000000, missionSelectionInfos = { - {0xffffff00, 243, 97}, - {0xffaf73cb, 55,78}, - {0xff008fc3, 122, 193}, - {0xff43c373, 166, 36}, - {0xff27871b, 241,176}, - {0xffc32323, 366,87}, - {0xff573327, 375,145}, - {0xffcfaf4b, 486, 136}, - {0xffbb6313, 441, 264} + {0xffffff00, 243, 97}, -- europe + {0xff27871b, 241,176}, -- africa + {0xffaf73cb, 55,78}, -- namerica + {0xff008fc3, 122, 193}, -- samerica + {0xff43c373, 166, 36}, -- green + {0xffbb6313, 441, 264}, -- austra + {0xffc32323, 366,87}, -- nasia + {0xff573327, 375,145}, -- sasia + {0xffcfaf4b, 486, 136} -- japan } } } diff --git a/doc/lua/events.md b/doc/lua/events.md index 61f842fa10..598827c5e4 100644 --- a/doc/lua/events.md +++ b/doc/lua/events.md @@ -107,3 +107,6 @@ Called when a pact has been canceled. **onPactCreated(PactType, suggestedByPlayerIdx, targetPlayerIdx, duration)** Called when a pact has been confirmed. + +**onHumanWinner()** +Called when the game is won by a human. diff --git a/libs/libGamedata/gameData/CampaignDescription.cpp b/libs/libGamedata/gameData/CampaignDescription.cpp index ae44b8821f..41194edc11 100644 --- a/libs/libGamedata/gameData/CampaignDescription.cpp +++ b/libs/libGamedata/gameData/CampaignDescription.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "CampaignDescription.h" +#include "CampaignSaveCodes.h" #include "RttrConfig.h" #include "helpers/format.hpp" #include "lua/CheckedLuaTable.h" @@ -12,6 +13,7 @@ CampaignDescription::CampaignDescription(const boost::filesystem::path& campaignPath, const kaguya::LuaRef& table) { CheckedLuaTable luaData(table); + luaData.getOrThrow(uid, "uid"); luaData.getOrThrow(version, "version"); luaData.getOrThrow(author, "author"); luaData.getOrThrow(name, "name"); @@ -45,6 +47,8 @@ CampaignDescription::CampaignDescription(const boost::filesystem::path& campaign // Default lua folder to map folder, i.e. LUA files are side by side with the maps luaFolder_ = resolveFolder(luaData.getOrDefault("luaFolder", mapFolder)); mapNames_ = luaData.getOrDefault("maps", std::vector()); + defaultChaptersEnabled = + luaData.getOrDefault("defaultChaptersEnabled", std::string{CampaignSaveCodes::defaultChaptersEnabled}); selectionMapData = luaData.getOptional("selectionMap"); luaData.checkUnused(); } diff --git a/libs/libGamedata/gameData/CampaignDescription.h b/libs/libGamedata/gameData/CampaignDescription.h index 7de41de861..a72c357639 100644 --- a/libs/libGamedata/gameData/CampaignDescription.h +++ b/libs/libGamedata/gameData/CampaignDescription.h @@ -15,6 +15,7 @@ class LuaRef; struct CampaignDescription { + std::string uid; std::string version; std::string author; std::string name; @@ -23,6 +24,7 @@ struct CampaignDescription std::optional image; unsigned maxHumanPlayers = 1; std::string difficulty; + std::string defaultChaptersEnabled; std::optional selectionMapData; CampaignDescription() = default; diff --git a/libs/libGamedata/gameData/CampaignSaveCodes.h b/libs/libGamedata/gameData/CampaignSaveCodes.h new file mode 100644 index 0000000000..e9f2dfb2e3 --- /dev/null +++ b/libs/libGamedata/gameData/CampaignSaveCodes.h @@ -0,0 +1,12 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace CampaignSaveCodes { +constexpr auto chapterDisabled = '0'; +constexpr auto chapterEnabled = '1'; +constexpr auto chapterCompleted = '2'; +constexpr auto defaultChaptersEnabled = "1100000000"; +} // namespace CampaignSaveCodes diff --git a/libs/s25main/CampaignSaveData.cpp b/libs/s25main/CampaignSaveData.cpp new file mode 100644 index 0000000000..0b4ec04fe7 --- /dev/null +++ b/libs/s25main/CampaignSaveData.cpp @@ -0,0 +1,45 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "CampaignSaveData.h" +#include "Settings.h" +#include "gameData/CampaignDescription.h" +#include "gameData/CampaignSaveCodes.h" + +namespace { +auto getCampaignSaveData(const CampaignDescription& campaignDesc) +{ + auto& saveData = SETTINGS.campaigns.saveData; + + if(!saveData.count(campaignDesc.uid)) + saveData[campaignDesc.uid] = campaignDesc.defaultChaptersEnabled; + + return saveData[campaignDesc.uid]; +} +} // namespace + +bool isChapterEnabled(const CampaignDescription& campaignDesc, unsigned char chapter) +{ + const auto& saveData = getCampaignSaveData(campaignDesc); + + if(chapter >= saveData.length()) + return false; + + return saveData[chapter] != CampaignSaveCodes::chapterDisabled; +} + +std::vector getMissionsStatus(const CampaignDescription& campaignDesc) +{ + const auto& saveData = getCampaignSaveData(campaignDesc); + + std::vector ret(saveData.length()); + for(auto i = 0u; i < saveData.length(); ++i) + { + ret[i].playable = saveData[i] != CampaignSaveCodes::chapterDisabled; + ret[i].conquered = saveData[i] == CampaignSaveCodes::chapterCompleted; + } + + ret.resize(campaignDesc.getNumMaps()); + return ret; +} diff --git a/libs/s25main/CampaignSaveData.h b/libs/s25main/CampaignSaveData.h new file mode 100644 index 0000000000..fa1fa3de03 --- /dev/null +++ b/libs/s25main/CampaignSaveData.h @@ -0,0 +1,12 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "controls/ctrlMapSelection.h" + +struct CampaignDescription; + +bool isChapterEnabled(const CampaignDescription& campaignDesc, unsigned char chapter); +std::vector getMissionsStatus(const CampaignDescription& campaignDesc); diff --git a/libs/s25main/Game.cpp b/libs/s25main/Game.cpp index 20e37c6bdb..da4cc9d779 100644 --- a/libs/s25main/Game.cpp +++ b/libs/s25main/Game.cpp @@ -178,9 +178,24 @@ void Game::CheckObjective() world_.GetGameInterface()->GI_TeamWinner(*bestTeam); else world_.GetGameInterface()->GI_Winner(bestPlayer); + + if(world_.HasLua() && IsWinnerHuman(bestTeam.value_or(0), bestPlayer)) + world_.GetLua().EventHumanWinner(); } } +bool Game::IsWinnerHuman(unsigned bestTeam, unsigned bestPlayer) const +{ + if(world_.GetPlayer(bestPlayer).isHuman()) + return true; + + for(auto i = 0u; i < world_.GetNumPlayers(); ++i) + if(world_.GetPlayer(bestTeam & (1 << i)).isHuman()) + return true; + + return false; +} + AIPlayer* Game::GetAIPlayer(unsigned id) { for(AIPlayer& ai : aiPlayers_) diff --git a/libs/s25main/Game.h b/libs/s25main/Game.h index 45b3c0c954..a114927906 100644 --- a/libs/s25main/Game.h +++ b/libs/s25main/Game.h @@ -38,6 +38,7 @@ class Game void StatisticStep(); /// Check if the objective was reached (if set) void CheckObjective(); + bool IsWinnerHuman(unsigned bestTeam, unsigned bestPlayer) const; bool started_, finished_; std::unique_ptr lua; diff --git a/libs/s25main/GameManager.cpp b/libs/s25main/GameManager.cpp index 4c33ee0552..be118ed48e 100644 --- a/libs/s25main/GameManager.cpp +++ b/libs/s25main/GameManager.cpp @@ -9,6 +9,7 @@ #include "RttrConfig.h" #include "Settings.h" #include "WindowManager.h" +#include "desktops/dskCampaignVictory.h" #include "desktops/dskLobby.h" #include "desktops/dskMainMenu.h" #include "desktops/dskSplash.h" @@ -183,6 +184,10 @@ bool GameManager::ShowMenu() if(LOBBYCLIENT.IsLoggedIn()) // Lobby zeigen windowManager_.Switch(std::make_unique()); + else if(GAMECLIENT.IsCampaignCompleted()) + WINDOWMANAGER.Switch(std::make_unique(0)); + else if(const auto chapter = GAMECLIENT.GetCampaignChapterCompleted()) + WINDOWMANAGER.Switch(std::make_unique(chapter)); else // Hauptmenü zeigen windowManager_.Switch(std::make_unique()); diff --git a/libs/s25main/Settings.cpp b/libs/s25main/Settings.cpp index 499af15c08..05ebeda528 100644 --- a/libs/s25main/Settings.cpp +++ b/libs/s25main/Settings.cpp @@ -21,8 +21,8 @@ #include const int Settings::VERSION = 13; -const std::array Settings::SECTION_NAMES = { - {"global", "video", "language", "driver", "sound", "lobby", "server", "proxy", "interface", "addons"}}; +const std::array Settings::SECTION_NAMES = { + {"global", "video", "language", "driver", "sound", "lobby", "server", "proxy", "interface", "addons", "campaigns"}}; const std::array Settings::SCREEN_REFRESH_RATES = { {-1, 25, 30, 50, 60, 75, 80, 100, 120, 150, 180, 200, 240}}; @@ -149,6 +149,11 @@ void Settings::LoadDefaults() addons.configuration.clear(); // } + // // campaigns + // { + campaigns.saveData.clear(); + // } + LoadIngameDefaults(); } @@ -197,10 +202,12 @@ void Settings::Load() static_cast(settings.find("interface")); const libsiedler2::ArchivItem_Ini* iniAddons = static_cast(settings.find("addons")); + const libsiedler2::ArchivItem_Ini* iniCampaigns = + static_cast(settings.find("campaigns")); // ist eine der Kategorien nicht vorhanden? if(!iniGlobal || !iniVideo || !iniLanguage || !iniDriver || !iniSound || !iniLobby || !iniServer || !iniProxy - || !iniInterface || !iniAddons) + || !iniInterface || !iniAddons || !iniCampaigns) { throw std::runtime_error("Missing section"); } @@ -315,6 +322,15 @@ void Settings::Load() s25util::fromStringClassic(item->getText()))); } + // campaigns + // { + for(unsigned campaign = 0; campaign < iniCampaigns->size(); ++campaign) + { + if(const auto* item = dynamic_cast(iniCampaigns->get(campaign))) + campaigns.saveData.emplace(item->getName(), item->getText()); + } + // } + LoadIngame(); // } } catch(std::runtime_error& e) @@ -390,10 +406,11 @@ void Settings::Save() libsiedler2::ArchivItem_Ini* iniProxy = static_cast(settings.find("proxy")); libsiedler2::ArchivItem_Ini* iniInterface = static_cast(settings.find("interface")); libsiedler2::ArchivItem_Ini* iniAddons = static_cast(settings.find("addons")); + libsiedler2::ArchivItem_Ini* iniCampaigns = static_cast(settings.find("campaigns")); // ist eine der Kategorien nicht vorhanden? RTTR_Assert(iniGlobal && iniVideo && iniLanguage && iniDriver && iniSound && iniLobby && iniServer && iniProxy - && iniInterface && iniAddons); + && iniInterface && iniAddons && iniCampaigns); // global // { @@ -475,6 +492,13 @@ void Settings::Save() iniAddons->setValue(s25util::toStringClassic(it.first), s25util::toStringClassic(it.second)); // } + // campaigns + // { + iniCampaigns->clear(); + for(const auto& it : campaigns.saveData) + iniCampaigns->setValue(it.first, it.second); + // } + bfs::path settingsPath = RTTRCONFIG.ExpandPath(s25::resources::config); if(libsiedler2::Write(settingsPath, settings) == 0) bfs::permissions(settingsPath, bfs::owner_read | bfs::owner_write); diff --git a/libs/s25main/Settings.h b/libs/s25main/Settings.h index 2674fb5871..a8a02a51cf 100644 --- a/libs/s25main/Settings.h +++ b/libs/s25main/Settings.h @@ -129,11 +129,16 @@ class Settings : public Singleton std::map configuration; } addons; + struct + { + std::map saveData; + } campaigns; + static const std::array SCREEN_REFRESH_RATES; private: static const int VERSION; - static const std::array SECTION_NAMES; + static const std::array SECTION_NAMES; }; #define SETTINGS Settings::inst() diff --git a/libs/s25main/desktops/dskCampaignMissionSelection.cpp b/libs/s25main/desktops/dskCampaignMissionSelection.cpp index 3d14bc2a6d..51362ad561 100644 --- a/libs/s25main/desktops/dskCampaignMissionSelection.cpp +++ b/libs/s25main/desktops/dskCampaignMissionSelection.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "dskCampaignMissionSelection.h" +#include "CampaignSaveData.h" #include "Loader.h" #include "WindowManager.h" #include "commonDefines.h" @@ -74,7 +75,7 @@ dskCampaignMissionSelection::dskCampaignMissionSelection(CreateServerInfo csi, c auto* mapSelection = AddMapSelection(ID_MapSelection, DrawPoint(0, 0), Extent(800, 508), *campaign_->selectionMapData); - mapSelection->setMissionsStatus(std::vector(campaign_->getNumMaps(), {true, true})); + mapSelection->setMissionsStatus(getMissionsStatus(*campaign_)); btStart->SetEnabled(static_cast(mapSelection->getSelection())); } else { @@ -147,8 +148,10 @@ void dskCampaignMissionSelection::UpdateMissionPage() const auto& header = checkedCast(map[0])->getHeader(); - group->AddTextButton(i, curBtPos, missionBtSize, TextureColor::Grey, s25util::ansiToUTF8(header.getName()), - NormalFont); + group + ->AddTextButton(i, curBtPos, missionBtSize, TextureColor::Grey, s25util::ansiToUTF8(header.getName()), + NormalFont) + ->SetEnabled(isChapterEnabled(*campaign_, i)); curBtPos.y += missionBtSize.y + distanceBetweenMissionButtonsY; } } diff --git a/libs/s25main/desktops/dskCampaignSelection.cpp b/libs/s25main/desktops/dskCampaignSelection.cpp index 36e294798c..88c970485b 100644 --- a/libs/s25main/desktops/dskCampaignSelection.cpp +++ b/libs/s25main/desktops/dskCampaignSelection.cpp @@ -86,7 +86,8 @@ dskCampaignSelection::dskCampaignSelection(CreateServerInfo csi) Extent(secondColumnExtentX, mutlilineExtentY), TextureColor::Grey, NormalFont); AddTextButton(ID_btBack, DrawPoint(380, 560), Extent(200, 22), TextureColor::Red1, _("Back"), NormalFont); - AddTextButton(ID_Next, DrawPoint(590, 560), Extent(200, 22), TextureColor::Green2, _("Continue"), NormalFont); + AddTextButton(ID_Next, DrawPoint(590, 560), Extent(200, 22), TextureColor::Green2, _("Continue"), NormalFont) + ->SetEnabled(false); showCampaignInfo(false); diff --git a/libs/s25main/desktops/dskCampaignVictory.cpp b/libs/s25main/desktops/dskCampaignVictory.cpp new file mode 100644 index 0000000000..052f14ef62 --- /dev/null +++ b/libs/s25main/desktops/dskCampaignVictory.cpp @@ -0,0 +1,39 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "dskCampaignVictory.h" +#include "Loader.h" +#include "WindowManager.h" +#include "desktops/dskMainMenu.h" +#include "network/GameClient.h" + +dskCampaignVictory::dskCampaignVictory(unsigned char chapter) + : Desktop(LOADER.GetImageN(ResourceId{chapter ? "setup896" : "setup895"}, 0)) +{ + GAMECLIENT.SetCampaignChapterCompleted(0); + GAMECLIENT.SetCampaignCompleted(false); + + if(!chapter) + return; + + AddText(10, DrawPoint{800 / 2, 600 - 50}, + _("You have successfully completed chapter") + std::string{" "} + std::to_string(chapter) + ".", + COLOR_YELLOW, FontStyle::CENTER, LargeFont); +} + +bool dskCampaignVictory::Msg_LeftDown(const MouseCoords&) +{ + return ShowMenu(); +} + +bool dskCampaignVictory::Msg_KeyDown(const KeyEvent&) +{ + return ShowMenu(); +} + +bool dskCampaignVictory::ShowMenu() +{ + WINDOWMANAGER.Switch(std::make_unique()); + return true; +} diff --git a/libs/s25main/desktops/dskCampaignVictory.h b/libs/s25main/desktops/dskCampaignVictory.h new file mode 100644 index 0000000000..5520422744 --- /dev/null +++ b/libs/s25main/desktops/dskCampaignVictory.h @@ -0,0 +1,21 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Desktop.h" + +class MouseCoords; + +class dskCampaignVictory : public Desktop +{ +public: + dskCampaignVictory(unsigned char chapter); + +private: + bool Msg_LeftDown(const MouseCoords&) override; + bool Msg_KeyDown(const KeyEvent&) override; + + bool ShowMenu(); +}; diff --git a/libs/s25main/lua/LuaInterfaceGame.cpp b/libs/s25main/lua/LuaInterfaceGame.cpp index 18c4d46cf7..2318d42fc1 100644 --- a/libs/s25main/lua/LuaInterfaceGame.cpp +++ b/libs/s25main/lua/LuaInterfaceGame.cpp @@ -5,6 +5,7 @@ #include "LuaInterfaceGame.h" #include "EventManager.h" #include "Game.h" +#include "Settings.h" #include "WindowManager.h" #include "ai/AIInterface.h" #include "ai/AIPlayer.h" @@ -12,9 +13,11 @@ #include "lua/LuaHelpers.h" #include "lua/LuaPlayer.h" #include "lua/LuaWorld.h" +#include "network/GameClient.h" #include "postSystem/PostMsg.h" #include "world/GameWorld.h" #include "gameTypes/Resource.h" +#include "gameData/CampaignSaveCodes.h" #include "s25util/Serializer.h" #include "s25util/strAlgos.h" @@ -196,23 +199,26 @@ KAGUYA_MEMBER_FUNCTION_OVERLOADS(SetMissionGoalWrapper, LuaInterfaceGame, SetMis void LuaInterfaceGame::Register(kaguya::State& state) { - state["RTTRGame"].setClass(kaguya::UserdataMetatable() - .addFunction("ClearResources", &LuaInterfaceGame::ClearResources) - .addFunction("GetGF", &LuaInterfaceGame::GetGF) - .addFunction("FormatNumGFs", &LuaInterfaceGame::FormatNumGFs) - .addFunction("GetGameFrame", &LuaInterfaceGame::GetGF) - .addFunction("GetNumPlayers", &LuaInterfaceGame::GetNumPlayers) - .addFunction("Chat", &LuaInterfaceGame::Chat) - .addOverloadedFunctions("MissionStatement", &LuaInterfaceGame::MissionStatement, - &LuaInterfaceGame::MissionStatement2, - &LuaInterfaceGame::MissionStatement3) - .addFunction("SetMissionGoal", SetMissionGoalWrapper()) - .addFunction("PostMessage", &LuaInterfaceGame::PostMessageLua) - .addFunction("PostMessageWithLocation", &LuaInterfaceGame::PostMessageWithLocation) - .addFunction("GetPlayer", &LuaInterfaceGame::GetPlayer) - .addFunction("GetWorld", &LuaInterfaceGame::GetWorld) - // Old name - .addFunction("GetPlayerCount", &LuaInterfaceGame::GetNumPlayers)); + state["RTTRGame"].setClass( + kaguya::UserdataMetatable() + .addFunction("ClearResources", &LuaInterfaceGame::ClearResources) + .addFunction("GetGF", &LuaInterfaceGame::GetGF) + .addFunction("FormatNumGFs", &LuaInterfaceGame::FormatNumGFs) + .addFunction("GetGameFrame", &LuaInterfaceGame::GetGF) + .addFunction("GetNumPlayers", &LuaInterfaceGame::GetNumPlayers) + .addFunction("Chat", &LuaInterfaceGame::Chat) + .addOverloadedFunctions("MissionStatement", &LuaInterfaceGame::MissionStatement, + &LuaInterfaceGame::MissionStatement2, &LuaInterfaceGame::MissionStatement3) + .addFunction("SetMissionGoal", SetMissionGoalWrapper()) + .addFunction("PostMessage", &LuaInterfaceGame::PostMessageLua) + .addFunction("PostMessageWithLocation", &LuaInterfaceGame::PostMessageWithLocation) + .addFunction("EnableCampaignChapter", &LuaInterfaceGame::EnableCampaignChapter) + .addFunction("SetCampaignChapterCompleted", &LuaInterfaceGame::SetCampaignChapterCompleted) + .addFunction("SetCampaignCompleted", &LuaInterfaceGame::SetCampaignCompleted) + .addFunction("GetPlayer", &LuaInterfaceGame::GetPlayer) + .addFunction("GetWorld", &LuaInterfaceGame::GetWorld) + // Old name + .addFunction("GetPlayerCount", &LuaInterfaceGame::GetNumPlayers)); state["RTTR_Serializer"].setClass(kaguya::UserdataMetatable() .addFunction("PushInt", &Serializer::PushSignedInt) .addFunction("PopInt", &Serializer::PopSignedInt) @@ -321,6 +327,42 @@ void LuaInterfaceGame::PostMessageWithLocation(int playerIdx, const std::string& gw.MakeMapPoint(Position(x, y)))); } +namespace { +void MarkCampaignChapter(const std::string& campaignUid, unsigned char chapter, char code) +{ + if(chapter < 1) + return; + + auto& saveData = SETTINGS.campaigns.saveData[campaignUid]; + + if(saveData.length() < chapter) + saveData.resize(chapter); + + auto& chapterSaveData = saveData[chapter - 1]; + + if(chapterSaveData == CampaignSaveCodes::chapterCompleted) + return; + + chapterSaveData = code; +} +} // namespace + +void LuaInterfaceGame::EnableCampaignChapter(const std::string& campaignUid, unsigned char chapter) +{ + MarkCampaignChapter(campaignUid, chapter, CampaignSaveCodes::chapterEnabled); +} + +void LuaInterfaceGame::SetCampaignChapterCompleted(const std::string& campaignUid, unsigned char chapter) +{ + MarkCampaignChapter(campaignUid, chapter, CampaignSaveCodes::chapterCompleted); + GAMECLIENT.SetCampaignChapterCompleted(chapter); +} + +void LuaInterfaceGame::SetCampaignCompleted(const std::string& /* campaignUid */) +{ + GAMECLIENT.SetCampaignCompleted(true); +} + LuaPlayer LuaInterfaceGame::GetPlayer(int playerIdx) { lua::assertTrue(playerIdx >= 0 && static_cast(playerIdx) < gw.GetNumPlayers(), "Invalid player idx"); @@ -371,6 +413,13 @@ void LuaInterfaceGame::EventStart(bool isFirstStart) onStart.call(isFirstStart); } +void LuaInterfaceGame::EventHumanWinner() +{ + kaguya::LuaRef onStart = lua["onHumanWinner"]; + if(onStart.type() == LUA_TFUNCTION) + onStart.call(); +} + void LuaInterfaceGame::EventGameFrame(unsigned nr) { kaguya::LuaRef onGameFrame = lua["onGameFrame"]; diff --git a/libs/s25main/lua/LuaInterfaceGame.h b/libs/s25main/lua/LuaInterfaceGame.h index e71758adcb..1fba80d6e1 100644 --- a/libs/s25main/lua/LuaInterfaceGame.h +++ b/libs/s25main/lua/LuaInterfaceGame.h @@ -33,6 +33,7 @@ class LuaInterfaceGame : public LuaInterfaceGameBase void EventOccupied(unsigned player, MapPoint pt); void EventAttack(unsigned char attackerPlayerId, unsigned char defenderPlayerId, unsigned attackerCount); void EventStart(bool isFirstStart); + void EventHumanWinner(); void EventGameFrame(unsigned nr); void EventResourceFound(unsigned char player, MapPoint pt, ResourceType type, unsigned char quantity); // Called if player wants to cancel a pact @@ -45,6 +46,7 @@ class LuaInterfaceGame : public LuaInterfaceGameBase // called if pact was created void EventPactCreated(PactType pt, unsigned char suggestedByPlayerId, unsigned char targetPlayerId, unsigned duration); + // Callable from Lua void ClearResources(); unsigned GetGF() const; @@ -59,6 +61,10 @@ class LuaInterfaceGame : public LuaInterfaceGameBase void PostMessageLua(int playerIdx, const std::string& msg); void PostMessageWithLocation(int playerIdx, const std::string& msg, int x, int y); + void EnableCampaignChapter(const std::string& campaignUid, unsigned char chapter); + void SetCampaignChapterCompleted(const std::string& campaignUid, unsigned char chapter); + void SetCampaignCompleted(const std::string& campaignUid); + private: ILocalGameState& localGameState; GameWorld& gw; diff --git a/libs/s25main/network/GameClient.h b/libs/s25main/network/GameClient.h index c1638a5890..f0d6c91a6a 100644 --- a/libs/s25main/network/GameClient.h +++ b/libs/s25main/network/GameClient.h @@ -102,6 +102,11 @@ class GameClient final : /// Beendet das Spiel, zerstört die Spielstrukturen void ExitGame(); + void SetCampaignChapterCompleted(unsigned char chapter) { chapterCompleted = chapter; } + void SetCampaignCompleted(bool state) { campaignCompleted = state; } + unsigned char GetCampaignChapterCompleted() const { return chapterCompleted; } + bool IsCampaignCompleted() const { return campaignCompleted; } + ClientState GetState() const { return state; } Replay* GetReplay(); std::shared_ptr GetNWFInfo() const; @@ -308,6 +313,9 @@ class GameClient final : /// GameCommands, die vom Client noch an den Server gesendet werden müssen std::vector gameCommands_; + unsigned char chapterCompleted = 0; + bool campaignCompleted = false; + std::unique_ptr replayinfo; bool replayMode; diff --git a/tests/libGameData/testCampaignLuaFile.cpp b/tests/libGameData/testCampaignLuaFile.cpp index 676ec64a5b..914e2c5817 100644 --- a/tests/libGameData/testCampaignLuaFile.cpp +++ b/tests/libGameData/testCampaignLuaFile.cpp @@ -30,6 +30,7 @@ BOOST_AUTO_TEST_CASE(ScriptVersion) file << R"(campaign ={ version = "1", + uid = "roman", author = "Max Meier", name = "My campaign", shortDescription = "Very short description", @@ -116,6 +117,7 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionWithoutTranslation) file << R"(campaign ={ version = "1", + uid = "roman", author = "Max Meier", name = "My campaign", shortDescription = "Very short description", @@ -125,7 +127,8 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionWithoutTranslation) difficulty = "easy", mapFolder = "/DATA/MAPS", luaFolder = "/CAMPAIGNS/ROMAN", - maps = { "dessert0.WLD", "dessert1.WLD", "dessert2.WLD"} + maps = { "dessert0.WLD", "dessert1.WLD", "dessert2.WLD"}, + defaultChaptersEnabled = "100000000" } )"; @@ -138,6 +141,7 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionWithoutTranslation) // campaign description BOOST_TEST(desc.version == "1"); + BOOST_TEST(desc.uid == "roman"); BOOST_TEST(desc.author == "Max Meier"); BOOST_TEST(desc.name == "My campaign"); BOOST_TEST(desc.shortDescription == "Very short description"); @@ -145,6 +149,7 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionWithoutTranslation) BOOST_TEST(desc.image == "/GFX/PICS/WORLD.LBM"); BOOST_TEST(desc.maxHumanPlayers == 1u); BOOST_TEST(desc.difficulty == "easy"); + BOOST_TEST(desc.defaultChaptersEnabled == "100000000"); // maps BOOST_TEST(desc.getNumMaps() == 3u); @@ -164,6 +169,7 @@ BOOST_AUTO_TEST_CASE(LoadCampaignWithoutImage) file << R"( campaign = { version = "1", + uid = "roman", author = "Max Meier", name = "My campaign", shortDescription = "short", @@ -206,6 +212,7 @@ BOOST_AUTO_TEST_CASE(HandleMapAndLuaPaths) file << R"( campaign = { version = "1", + uid = "roman", author = "Max Meier", name = "My campaign", shortDescription = "short", @@ -329,6 +336,7 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionFailsDueToIncorrectDifficulty) file << R"(campaign ={ version = "1", + uid = "roman", author = "Max Meier", name = "My campaign", shortDescription = "Very short description", @@ -360,6 +368,7 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionFailsDueToMissingField) file << R"(campaign ={ version = "1", + uid = "roman", author = "Max Meier", name = "My campaign", shortDescription = "Very short description", @@ -407,6 +416,7 @@ BOOST_AUTO_TEST_CASE(CampaignDescriptionLoadWithTranslation) file << R"(campaign = { version = "1", + uid = "roman", author = "Max Meier", name = _"name", shortDescription = _"shortDescription", @@ -431,6 +441,7 @@ BOOST_AUTO_TEST_CASE(CampaignDescriptionLoadWithTranslation) // campaign description BOOST_TEST(desc.version == "1"); + BOOST_TEST(desc.uid == "roman"); BOOST_TEST(desc.author == "Max Meier"); BOOST_TEST(desc.name == "My campaign"); BOOST_TEST(desc.shortDescription == "Sehr kurze Beschreibung"); @@ -460,6 +471,7 @@ BOOST_AUTO_TEST_CASE(OptionalSelectionMapLoadTest) file << R"(campaign = { version = "1", + uid = "roman", author = "Max Meier", name = "My campaign", shortDescription = "Very short description", @@ -496,6 +508,7 @@ BOOST_AUTO_TEST_CASE(OptionalSelectionMapLoadTest) // campaign description BOOST_TEST(desc.version == "1"); + BOOST_TEST(desc.uid == "roman"); BOOST_TEST(desc.author == "Max Meier"); BOOST_TEST(desc.name == "My campaign"); BOOST_TEST(desc.shortDescription == "Very short description"); diff --git a/tests/s25Main/integration/testCampaignSaveData.cpp b/tests/s25Main/integration/testCampaignSaveData.cpp new file mode 100644 index 0000000000..2e2ca89c6c --- /dev/null +++ b/tests/s25Main/integration/testCampaignSaveData.cpp @@ -0,0 +1,102 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "CampaignSaveData.h" +#include "Settings.h" +#include "lua/CampaignDataLoader.h" +#include "gameData/CampaignDescription.h" +#include "gameData/CampaignSaveCodes.h" +#include "rttr/test/TmpFolder.hpp" +#include +#include + +namespace { +constexpr auto uid = "uniqueID"; +struct CampaignSaveDataFixture +{ + CampaignSaveDataFixture() + { + desc.uid = uid; + saveData.clear(); + } + + CampaignDescription desc; + std::map& saveData = SETTINGS.campaigns.saveData; +}; +} // namespace + +BOOST_FIXTURE_TEST_CASE(isChapterEnabled_ReturnsFalse_WhenChapterIsDisabled, CampaignSaveDataFixture) +{ + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 0) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 1) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 2) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 9) == false); +} + +BOOST_FIXTURE_TEST_CASE(isChapterEnabled_ReturnsTrue_WhenChapterIsEnabled, CampaignSaveDataFixture) +{ + saveData[uid] = "011"; + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 0) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 1) == true); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 2) == true); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 9) == false); +} + +BOOST_FIXTURE_TEST_CASE(Chapters0and1AreEnabledByDefault, CampaignSaveDataFixture) +{ + saveData[uid] = CampaignSaveCodes::defaultChaptersEnabled; + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 0) == true); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 1) == true); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 2) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 9) == false); +} + +BOOST_FIXTURE_TEST_CASE(DefaultChaptersCanBeSetPerCampaign, CampaignSaveDataFixture) +{ + desc.defaultChaptersEnabled = "100"; + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 0) == true); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 1) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 2) == false); + BOOST_TEST_REQUIRE(isChapterEnabled(desc, 9) == false); +} + +BOOST_FIXTURE_TEST_CASE( + getMissionsStatus_ReturnsPlayableForChaptersWhichAreEnabled_AndConqueredForChaptersWhichAreCompleted, + CampaignSaveDataFixture) +{ + rttr::test::TmpFolder tmp; + { + boost::nowide::ofstream file(tmp / "campaign.lua"); + + file << "campaign ={\ + version = \"1\",\ + uid = \"roman\",\ + author = \"Max Meier\",\ + name = \"Meine Kampagne\",\ + shortDescription = \"Sehr kurze Beschreibung\",\ + longDescription = \"Das ist die lange Beschreibung\",\ + image = \"/GFX/PICS/WORLD.LBM\",\ + maxHumanPlayers = 1,\ + difficulty = \"easy\",\ + mapFolder = \"/DATA/MAPS\",\ + luaFolder = \"/CAMPAIGNS/ROMAN\",\ + maps = { \"dessert0.WLD\", \"dessert1.WLD\", \"dessert2.WLD\"}\ + }"; + + file << "function getRequiredLuaVersion() return 1 end"; + } + + CampaignDescription dsc; + CampaignDataLoader loader{dsc, tmp}; + BOOST_TEST_REQUIRE(loader.Load()); + + saveData["roman"] = "210"; + const auto& ms = getMissionsStatus(dsc); + BOOST_TEST_REQUIRE(ms[0].playable == true); + BOOST_TEST_REQUIRE(ms[0].conquered == true); + BOOST_TEST_REQUIRE(ms[1].playable == true); + BOOST_TEST_REQUIRE(ms[1].conquered == false); + BOOST_TEST_REQUIRE(ms[2].playable == false); + BOOST_TEST_REQUIRE(ms[2].conquered == false); +} diff --git a/tests/s25Main/lua/testLua.cpp b/tests/s25Main/lua/testLua.cpp index 34fe0d42fb..0c5e452f83 100644 --- a/tests/s25Main/lua/testLua.cpp +++ b/tests/s25Main/lua/testLua.cpp @@ -6,6 +6,7 @@ #include "Loader.h" #include "PointOutput.h" #include "RttrForeachPt.h" +#include "Settings.h" #include "buildings/noBuildingSite.h" #include "buildings/nobHQ.h" #include "enum_cast.hpp" @@ -843,6 +844,21 @@ BOOST_AUTO_TEST_CASE(onExplored) } } +BOOST_AUTO_TEST_CASE(CampaignStatusCanBeChangedFromLua) +{ + initWorld(); + + SETTINGS.campaigns.saveData["campaign_id"] = "110"; + executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 2)"); + executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 4)"); + executeLua("rttr:EnableCampaignChapter('campaign_id', 5)"); + executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 0)"); // noop - chapters start from 1 + executeLua("rttr:EnableCampaignChapter('campaign_id', 0)"); // noop - chapters start from 1 + executeLua("rttr:EnableCampaignChapter('campaign_id', 2)"); // noop - already completed + game.executeAICommands(); + BOOST_TEST_REQUIRE(SETTINGS.campaigns.saveData["campaign_id"] == "12021"); +} + BOOST_AUTO_TEST_CASE(LuaPacts) { initWorld(); From 59d8813dcee2ed3501169956ec6564023d6eeee3 Mon Sep 17 00:00:00 2001 From: Jakub Audykowicz Date: Sun, 22 Dec 2024 14:43:59 +0100 Subject: [PATCH 4/6] Temporarily enable Roman chapter 8 after completing chapter 6 --- data/RTTR/campaigns/roman/MISS205.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/data/RTTR/campaigns/roman/MISS205.lua b/data/RTTR/campaigns/roman/MISS205.lua index 4ced4c3071..724a0d2fff 100644 --- a/data/RTTR/campaigns/roman/MISS205.lua +++ b/data/RTTR/campaigns/roman/MISS205.lua @@ -494,6 +494,7 @@ function MissionEvent(e, onLoad) rttr:GetWorld():AddStaticObject(148, 50, 561, 0xFFFF, 2) rttr:SetCampaignChapterCompleted("roman", 6) rttr:EnableCampaignChapter("roman", 7) + rttr:EnableCampaignChapter("roman", 8) -- TODO: remove this when chapter 7 is playable end -- update event state From 7a15afd709d1f52f4a0c981256683034eedb61f1 Mon Sep 17 00:00:00 2001 From: Jakub Audykowicz Date: Wed, 25 Dec 2024 22:11:29 +0100 Subject: [PATCH 5/6] Rework CampaignSettings --- data/RTTR/campaigns/roman/MISS200.lua | 2 +- data/RTTR/campaigns/roman/MISS201.lua | 4 +- data/RTTR/campaigns/roman/MISS202.lua | 4 +- data/RTTR/campaigns/roman/MISS203.lua | 4 +- data/RTTR/campaigns/roman/MISS204.lua | 4 +- data/RTTR/campaigns/roman/MISS205.lua | 6 +- data/RTTR/campaigns/roman/MISS206.lua | 4 +- data/RTTR/campaigns/roman/MISS207.lua | 4 +- data/RTTR/campaigns/roman/MISS208.lua | 4 +- data/RTTR/campaigns/roman/MISS209.lua | 2 +- data/RTTR/campaigns/world/campaign.lua | 2 +- .../gameData/CampaignDescription.cpp | 7 +- .../gameData/CampaignDescription.h | 3 +- libs/libGamedata/gameData/CampaignSaveCodes.h | 12 -- libs/libGamedata/gameData/CampaignTypes.h | 10 ++ libs/s25main/CampaignSaveData.cpp | 45 ------- libs/s25main/CampaignSaveData.h | 12 -- libs/s25main/CampaignSettings.cpp | 116 +++++++++++++++++ libs/s25main/CampaignSettings.h | 48 +++++++ libs/s25main/GameManager.cpp | 6 +- libs/s25main/Settings.cpp | 6 +- libs/s25main/Settings.h | 8 +- .../desktops/dskCampaignMissionSelection.cpp | 6 +- libs/s25main/desktops/dskCampaignVictory.cpp | 21 ++-- libs/s25main/desktops/dskCampaignVictory.h | 2 +- libs/s25main/lua/LuaInterfaceGame.cpp | 34 +---- libs/s25main/lua/LuaInterfaceGame.h | 7 +- libs/s25main/network/GameClient.h | 8 -- tests/libGameData/testCampaignLuaFile.cpp | 13 +- .../integration/testCampaignSaveData.cpp | 102 --------------- .../integration/testCampaignSettings.cpp | 118 ++++++++++++++++++ tests/s25Main/lua/testLua.cpp | 25 ++-- 32 files changed, 360 insertions(+), 289 deletions(-) delete mode 100644 libs/libGamedata/gameData/CampaignSaveCodes.h create mode 100644 libs/libGamedata/gameData/CampaignTypes.h delete mode 100644 libs/s25main/CampaignSaveData.cpp delete mode 100644 libs/s25main/CampaignSaveData.h create mode 100644 libs/s25main/CampaignSettings.cpp create mode 100644 libs/s25main/CampaignSettings.h delete mode 100644 tests/s25Main/integration/testCampaignSaveData.cpp create mode 100644 tests/s25Main/integration/testCampaignSettings.cpp diff --git a/data/RTTR/campaigns/roman/MISS200.lua b/data/RTTR/campaigns/roman/MISS200.lua index bdfea827ee..6dc926d3d7 100644 --- a/data/RTTR/campaigns/roman/MISS200.lua +++ b/data/RTTR/campaigns/roman/MISS200.lua @@ -508,7 +508,7 @@ function MissionEvent(e, onLoad) elseif(e == 99) then -- Show opened arc rttr:GetWorld():AddStaticObject(14, 8, 561, 0xFFFF, 2) - rttr:SetCampaignChapterCompleted("roman", 1) + rttr:SetCampaignChapterCompleted("roman", 0) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS201.lua b/data/RTTR/campaigns/roman/MISS201.lua index f8dde050a7..bcc2b4a6a1 100644 --- a/data/RTTR/campaigns/roman/MISS201.lua +++ b/data/RTTR/campaigns/roman/MISS201.lua @@ -515,8 +515,8 @@ function MissionEvent(e, onLoad) elseif(e == 99) then -- Show opened arc rttr:GetWorld():AddStaticObject(48, 9, 561, 0xFFFF, 2) - rttr:SetCampaignChapterCompleted("roman", 2) - rttr:EnableCampaignChapter("roman", 3) + rttr:SetCampaignChapterCompleted("roman", 1) + rttr:EnableCampaignChapter("roman", 2) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS202.lua b/data/RTTR/campaigns/roman/MISS202.lua index def471c7c2..e45e907429 100644 --- a/data/RTTR/campaigns/roman/MISS202.lua +++ b/data/RTTR/campaigns/roman/MISS202.lua @@ -497,8 +497,8 @@ function MissionEvent(e, onLoad) elseif(e == 99) then -- Show opened arc rttr:GetWorld():AddStaticObject(89, 20, 561, 0xFFFF, 2) - rttr:SetCampaignChapterCompleted("roman", 3) - rttr:EnableCampaignChapter("roman", 4) + rttr:SetCampaignChapterCompleted("roman", 2) + rttr:EnableCampaignChapter("roman", 3) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS203.lua b/data/RTTR/campaigns/roman/MISS203.lua index b34159d768..abf545db76 100644 --- a/data/RTTR/campaigns/roman/MISS203.lua +++ b/data/RTTR/campaigns/roman/MISS203.lua @@ -478,8 +478,8 @@ function MissionEvent(e, onLoad) elseif(e == 99) then -- Show opened arc rttr:GetWorld():AddStaticObject(97, 68, 561, 0xFFFF, 2) - rttr:SetCampaignChapterCompleted("roman", 4) - rttr:EnableCampaignChapter("roman", 5) + rttr:SetCampaignChapterCompleted("roman", 3) + rttr:EnableCampaignChapter("roman", 4) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS204.lua b/data/RTTR/campaigns/roman/MISS204.lua index 2d74fc06c1..a20f0a4ded 100644 --- a/data/RTTR/campaigns/roman/MISS204.lua +++ b/data/RTTR/campaigns/roman/MISS204.lua @@ -434,8 +434,8 @@ function MissionEvent(e, onLoad) if(e == 99) then -- Show opened arc rttr:GetWorld():AddStaticObject(19, 37, 561, 0xFFFF, 2) - rttr:SetCampaignChapterCompleted("roman", 5) - rttr:EnableCampaignChapter("roman", 6) + rttr:SetCampaignChapterCompleted("roman", 4) + rttr:EnableCampaignChapter("roman", 5) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS205.lua b/data/RTTR/campaigns/roman/MISS205.lua index 724a0d2fff..4aea639bee 100644 --- a/data/RTTR/campaigns/roman/MISS205.lua +++ b/data/RTTR/campaigns/roman/MISS205.lua @@ -492,9 +492,9 @@ function MissionEvent(e, onLoad) if(e == 99) then -- Show opened arc rttr:GetWorld():AddStaticObject(148, 50, 561, 0xFFFF, 2) - rttr:SetCampaignChapterCompleted("roman", 6) - rttr:EnableCampaignChapter("roman", 7) - rttr:EnableCampaignChapter("roman", 8) -- TODO: remove this when chapter 7 is playable + rttr:SetCampaignChapterCompleted("roman", 5) + rttr:EnableCampaignChapter("roman", 6) + rttr:EnableCampaignChapter("roman", 7) -- TODO: remove this when chapter 7 is playable end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS206.lua b/data/RTTR/campaigns/roman/MISS206.lua index dfae05cbf0..9d961a797c 100644 --- a/data/RTTR/campaigns/roman/MISS206.lua +++ b/data/RTTR/campaigns/roman/MISS206.lua @@ -452,8 +452,8 @@ function MissionEvent(e, onLoad) if(e == 99) then -- Show opened arc rttr:GetWorld():AddStaticObject(13, 66, 561, 0xFFFF, 2) - rttr:SetCampaignChapterCompleted("roman", 7) - rttr:EnableCampaignChapter("roman", 8) + rttr:SetCampaignChapterCompleted("roman", 6) + rttr:EnableCampaignChapter("roman", 7) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS207.lua b/data/RTTR/campaigns/roman/MISS207.lua index 3fd28a5345..76c438a65b 100644 --- a/data/RTTR/campaigns/roman/MISS207.lua +++ b/data/RTTR/campaigns/roman/MISS207.lua @@ -423,8 +423,8 @@ function MissionEvent(e, onLoad) if(e == 99) then -- Show opened arc rttr:GetWorld():AddStaticObject(11, 125, 561, 0xFFFF, 2) - rttr:SetCampaignChapterCompleted("roman", 8) - rttr:EnableCampaignChapter("roman", 9) + rttr:SetCampaignChapterCompleted("roman", 7) + rttr:EnableCampaignChapter("roman", 8) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS208.lua b/data/RTTR/campaigns/roman/MISS208.lua index 45f0ba9887..df4f7e17b7 100644 --- a/data/RTTR/campaigns/roman/MISS208.lua +++ b/data/RTTR/campaigns/roman/MISS208.lua @@ -420,8 +420,8 @@ function MissionEvent(e, onLoad) if(e == 99) then -- Show opened arc - Done rttr:GetWorld():AddStaticObject(127, 48, 561, 0xFFFF, 2) - rttr:SetCampaignChapterCompleted("roman", 9) - rttr:EnableCampaignChapter("roman", 10) + rttr:SetCampaignChapterCompleted("roman", 8) + rttr:EnableCampaignChapter("roman", 9) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS209.lua b/data/RTTR/campaigns/roman/MISS209.lua index cc4a2c6f13..3ede0dbf57 100644 --- a/data/RTTR/campaigns/roman/MISS209.lua +++ b/data/RTTR/campaigns/roman/MISS209.lua @@ -441,7 +441,7 @@ function MissionEvent(e, onLoad) if(e == 99) then -- Show opened arc rttr:GetWorld():AddStaticObject(75, 40, 561, 0xFFFF, 2) - rttr:SetCampaignChapterCompleted("roman", 10) + rttr:SetCampaignChapterCompleted("roman", 9) rttr:SetCampaignCompleted("roman") end diff --git a/data/RTTR/campaigns/world/campaign.lua b/data/RTTR/campaigns/world/campaign.lua index ce1af9d1c7..be0140fc6e 100644 --- a/data/RTTR/campaigns/world/campaign.lua +++ b/data/RTTR/campaigns/world/campaign.lua @@ -32,7 +32,7 @@ campaign = { mapFolder = "/DATA/MAPS2", luaFolder = "/CAMPAIGNS/WORLD", maps = {"EUROPE.WLD", "AFRICA.WLD", "NAMERICA.WLD", "SAMERICA.WLD", "GREEN.WLD", "AUSTRA.WLD", "NASIA.WLD", "SASIA.WLD", "JAPAN.WLD"}, - defaultChaptersEnabled = "100000000", + chaptersEnabled = {1}, selectionMap = { background = {"/GFX/PICS/SETUP990.LBM", 0}, map = {"/GFX/PICS/WORLD.LBM", 0}, diff --git a/libs/libGamedata/gameData/CampaignDescription.cpp b/libs/libGamedata/gameData/CampaignDescription.cpp index 41194edc11..8edb7b8d52 100644 --- a/libs/libGamedata/gameData/CampaignDescription.cpp +++ b/libs/libGamedata/gameData/CampaignDescription.cpp @@ -3,7 +3,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "CampaignDescription.h" -#include "CampaignSaveCodes.h" #include "RttrConfig.h" #include "helpers/format.hpp" #include "lua/CheckedLuaTable.h" @@ -13,7 +12,7 @@ CampaignDescription::CampaignDescription(const boost::filesystem::path& campaignPath, const kaguya::LuaRef& table) { CheckedLuaTable luaData(table); - luaData.getOrThrow(uid, "uid"); + uid = luaData.getOrDefault("uid", decltype(uid){""}); luaData.getOrThrow(version, "version"); luaData.getOrThrow(author, "author"); luaData.getOrThrow(name, "name"); @@ -47,8 +46,8 @@ CampaignDescription::CampaignDescription(const boost::filesystem::path& campaign // Default lua folder to map folder, i.e. LUA files are side by side with the maps luaFolder_ = resolveFolder(luaData.getOrDefault("luaFolder", mapFolder)); mapNames_ = luaData.getOrDefault("maps", std::vector()); - defaultChaptersEnabled = - luaData.getOrDefault("defaultChaptersEnabled", std::string{CampaignSaveCodes::defaultChaptersEnabled}); + // enable chapters 1 and 2 (like in the Roman campaign and the FANpaign) unless specified otherwise + chaptersEnabled = luaData.getOrDefault("chaptersEnabled", {0, 1}); selectionMapData = luaData.getOptional("selectionMap"); luaData.checkUnused(); } diff --git a/libs/libGamedata/gameData/CampaignDescription.h b/libs/libGamedata/gameData/CampaignDescription.h index a72c357639..6a8d26cb2e 100644 --- a/libs/libGamedata/gameData/CampaignDescription.h +++ b/libs/libGamedata/gameData/CampaignDescription.h @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include "CampaignTypes.h" #include "SelectionMapInputData.h" #include #include @@ -24,7 +25,7 @@ struct CampaignDescription std::optional image; unsigned maxHumanPlayers = 1; std::string difficulty; - std::string defaultChaptersEnabled; + std::vector chaptersEnabled; std::optional selectionMapData; CampaignDescription() = default; diff --git a/libs/libGamedata/gameData/CampaignSaveCodes.h b/libs/libGamedata/gameData/CampaignSaveCodes.h deleted file mode 100644 index e9f2dfb2e3..0000000000 --- a/libs/libGamedata/gameData/CampaignSaveCodes.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) -// -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -namespace CampaignSaveCodes { -constexpr auto chapterDisabled = '0'; -constexpr auto chapterEnabled = '1'; -constexpr auto chapterCompleted = '2'; -constexpr auto defaultChaptersEnabled = "1100000000"; -} // namespace CampaignSaveCodes diff --git a/libs/libGamedata/gameData/CampaignTypes.h b/libs/libGamedata/gameData/CampaignTypes.h new file mode 100644 index 0000000000..0cd16e96fd --- /dev/null +++ b/libs/libGamedata/gameData/CampaignTypes.h @@ -0,0 +1,10 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +using CampaignID = std::string; +using ChapterID = unsigned; diff --git a/libs/s25main/CampaignSaveData.cpp b/libs/s25main/CampaignSaveData.cpp deleted file mode 100644 index 0b4ec04fe7..0000000000 --- a/libs/s25main/CampaignSaveData.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) -// -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "CampaignSaveData.h" -#include "Settings.h" -#include "gameData/CampaignDescription.h" -#include "gameData/CampaignSaveCodes.h" - -namespace { -auto getCampaignSaveData(const CampaignDescription& campaignDesc) -{ - auto& saveData = SETTINGS.campaigns.saveData; - - if(!saveData.count(campaignDesc.uid)) - saveData[campaignDesc.uid] = campaignDesc.defaultChaptersEnabled; - - return saveData[campaignDesc.uid]; -} -} // namespace - -bool isChapterEnabled(const CampaignDescription& campaignDesc, unsigned char chapter) -{ - const auto& saveData = getCampaignSaveData(campaignDesc); - - if(chapter >= saveData.length()) - return false; - - return saveData[chapter] != CampaignSaveCodes::chapterDisabled; -} - -std::vector getMissionsStatus(const CampaignDescription& campaignDesc) -{ - const auto& saveData = getCampaignSaveData(campaignDesc); - - std::vector ret(saveData.length()); - for(auto i = 0u; i < saveData.length(); ++i) - { - ret[i].playable = saveData[i] != CampaignSaveCodes::chapterDisabled; - ret[i].conquered = saveData[i] == CampaignSaveCodes::chapterCompleted; - } - - ret.resize(campaignDesc.getNumMaps()); - return ret; -} diff --git a/libs/s25main/CampaignSaveData.h b/libs/s25main/CampaignSaveData.h deleted file mode 100644 index fa1fa3de03..0000000000 --- a/libs/s25main/CampaignSaveData.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) -// -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "controls/ctrlMapSelection.h" - -struct CampaignDescription; - -bool isChapterEnabled(const CampaignDescription& campaignDesc, unsigned char chapter); -std::vector getMissionsStatus(const CampaignDescription& campaignDesc); diff --git a/libs/s25main/CampaignSettings.cpp b/libs/s25main/CampaignSettings.cpp new file mode 100644 index 0000000000..aa839aa5e5 --- /dev/null +++ b/libs/s25main/CampaignSettings.cpp @@ -0,0 +1,116 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "CampaignSettings.h" +#include "Settings.h" +#include "gameData/CampaignDescription.h" + +void CampaignSettings::readSaveData(const CampaignID& campaignId, const std::string& saveString) +{ + int i = 0; + for(auto c : saveString) + setChapterStatus(campaignId, i++, + c == '1' ? ChapterStatus::Enabled : + c == '2' ? ChapterStatus::Completed : + ChapterStatus::Disabled); +} + +std::map CampaignSettings::createSaveData() const +{ + std::map result; + for(const auto& state : states_) + result[state.first] = toSaveString(state.second); + return result; +} + +bool CampaignSettings::isChapterPlayable(const CampaignDescription& campaignDesc, ChapterID chapter) +{ + // backwards compatibility - if campaign uid not specified, then all chapters are playable + if(campaignDesc.uid.empty()) + return true; + + const auto& state = getCampaignState(campaignDesc); + const auto it = state.find(chapter); + return it != state.cend() && it->second != ChapterStatus::Disabled; +} + +std::vector CampaignSettings::getMissionsStatus(const CampaignDescription& campaignDesc) +{ + const auto numMaps = campaignDesc.getNumMaps(); + std::vector result(numMaps); + + // backwards compatibility - if campaign uid not specified, then all chapters are playable + if(campaignDesc.uid.empty()) + { + for(auto& status : result) + status.playable = true; + return result; + } + + auto& state = getCampaignState(campaignDesc); + for(auto i = 0u; i < numMaps; ++i) + { + result[i].playable = state[i] != ChapterStatus::Disabled; + result[i].conquered = state[i] == ChapterStatus::Completed; + } + return result; +} + +bool CampaignSettings::shouldShowVictoryScreen() const +{ + return chapterCompleted_ || campaignCompleted_; +} + +void CampaignSettings::enableChapter(CampaignID campaignUid, ChapterID chapter) +{ + if(states_[campaignUid][chapter] == ChapterStatus::Disabled) + setChapterStatus(campaignUid, chapter, ChapterStatus::Enabled); +} + +void CampaignSettings::setChapterCompleted(CampaignID campaignUid, ChapterID chapter) +{ + setChapterStatus(campaignUid, chapter, ChapterStatus::Completed); + chapterCompleted_ = chapter; +} + +void CampaignSettings::setCampaignCompleted(CampaignID campaignUid) +{ + campaignCompleted_ = campaignUid; +} + +void CampaignSettings::resetCompletionStatus() +{ + chapterCompleted_ = {}; + campaignCompleted_ = {}; +} + +CampaignSettings::CampaignState& CampaignSettings::getCampaignState(const CampaignDescription& campaignDesc) +{ + const auto id = campaignDesc.uid; + if(!states_.count(id)) + for(auto chapter : campaignDesc.chaptersEnabled) + states_[id][chapter] = ChapterStatus::Enabled; + return states_[id]; +} + +std::string CampaignSettings::toSaveString(const CampaignState& state) const +{ + if(state.empty()) + return ""; + + std::string result; + + result.resize(state.crbegin()->first + 1); + for(const auto& status : state) + { + const auto ss = status.second; + result[status.first] = ss == ChapterStatus::Enabled ? '1' : ss == ChapterStatus::Completed ? '2' : '0'; + } + return result; +} + +void CampaignSettings::setChapterStatus(CampaignID campaignUid, ChapterID chapter, ChapterStatus status) +{ + states_[campaignUid][chapter] = status; +} diff --git a/libs/s25main/CampaignSettings.h b/libs/s25main/CampaignSettings.h new file mode 100644 index 0000000000..35f36932bc --- /dev/null +++ b/libs/s25main/CampaignSettings.h @@ -0,0 +1,48 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "controls/ctrlMapSelection.h" +#include "gameData/CampaignTypes.h" + +#include + +struct CampaignDescription; + +class CampaignSettings +{ +public: + void readSaveData(const CampaignID& campaignId, const std::string& saveString); + std::map createSaveData() const; + + bool isChapterPlayable(const CampaignDescription& campaignDesc, ChapterID chapter); + std::vector getMissionsStatus(const CampaignDescription& campaignDesc); + + bool shouldShowVictoryScreen() const; + auto getCompletedCampaign() const { return campaignCompleted_; } + auto getCompletedChapter() const { return chapterCompleted_; } + + void enableChapter(CampaignID campaignUid, ChapterID chapter); + void setChapterCompleted(CampaignID campaignUid, ChapterID chapter); + void setCampaignCompleted(CampaignID campaignUid); + void resetCompletionStatus(); + +private: + enum class ChapterStatus + { + Disabled, + Enabled, + Completed + }; + using CampaignState = std::map; + + CampaignState& getCampaignState(const CampaignDescription& campaignDesc); + std::string toSaveString(const CampaignState& state) const; + void setChapterStatus(CampaignID campaignUid, ChapterID chapter, ChapterStatus status); + + std::map states_; + std::optional chapterCompleted_; + std::optional campaignCompleted_; +}; diff --git a/libs/s25main/GameManager.cpp b/libs/s25main/GameManager.cpp index be118ed48e..7b29bb69f7 100644 --- a/libs/s25main/GameManager.cpp +++ b/libs/s25main/GameManager.cpp @@ -184,10 +184,8 @@ bool GameManager::ShowMenu() if(LOBBYCLIENT.IsLoggedIn()) // Lobby zeigen windowManager_.Switch(std::make_unique()); - else if(GAMECLIENT.IsCampaignCompleted()) - WINDOWMANAGER.Switch(std::make_unique(0)); - else if(const auto chapter = GAMECLIENT.GetCampaignChapterCompleted()) - WINDOWMANAGER.Switch(std::make_unique(chapter)); + else if(SETTINGS.campaigns.shouldShowVictoryScreen()) + WINDOWMANAGER.Switch(std::make_unique()); else // Hauptmenü zeigen windowManager_.Switch(std::make_unique()); diff --git a/libs/s25main/Settings.cpp b/libs/s25main/Settings.cpp index 76c4ec3c78..35fd70198e 100644 --- a/libs/s25main/Settings.cpp +++ b/libs/s25main/Settings.cpp @@ -151,7 +151,7 @@ void Settings::LoadDefaults() // campaigns // { - campaigns.saveData.clear(); + campaigns = CampaignSettings{}; // } LoadIngameDefaults(); @@ -327,7 +327,7 @@ void Settings::Load() for(unsigned campaign = 0; campaign < iniCampaigns->size(); ++campaign) { if(const auto* item = dynamic_cast(iniCampaigns->get(campaign))) - campaigns.saveData.emplace(item->getName(), item->getText()); + campaigns.readSaveData(item->getName(), item->getText()); } // } @@ -495,7 +495,7 @@ void Settings::Save() // campaigns // { iniCampaigns->clear(); - for(const auto& it : campaigns.saveData) + for(const auto& it : campaigns.createSaveData()) iniCampaigns->setValue(it.first, it.second); // } diff --git a/libs/s25main/Settings.h b/libs/s25main/Settings.h index a8a02a51cf..78eb5dd003 100644 --- a/libs/s25main/Settings.h +++ b/libs/s25main/Settings.h @@ -4,14 +4,15 @@ #pragma once +#include "CampaignSettings.h" #include "DrawPoint.h" #include "driver/VideoMode.h" +#include "gameData/const_gui_ids.h" #include "s25util/ProxySettings.h" #include "s25util/Singleton.h" #include #include #include -#include #include #include @@ -129,10 +130,7 @@ class Settings : public Singleton std::map configuration; } addons; - struct - { - std::map saveData; - } campaigns; + CampaignSettings campaigns; static const std::array SCREEN_REFRESH_RATES; diff --git a/libs/s25main/desktops/dskCampaignMissionSelection.cpp b/libs/s25main/desktops/dskCampaignMissionSelection.cpp index 51362ad561..2fad5173ec 100644 --- a/libs/s25main/desktops/dskCampaignMissionSelection.cpp +++ b/libs/s25main/desktops/dskCampaignMissionSelection.cpp @@ -3,8 +3,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "dskCampaignMissionSelection.h" -#include "CampaignSaveData.h" #include "Loader.h" +#include "Settings.h" #include "WindowManager.h" #include "commonDefines.h" #include "controls/ctrlGroup.h" @@ -75,7 +75,7 @@ dskCampaignMissionSelection::dskCampaignMissionSelection(CreateServerInfo csi, c auto* mapSelection = AddMapSelection(ID_MapSelection, DrawPoint(0, 0), Extent(800, 508), *campaign_->selectionMapData); - mapSelection->setMissionsStatus(getMissionsStatus(*campaign_)); + mapSelection->setMissionsStatus(SETTINGS.campaigns.getMissionsStatus(*campaign_)); btStart->SetEnabled(static_cast(mapSelection->getSelection())); } else { @@ -151,7 +151,7 @@ void dskCampaignMissionSelection::UpdateMissionPage() group ->AddTextButton(i, curBtPos, missionBtSize, TextureColor::Grey, s25util::ansiToUTF8(header.getName()), NormalFont) - ->SetEnabled(isChapterEnabled(*campaign_, i)); + ->SetEnabled(SETTINGS.campaigns.isChapterPlayable(*campaign_, i)); curBtPos.y += missionBtSize.y + distanceBetweenMissionButtonsY; } } diff --git a/libs/s25main/desktops/dskCampaignVictory.cpp b/libs/s25main/desktops/dskCampaignVictory.cpp index 052f14ef62..c3bd550891 100644 --- a/libs/s25main/desktops/dskCampaignVictory.cpp +++ b/libs/s25main/desktops/dskCampaignVictory.cpp @@ -4,22 +4,19 @@ #include "dskCampaignVictory.h" #include "Loader.h" +#include "Settings.h" #include "WindowManager.h" #include "desktops/dskMainMenu.h" -#include "network/GameClient.h" -dskCampaignVictory::dskCampaignVictory(unsigned char chapter) - : Desktop(LOADER.GetImageN(ResourceId{chapter ? "setup896" : "setup895"}, 0)) +dskCampaignVictory::dskCampaignVictory() + : Desktop(LOADER.GetImageN(ResourceId{SETTINGS.campaigns.getCompletedCampaign() ? "setup895" : "setup896"}, 0)) { - GAMECLIENT.SetCampaignChapterCompleted(0); - GAMECLIENT.SetCampaignCompleted(false); - - if(!chapter) - return; - - AddText(10, DrawPoint{800 / 2, 600 - 50}, - _("You have successfully completed chapter") + std::string{" "} + std::to_string(chapter) + ".", - COLOR_YELLOW, FontStyle::CENTER, LargeFont); + if(!SETTINGS.campaigns.getCompletedCampaign()) + AddText(10, DrawPoint{800 / 2, 600 - 50}, + _("You have successfully completed chapter") + std::string{" "} + + std::to_string(*SETTINGS.campaigns.getCompletedChapter()) + ".", + COLOR_YELLOW, FontStyle::CENTER, LargeFont); + SETTINGS.campaigns.resetCompletionStatus(); } bool dskCampaignVictory::Msg_LeftDown(const MouseCoords&) diff --git a/libs/s25main/desktops/dskCampaignVictory.h b/libs/s25main/desktops/dskCampaignVictory.h index 5520422744..9f2089427d 100644 --- a/libs/s25main/desktops/dskCampaignVictory.h +++ b/libs/s25main/desktops/dskCampaignVictory.h @@ -11,7 +11,7 @@ class MouseCoords; class dskCampaignVictory : public Desktop { public: - dskCampaignVictory(unsigned char chapter); + dskCampaignVictory(); private: bool Msg_LeftDown(const MouseCoords&) override; diff --git a/libs/s25main/lua/LuaInterfaceGame.cpp b/libs/s25main/lua/LuaInterfaceGame.cpp index 2318d42fc1..ce574c36a4 100644 --- a/libs/s25main/lua/LuaInterfaceGame.cpp +++ b/libs/s25main/lua/LuaInterfaceGame.cpp @@ -17,7 +17,6 @@ #include "postSystem/PostMsg.h" #include "world/GameWorld.h" #include "gameTypes/Resource.h" -#include "gameData/CampaignSaveCodes.h" #include "s25util/Serializer.h" #include "s25util/strAlgos.h" @@ -327,40 +326,19 @@ void LuaInterfaceGame::PostMessageWithLocation(int playerIdx, const std::string& gw.MakeMapPoint(Position(x, y)))); } -namespace { -void MarkCampaignChapter(const std::string& campaignUid, unsigned char chapter, char code) +void LuaInterfaceGame::EnableCampaignChapter(const CampaignID& campaignUid, ChapterID chapter) { - if(chapter < 1) - return; - - auto& saveData = SETTINGS.campaigns.saveData[campaignUid]; - - if(saveData.length() < chapter) - saveData.resize(chapter); - - auto& chapterSaveData = saveData[chapter - 1]; - - if(chapterSaveData == CampaignSaveCodes::chapterCompleted) - return; - - chapterSaveData = code; -} -} // namespace - -void LuaInterfaceGame::EnableCampaignChapter(const std::string& campaignUid, unsigned char chapter) -{ - MarkCampaignChapter(campaignUid, chapter, CampaignSaveCodes::chapterEnabled); + SETTINGS.campaigns.enableChapter(campaignUid, chapter); } -void LuaInterfaceGame::SetCampaignChapterCompleted(const std::string& campaignUid, unsigned char chapter) +void LuaInterfaceGame::SetCampaignChapterCompleted(const CampaignID& campaignUid, ChapterID chapter) { - MarkCampaignChapter(campaignUid, chapter, CampaignSaveCodes::chapterCompleted); - GAMECLIENT.SetCampaignChapterCompleted(chapter); + SETTINGS.campaigns.setChapterCompleted(campaignUid, chapter); } -void LuaInterfaceGame::SetCampaignCompleted(const std::string& /* campaignUid */) +void LuaInterfaceGame::SetCampaignCompleted(const CampaignID& campaignUid) { - GAMECLIENT.SetCampaignCompleted(true); + SETTINGS.campaigns.setCampaignCompleted(campaignUid); } LuaPlayer LuaInterfaceGame::GetPlayer(int playerIdx) diff --git a/libs/s25main/lua/LuaInterfaceGame.h b/libs/s25main/lua/LuaInterfaceGame.h index 1fba80d6e1..7b6ca62df6 100644 --- a/libs/s25main/lua/LuaInterfaceGame.h +++ b/libs/s25main/lua/LuaInterfaceGame.h @@ -7,6 +7,7 @@ #include "LuaInterfaceGameBase.h" #include "gameTypes/MapCoordinates.h" #include "gameTypes/PactTypes.h" +#include "gameData/CampaignTypes.h" #include #include @@ -61,9 +62,9 @@ class LuaInterfaceGame : public LuaInterfaceGameBase void PostMessageLua(int playerIdx, const std::string& msg); void PostMessageWithLocation(int playerIdx, const std::string& msg, int x, int y); - void EnableCampaignChapter(const std::string& campaignUid, unsigned char chapter); - void SetCampaignChapterCompleted(const std::string& campaignUid, unsigned char chapter); - void SetCampaignCompleted(const std::string& campaignUid); + void EnableCampaignChapter(const CampaignID& campaignUid, ChapterID chapter); + void SetCampaignChapterCompleted(const CampaignID& campaignUid, ChapterID chapter); + void SetCampaignCompleted(const CampaignID& campaignUid); private: ILocalGameState& localGameState; diff --git a/libs/s25main/network/GameClient.h b/libs/s25main/network/GameClient.h index f0d6c91a6a..c1638a5890 100644 --- a/libs/s25main/network/GameClient.h +++ b/libs/s25main/network/GameClient.h @@ -102,11 +102,6 @@ class GameClient final : /// Beendet das Spiel, zerstört die Spielstrukturen void ExitGame(); - void SetCampaignChapterCompleted(unsigned char chapter) { chapterCompleted = chapter; } - void SetCampaignCompleted(bool state) { campaignCompleted = state; } - unsigned char GetCampaignChapterCompleted() const { return chapterCompleted; } - bool IsCampaignCompleted() const { return campaignCompleted; } - ClientState GetState() const { return state; } Replay* GetReplay(); std::shared_ptr GetNWFInfo() const; @@ -313,9 +308,6 @@ class GameClient final : /// GameCommands, die vom Client noch an den Server gesendet werden müssen std::vector gameCommands_; - unsigned char chapterCompleted = 0; - bool campaignCompleted = false; - std::unique_ptr replayinfo; bool replayMode; diff --git a/tests/libGameData/testCampaignLuaFile.cpp b/tests/libGameData/testCampaignLuaFile.cpp index 914e2c5817..2efc3e6d22 100644 --- a/tests/libGameData/testCampaignLuaFile.cpp +++ b/tests/libGameData/testCampaignLuaFile.cpp @@ -30,7 +30,6 @@ BOOST_AUTO_TEST_CASE(ScriptVersion) file << R"(campaign ={ version = "1", - uid = "roman", author = "Max Meier", name = "My campaign", shortDescription = "Very short description", @@ -128,7 +127,7 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionWithoutTranslation) mapFolder = "/DATA/MAPS", luaFolder = "/CAMPAIGNS/ROMAN", maps = { "dessert0.WLD", "dessert1.WLD", "dessert2.WLD"}, - defaultChaptersEnabled = "100000000" + chaptersEnabled = {1, 3, 7} } )"; @@ -149,7 +148,7 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionWithoutTranslation) BOOST_TEST(desc.image == "/GFX/PICS/WORLD.LBM"); BOOST_TEST(desc.maxHumanPlayers == 1u); BOOST_TEST(desc.difficulty == "easy"); - BOOST_TEST(desc.defaultChaptersEnabled == "100000000"); + BOOST_TEST(desc.chaptersEnabled == (decltype(desc.chaptersEnabled){1, 3, 7})); // maps BOOST_TEST(desc.getNumMaps() == 3u); @@ -169,7 +168,6 @@ BOOST_AUTO_TEST_CASE(LoadCampaignWithoutImage) file << R"( campaign = { version = "1", - uid = "roman", author = "Max Meier", name = "My campaign", shortDescription = "short", @@ -212,7 +210,6 @@ BOOST_AUTO_TEST_CASE(HandleMapAndLuaPaths) file << R"( campaign = { version = "1", - uid = "roman", author = "Max Meier", name = "My campaign", shortDescription = "short", @@ -336,7 +333,6 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionFailsDueToIncorrectDifficulty) file << R"(campaign ={ version = "1", - uid = "roman", author = "Max Meier", name = "My campaign", shortDescription = "Very short description", @@ -368,7 +364,6 @@ BOOST_AUTO_TEST_CASE(LoadCampaignDescriptionFailsDueToMissingField) file << R"(campaign ={ version = "1", - uid = "roman", author = "Max Meier", name = "My campaign", shortDescription = "Very short description", @@ -416,7 +411,6 @@ BOOST_AUTO_TEST_CASE(CampaignDescriptionLoadWithTranslation) file << R"(campaign = { version = "1", - uid = "roman", author = "Max Meier", name = _"name", shortDescription = _"shortDescription", @@ -441,7 +435,6 @@ BOOST_AUTO_TEST_CASE(CampaignDescriptionLoadWithTranslation) // campaign description BOOST_TEST(desc.version == "1"); - BOOST_TEST(desc.uid == "roman"); BOOST_TEST(desc.author == "Max Meier"); BOOST_TEST(desc.name == "My campaign"); BOOST_TEST(desc.shortDescription == "Sehr kurze Beschreibung"); @@ -471,7 +464,6 @@ BOOST_AUTO_TEST_CASE(OptionalSelectionMapLoadTest) file << R"(campaign = { version = "1", - uid = "roman", author = "Max Meier", name = "My campaign", shortDescription = "Very short description", @@ -508,7 +500,6 @@ BOOST_AUTO_TEST_CASE(OptionalSelectionMapLoadTest) // campaign description BOOST_TEST(desc.version == "1"); - BOOST_TEST(desc.uid == "roman"); BOOST_TEST(desc.author == "Max Meier"); BOOST_TEST(desc.name == "My campaign"); BOOST_TEST(desc.shortDescription == "Very short description"); diff --git a/tests/s25Main/integration/testCampaignSaveData.cpp b/tests/s25Main/integration/testCampaignSaveData.cpp deleted file mode 100644 index 2e2ca89c6c..0000000000 --- a/tests/s25Main/integration/testCampaignSaveData.cpp +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) -// -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "CampaignSaveData.h" -#include "Settings.h" -#include "lua/CampaignDataLoader.h" -#include "gameData/CampaignDescription.h" -#include "gameData/CampaignSaveCodes.h" -#include "rttr/test/TmpFolder.hpp" -#include -#include - -namespace { -constexpr auto uid = "uniqueID"; -struct CampaignSaveDataFixture -{ - CampaignSaveDataFixture() - { - desc.uid = uid; - saveData.clear(); - } - - CampaignDescription desc; - std::map& saveData = SETTINGS.campaigns.saveData; -}; -} // namespace - -BOOST_FIXTURE_TEST_CASE(isChapterEnabled_ReturnsFalse_WhenChapterIsDisabled, CampaignSaveDataFixture) -{ - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 0) == false); - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 1) == false); - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 2) == false); - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 9) == false); -} - -BOOST_FIXTURE_TEST_CASE(isChapterEnabled_ReturnsTrue_WhenChapterIsEnabled, CampaignSaveDataFixture) -{ - saveData[uid] = "011"; - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 0) == false); - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 1) == true); - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 2) == true); - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 9) == false); -} - -BOOST_FIXTURE_TEST_CASE(Chapters0and1AreEnabledByDefault, CampaignSaveDataFixture) -{ - saveData[uid] = CampaignSaveCodes::defaultChaptersEnabled; - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 0) == true); - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 1) == true); - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 2) == false); - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 9) == false); -} - -BOOST_FIXTURE_TEST_CASE(DefaultChaptersCanBeSetPerCampaign, CampaignSaveDataFixture) -{ - desc.defaultChaptersEnabled = "100"; - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 0) == true); - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 1) == false); - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 2) == false); - BOOST_TEST_REQUIRE(isChapterEnabled(desc, 9) == false); -} - -BOOST_FIXTURE_TEST_CASE( - getMissionsStatus_ReturnsPlayableForChaptersWhichAreEnabled_AndConqueredForChaptersWhichAreCompleted, - CampaignSaveDataFixture) -{ - rttr::test::TmpFolder tmp; - { - boost::nowide::ofstream file(tmp / "campaign.lua"); - - file << "campaign ={\ - version = \"1\",\ - uid = \"roman\",\ - author = \"Max Meier\",\ - name = \"Meine Kampagne\",\ - shortDescription = \"Sehr kurze Beschreibung\",\ - longDescription = \"Das ist die lange Beschreibung\",\ - image = \"/GFX/PICS/WORLD.LBM\",\ - maxHumanPlayers = 1,\ - difficulty = \"easy\",\ - mapFolder = \"/DATA/MAPS\",\ - luaFolder = \"/CAMPAIGNS/ROMAN\",\ - maps = { \"dessert0.WLD\", \"dessert1.WLD\", \"dessert2.WLD\"}\ - }"; - - file << "function getRequiredLuaVersion() return 1 end"; - } - - CampaignDescription dsc; - CampaignDataLoader loader{dsc, tmp}; - BOOST_TEST_REQUIRE(loader.Load()); - - saveData["roman"] = "210"; - const auto& ms = getMissionsStatus(dsc); - BOOST_TEST_REQUIRE(ms[0].playable == true); - BOOST_TEST_REQUIRE(ms[0].conquered == true); - BOOST_TEST_REQUIRE(ms[1].playable == true); - BOOST_TEST_REQUIRE(ms[1].conquered == false); - BOOST_TEST_REQUIRE(ms[2].playable == false); - BOOST_TEST_REQUIRE(ms[2].conquered == false); -} diff --git a/tests/s25Main/integration/testCampaignSettings.cpp b/tests/s25Main/integration/testCampaignSettings.cpp new file mode 100644 index 0000000000..d7b9852925 --- /dev/null +++ b/tests/s25Main/integration/testCampaignSettings.cpp @@ -0,0 +1,118 @@ +// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "CampaignSettings.h" +#include "Settings.h" +#include "lua/CampaignDataLoader.h" +#include "gameData/CampaignDescription.h" +#include "rttr/test/TmpFolder.hpp" +#include +#include + +namespace { +struct CampaignSettingsFixture +{ + CampaignDescription desc; + CampaignSettings sut; +}; +} // namespace + +BOOST_FIXTURE_TEST_CASE(CampaignSettingsIntegrationTest, CampaignSettingsFixture) +{ + constexpr auto cmpgn1 = "roman"; + constexpr auto cmpgn2 = "world"; + rttr::test::TmpFolder tmp; + { + boost::nowide::ofstream file(tmp / "campaign.lua"); + // read first campaign - chaptersEnabled not specified + file << R"(campaign = { + version = "1", + uid = "roman", + author = "Max Meier", + name = "My campaign", + shortDescription = "Very short description", + longDescription = "This is the long description", + image = "/GFX/PICS/WORLD.LBM", + maxHumanPlayers = 1, + difficulty = "easy", + mapFolder = "/DATA/MAPS", + luaFolder = "/CAMPAIGNS/ROMAN", + maps = { "dessert0.WLD", "dessert1.WLD", "dessert2.WLD"} + } + )"; + file << "function getRequiredLuaVersion() return 1 end"; + } + BOOST_TEST_REQUIRE((CampaignDataLoader{desc, tmp}.Load())); + + // chapters 1 and 2 are playable by default + BOOST_TEST_REQUIRE(sut.isChapterPlayable(desc, 0) == true); + BOOST_TEST_REQUIRE(sut.isChapterPlayable(desc, 1) == true); + BOOST_TEST_REQUIRE(sut.isChapterPlayable(desc, 2) == false); + BOOST_TEST_REQUIRE(sut.isChapterPlayable(desc, 9) == false); + + // complete ch2 and enable ch3 + sut.setChapterCompleted(cmpgn1, 1); + sut.enableChapter(cmpgn1, 2); + auto ms = sut.getMissionsStatus(desc); + BOOST_TEST_REQUIRE(ms[0].playable == true); + BOOST_TEST_REQUIRE(ms[0].conquered == false); + BOOST_TEST_REQUIRE(ms[1].playable == true); + BOOST_TEST_REQUIRE(ms[1].conquered == true); + BOOST_TEST_REQUIRE(ms[2].playable == true); + BOOST_TEST_REQUIRE(ms[2].conquered == false); + + // read save data - ch1 completed, ch2 enabled, ch3 disabled + sut.readSaveData(cmpgn1, "210"); + ms = sut.getMissionsStatus(desc); + BOOST_TEST_REQUIRE(ms[0].playable == true); + BOOST_TEST_REQUIRE(ms[0].conquered == true); + BOOST_TEST_REQUIRE(ms[1].playable == true); + BOOST_TEST_REQUIRE(ms[1].conquered == false); + BOOST_TEST_REQUIRE(ms[2].playable == false); + BOOST_TEST_REQUIRE(ms[2].conquered == false); + + // enable ch3 + sut.enableChapter(cmpgn1, 2); + // save code should now be 211 + decltype(sut.createSaveData()) expectedSaveData; + expectedSaveData[cmpgn1] = "211"; + BOOST_TEST_REQUIRE(sut.createSaveData() == expectedSaveData); + + // read and save data for campaign 2 + sut.readSaveData(cmpgn2, "21010"); + expectedSaveData[cmpgn2] = "21010"; + BOOST_TEST_REQUIRE(sut.createSaveData() == expectedSaveData); + + // read second campaign - chaptersEnabled specified + { + boost::nowide::ofstream file(tmp / "campaign.lua"); + file.clear(); + file << R"(campaign = { + version = "1", + uid = "world", + author = "Max Meier", + name = "My campaign", + shortDescription = "Very short description", + longDescription = "This is the long description", + image = "/GFX/PICS/WORLD.LBM", + maxHumanPlayers = 1, + difficulty = "easy", + mapFolder = "/DATA/MAPS", + luaFolder = "/CAMPAIGNS/ROMAN", + maps = { "dessert0.WLD", "dessert1.WLD", "dessert2.WLD"}, + chaptersEnabled = {1, 2, 4} + } + )"; + file << "function getRequiredLuaVersion() return 1 end"; + } + BOOST_TEST_REQUIRE((CampaignDataLoader{desc, tmp}.Load())); + + BOOST_TEST_REQUIRE(sut.isChapterPlayable(desc, 0) == true); + BOOST_TEST_REQUIRE(sut.isChapterPlayable(desc, 1) == true); + BOOST_TEST_REQUIRE(sut.isChapterPlayable(desc, 2) == false); + BOOST_TEST_REQUIRE(sut.isChapterPlayable(desc, 3) == true); + + sut.enableChapter(cmpgn2, 2); + BOOST_TEST_REQUIRE(sut.isChapterPlayable(desc, 2) == true); +} diff --git a/tests/s25Main/lua/testLua.cpp b/tests/s25Main/lua/testLua.cpp index 0c5e452f83..8b94ddb68a 100644 --- a/tests/s25Main/lua/testLua.cpp +++ b/tests/s25Main/lua/testLua.cpp @@ -844,21 +844,6 @@ BOOST_AUTO_TEST_CASE(onExplored) } } -BOOST_AUTO_TEST_CASE(CampaignStatusCanBeChangedFromLua) -{ - initWorld(); - - SETTINGS.campaigns.saveData["campaign_id"] = "110"; - executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 2)"); - executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 4)"); - executeLua("rttr:EnableCampaignChapter('campaign_id', 5)"); - executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 0)"); // noop - chapters start from 1 - executeLua("rttr:EnableCampaignChapter('campaign_id', 0)"); // noop - chapters start from 1 - executeLua("rttr:EnableCampaignChapter('campaign_id', 2)"); // noop - already completed - game.executeAICommands(); - BOOST_TEST_REQUIRE(SETTINGS.campaigns.saveData["campaign_id"] == "12021"); -} - BOOST_AUTO_TEST_CASE(LuaPacts) { initWorld(); @@ -921,4 +906,14 @@ BOOST_AUTO_TEST_CASE(LuaPacts) BOOST_TEST_REQUIRE(getLog() == "Pact created\n"); } +BOOST_AUTO_TEST_CASE(CampaignStatusCanBeChangedFromLua) +{ + SETTINGS.campaigns.readSaveData("campaign_id", "110"); + executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 1)"); + executeLua("rttr:SetCampaignChapterCompleted('campaign_id', 3)"); + executeLua("rttr:EnableCampaignChapter('campaign_id', 4)"); + executeLua("rttr:EnableCampaignChapter('campaign_id', 1)"); // noop - already completed + BOOST_TEST_REQUIRE(SETTINGS.campaigns.createSaveData()["campaign_id"] == "12021"); +} + BOOST_AUTO_TEST_SUITE_END() From 8977384b4c0372ba0abcc87a54de444a2fd6f4eb Mon Sep 17 00:00:00 2001 From: Jakub Audykowicz Date: Mon, 2 Feb 2026 18:55:18 +0100 Subject: [PATCH 6/6] Fix some after-merge issues --- data/RTTR/campaigns/world/campaign.lua | 1 + libs/s25main/desktops/dskCampaignVictory.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/data/RTTR/campaigns/world/campaign.lua b/data/RTTR/campaigns/world/campaign.lua index 8a2a8d1828..89a9538ee8 100644 --- a/data/RTTR/campaigns/world/campaign.lua +++ b/data/RTTR/campaigns/world/campaign.lua @@ -38,6 +38,7 @@ campaign = { mapFolder = "/DATA/MAPS2", luaFolder = "/CAMPAIGNS/WORLD", maps = {"EUROPE.WLD", "AFRICA.WLD", "NAMERICA.WLD", "SAMERICA.WLD", "GREEN.WLD", "AUSTRA.WLD", "NASIA.WLD", "SASIA.WLD", "JAPAN.WLD"}, + chaptersEnabled = {0}, selectionMap = { background = {"/GFX/PICS/SETUP990.LBM", 0}, map = {"/GFX/PICS/WORLD.LBM", 0}, diff --git a/libs/s25main/desktops/dskCampaignVictory.h b/libs/s25main/desktops/dskCampaignVictory.h index 9f2089427d..0183cb6314 100644 --- a/libs/s25main/desktops/dskCampaignVictory.h +++ b/libs/s25main/desktops/dskCampaignVictory.h @@ -6,7 +6,7 @@ #include "Desktop.h" -class MouseCoords; +struct MouseCoords; class dskCampaignVictory : public Desktop {