diff --git a/data/RTTR/campaigns/roman/MISS200.lua b/data/RTTR/campaigns/roman/MISS200.lua index 64ea3b4cb5..6dc926d3d7 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", 0) end -- update event state diff --git a/data/RTTR/campaigns/roman/MISS201.lua b/data/RTTR/campaigns/roman/MISS201.lua index 2f75fe0623..bcc2b4a6a1 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", 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 01a8a19c24..e45e907429 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", 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 727ac32db5..abf545db76 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", 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 1556ba7362..a20f0a4ded 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", 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 a6ec52edf1..4aea639bee 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,11 @@ 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", 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 7e878824db..9d961a797c 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", 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 24f66c450d..76c438a65b 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", 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 269f256d4f..df4f7e17b7 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", 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 f9cd271574..3ede0dbf57 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", 9) + 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..be0140fc6e 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"}, + chaptersEnabled = {1}, 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..8edb7b8d52 100644 --- a/libs/libGamedata/gameData/CampaignDescription.cpp +++ b/libs/libGamedata/gameData/CampaignDescription.cpp @@ -12,6 +12,7 @@ CampaignDescription::CampaignDescription(const boost::filesystem::path& campaignPath, const kaguya::LuaRef& table) { CheckedLuaTable luaData(table); + uid = luaData.getOrDefault("uid", decltype(uid){""}); luaData.getOrThrow(version, "version"); luaData.getOrThrow(author, "author"); luaData.getOrThrow(name, "name"); @@ -45,6 +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()); + // 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 7de41de861..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 @@ -15,6 +16,7 @@ class LuaRef; struct CampaignDescription { + std::string uid; std::string version; std::string author; std::string name; @@ -23,6 +25,7 @@ struct CampaignDescription std::optional image; unsigned maxHumanPlayers = 1; std::string difficulty; + std::vector chaptersEnabled; std::optional selectionMapData; CampaignDescription() = default; 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/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/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..7b29bb69f7 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,8 @@ bool GameManager::ShowMenu() if(LOBBYCLIENT.IsLoggedIn()) // Lobby zeigen windowManager_.Switch(std::make_unique()); + 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 499af15c08..35fd70198e 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 = CampaignSettings{}; + // } + 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.readSaveData(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.createSaveData()) + 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..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,11 +130,13 @@ class Settings : public Singleton std::map configuration; } addons; + CampaignSettings 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..2fad5173ec 100644 --- a/libs/s25main/desktops/dskCampaignMissionSelection.cpp +++ b/libs/s25main/desktops/dskCampaignMissionSelection.cpp @@ -4,6 +4,7 @@ #include "dskCampaignMissionSelection.h" #include "Loader.h" +#include "Settings.h" #include "WindowManager.h" #include "commonDefines.h" #include "controls/ctrlGroup.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(SETTINGS.campaigns.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(SETTINGS.campaigns.isChapterPlayable(*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..c3bd550891 --- /dev/null +++ b/libs/s25main/desktops/dskCampaignVictory.cpp @@ -0,0 +1,36 @@ +// 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 "Settings.h" +#include "WindowManager.h" +#include "desktops/dskMainMenu.h" + +dskCampaignVictory::dskCampaignVictory() + : Desktop(LOADER.GetImageN(ResourceId{SETTINGS.campaigns.getCompletedCampaign() ? "setup895" : "setup896"}, 0)) +{ + 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&) +{ + 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..9f2089427d --- /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(); + +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..ce574c36a4 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,6 +13,7 @@ #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" @@ -196,23 +198,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 +326,21 @@ void LuaInterfaceGame::PostMessageWithLocation(int playerIdx, const std::string& gw.MakeMapPoint(Position(x, y)))); } +void LuaInterfaceGame::EnableCampaignChapter(const CampaignID& campaignUid, ChapterID chapter) +{ + SETTINGS.campaigns.enableChapter(campaignUid, chapter); +} + +void LuaInterfaceGame::SetCampaignChapterCompleted(const CampaignID& campaignUid, ChapterID chapter) +{ + SETTINGS.campaigns.setChapterCompleted(campaignUid, chapter); +} + +void LuaInterfaceGame::SetCampaignCompleted(const CampaignID& campaignUid) +{ + SETTINGS.campaigns.setCampaignCompleted(campaignUid); +} + LuaPlayer LuaInterfaceGame::GetPlayer(int playerIdx) { lua::assertTrue(playerIdx >= 0 && static_cast(playerIdx) < gw.GetNumPlayers(), "Invalid player idx"); @@ -371,6 +391,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..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 @@ -33,6 +34,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 +47,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 +62,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 CampaignID& campaignUid, ChapterID chapter); + void SetCampaignChapterCompleted(const CampaignID& campaignUid, ChapterID chapter); + void SetCampaignCompleted(const CampaignID& campaignUid); + private: ILocalGameState& localGameState; GameWorld& gw; diff --git a/tests/libGameData/testCampaignLuaFile.cpp b/tests/libGameData/testCampaignLuaFile.cpp index 676ec64a5b..2efc3e6d22 100644 --- a/tests/libGameData/testCampaignLuaFile.cpp +++ b/tests/libGameData/testCampaignLuaFile.cpp @@ -116,6 +116,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 +126,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"}, + chaptersEnabled = {1, 3, 7} } )"; @@ -138,6 +140,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 +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.chaptersEnabled == (decltype(desc.chaptersEnabled){1, 3, 7})); // maps BOOST_TEST(desc.getNumMaps() == 3u); 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 34fe0d42fb..8b94ddb68a 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" @@ -905,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()