From 7625b14ae18196697951463a0dde1a60090dfd85 Mon Sep 17 00:00:00 2001 From: Jakub Audykowicz Date: Mon, 15 Jul 2024 12:21:08 +0200 Subject: [PATCH 1/5] Fix map "The Snake" --- data/RTTR/campaigns/roman/MISS206.lua | 8 +++++-- libs/s25main/GamePlayer.cpp | 20 ++++++++++++++++ libs/s25main/GamePlayer.h | 5 ++++ libs/s25main/lua/LuaPlayer.cpp | 23 ++++++++++++------ libs/s25main/lua/LuaPlayer.h | 2 ++ libs/s25main/network/GameClient.cpp | 4 ++-- libs/s25main/world/MapLoader.cpp | 7 ++++-- tests/s25Main/integration/testGamePlayer.cpp | 25 ++++++++++++++++++++ 8 files changed, 81 insertions(+), 13 deletions(-) diff --git a/data/RTTR/campaigns/roman/MISS206.lua b/data/RTTR/campaigns/roman/MISS206.lua index 7e878824db..d050eda176 100644 --- a/data/RTTR/campaigns/roman/MISS206.lua +++ b/data/RTTR/campaigns/roman/MISS206.lua @@ -189,8 +189,12 @@ function addPlayerBld(p, onLoad) rttr:GetPlayer(p):DisableBuilding(BLD_SHIPYARD, false) rttr:GetPlayer(p):DisableBuilding(BLD_HARBORBUILDING, false) - if(p == 2) then - if onLoad then return end + if onLoad then return end + + if(p == 1) then + rttr:GetPlayer(p):PlaceHQ(112, 82) -- !SET_HOUSE 24, 112, 82 + elseif(p == 2) then + rttr:GetPlayer(p):PlaceHQ(40, 54) -- !SET_HOUSE 24, 40, 54 rttr:GetPlayer(p):AIConstructionOrder(43, 59, BLD_FORTRESS) end end diff --git a/libs/s25main/GamePlayer.cpp b/libs/s25main/GamePlayer.cpp index d00de03b98..30f9db615a 100644 --- a/libs/s25main/GamePlayer.cpp +++ b/libs/s25main/GamePlayer.cpp @@ -14,6 +14,7 @@ #include "WineLoader.h" #include "addons/const_addons.h" #include "buildings/noBuildingSite.h" +#include "buildings/nobHQ.h" #include "buildings/nobHarborBuilding.h" #include "buildings/nobMilitary.h" #include "buildings/nobUsual.h" @@ -411,6 +412,19 @@ void GamePlayer::RemoveBuildingSite(noBuildingSite* bldSite) buildings.Remove(bldSite); } +bool GamePlayer::IsHQTent() const +{ + if(const nobHQ* hq = GetHQ()) + return hq->IsTent(); + return false; +} + +void GamePlayer::SetHQIsTent(bool isTent) +{ + if(nobHQ* hq = GetHQ()) + hq->SetIsTent(isTent); +} + void GamePlayer::AddBuilding(noBuilding* bld, BuildingType bldType) { RTTR_Assert(bld->GetPlayer() == GetPlayerId()); @@ -1400,6 +1414,12 @@ void GamePlayer::TestDefeat() Surrender(); } +nobHQ* GamePlayer::GetHQ() const +{ + const MapPoint& hqPos = GetHQPos(); + return const_cast(hqPos.isValid() ? GetGameWorld().GetSpecObj(hqPos) : nullptr); +} + void GamePlayer::Surrender() { if(isDefeated) diff --git a/libs/s25main/GamePlayer.h b/libs/s25main/GamePlayer.h index 59ee843473..5554c613a2 100644 --- a/libs/s25main/GamePlayer.h +++ b/libs/s25main/GamePlayer.h @@ -31,6 +31,7 @@ class noShip; class nobBaseMilitary; class nobBaseWarehouse; class nobHarborBuilding; +class nobHQ; class nobMilitary; class nofCarrier; class nofFlagWorker; @@ -91,6 +92,9 @@ class GamePlayer : public GamePlayerInfo const GameWorld& GetGameWorld() const { return world; } const MapPoint& GetHQPos() const { return hqPos; } + bool IsHQTent() const; + void SetHQIsTent(bool isTent); + void AddBuilding(noBuilding* bld, BuildingType bldType); void RemoveBuilding(noBuilding* bld, BuildingType bldType); void AddBuildingSite(noBuildingSite* bldSite); @@ -426,6 +430,7 @@ class GamePlayer : public GamePlayerInfo bool FindWarehouseForJob(Job job, noRoadNode* goal) const; /// Prüft, ob der Spieler besiegt wurde void TestDefeat(); + nobHQ* GetHQ() const; ////////////////////////////////////////////////////////////////////////// /// Unsynchronized state (e.g. lua, gui...) diff --git a/libs/s25main/lua/LuaPlayer.cpp b/libs/s25main/lua/LuaPlayer.cpp index 3dd5158883..7984ca2ee7 100644 --- a/libs/s25main/lua/LuaPlayer.cpp +++ b/libs/s25main/lua/LuaPlayer.cpp @@ -9,6 +9,7 @@ #include "ai/AIPlayer.h" #include "buildings/nobBaseWarehouse.h" #include "buildings/nobHQ.h" +#include "factories/BuildingFactory.h" #include "helpers/EnumRange.h" #include "helpers/toString.h" #include "lua/LuaHelpers.h" @@ -46,6 +47,7 @@ void LuaPlayer::Register(kaguya::State& state) .addFunction("GetNumPeople", &LuaPlayer::GetNumPeople) .addFunction("GetStatisticsValue", &LuaPlayer::GetStatisticsValue) .addFunction("AIConstructionOrder", &LuaPlayer::AIConstructionOrder) + .addFunction("PlaceHQ", &LuaPlayer::PlaceHQ) .addFunction("ModifyHQ", &LuaPlayer::ModifyHQ) .addFunction("GetHQPos", &LuaPlayer::GetHQPos) .addFunction("IsDefeated", &LuaPlayer::IsDefeated) @@ -259,15 +261,22 @@ bool LuaPlayer::AIConstructionOrder(unsigned x, unsigned y, lua::SafeEnum(hqPos); - if(hq) - hq->SetIsTent(isTent); - } + player.SetHQIsTent(isTent); } bool LuaPlayer::IsDefeated() const diff --git a/libs/s25main/lua/LuaPlayer.h b/libs/s25main/lua/LuaPlayer.h index 3335084a51..f15aa6bf78 100644 --- a/libs/s25main/lua/LuaPlayer.h +++ b/libs/s25main/lua/LuaPlayer.h @@ -10,6 +10,7 @@ #include "gameTypes/BuildingType.h" #include "gameTypes/GoodTypes.h" #include "gameTypes/JobTypes.h" +#include "gameTypes/MapCoordinates.h" #include "gameTypes/PactTypes.h" #include "gameTypes/StatisticTypes.h" #include @@ -50,6 +51,7 @@ class LuaPlayer : public LuaPlayerBase unsigned GetNumPeople(lua::SafeEnum job) const; unsigned GetStatisticsValue(lua::SafeEnum stat) const; bool AIConstructionOrder(unsigned x, unsigned y, lua::SafeEnum bld); + void PlaceHQ(MapCoord x, MapCoord y); void ModifyHQ(bool isTent); bool IsDefeated() const; void Surrender(bool destroyBlds); diff --git a/libs/s25main/network/GameClient.cpp b/libs/s25main/network/GameClient.cpp index f6bf470891..0c830e4b5b 100644 --- a/libs/s25main/network/GameClient.cpp +++ b/libs/s25main/network/GameClient.cpp @@ -323,8 +323,8 @@ void GameClient::StartGame(const unsigned random_init) gameWorld.GetPlayer(i).MakeStartPacts(); MapLoader loader(gameWorld); - if(!loader.Load(mapinfo.filepath) - || (!mapinfo.luaFilepath.empty() && !loader.LoadLuaScript(*game, *this, mapinfo.luaFilepath))) + if((!mapinfo.luaFilepath.empty() && !loader.LoadLuaScript(*game, *this, mapinfo.luaFilepath)) + || !loader.Load(mapinfo.filepath)) // do not reorder: load lua first, load map second { OnError(ClientError::InvalidMap); return; diff --git a/libs/s25main/world/MapLoader.cpp b/libs/s25main/world/MapLoader.cpp index b3b60b0dcb..40a40ae241 100644 --- a/libs/s25main/world/MapLoader.cpp +++ b/libs/s25main/world/MapLoader.cpp @@ -409,8 +409,11 @@ bool MapLoader::PlaceHQs(GameWorldBase& world, std::vector hqPositions // Does the HQ have a position? if(i >= hqPositions.size() || !hqPositions[i].isValid()) { - LOG.write(_("Player %u does not have a valid start position!")) % i; - return false; + LOG.write(_("Player %u does not have a valid start position!\n")) % i; + if(world.HasLua()) // maybe the HQ is placed in the script? + continue; + else + return false; } BuildingFactory::CreateBuilding(world, BuildingType::Headquarters, hqPositions[i], i, diff --git a/tests/s25Main/integration/testGamePlayer.cpp b/tests/s25Main/integration/testGamePlayer.cpp index 438e0131fe..4fb8c4af11 100644 --- a/tests/s25Main/integration/testGamePlayer.cpp +++ b/tests/s25Main/integration/testGamePlayer.cpp @@ -119,3 +119,28 @@ BOOST_FIXTURE_TEST_CASE(ProductivityStats, WorldFixtureEmpty1P) BOOST_TEST(buildingRegister.CalcProductivities() == expectedProductivity, per_element()); BOOST_TEST(buildingRegister.CalcAverageProductivity() == avgProd); } + +BOOST_FIXTURE_TEST_CASE(IsHQTent_ReturnsFalse_IfPrimaryHQIsNotTent, WorldFixtureEmpty1P) +{ + GamePlayer& p1 = world.GetPlayer(0); + + // place another HQ that is a tent + MapPoint newHqPos = p1.GetHQPos(); + newHqPos.x += 3; + BuildingFactory::CreateBuilding(world, BuildingType::Headquarters, newHqPos, 0, Nation::Babylonians, true); + + BOOST_TEST_REQUIRE(p1.IsHQTent() == false); +} + +BOOST_FIXTURE_TEST_CASE(IsHQTent_ReturnsTrue_IfPrimaryHQIsTent, WorldFixtureEmpty1P) +{ + GamePlayer& p1 = world.GetPlayer(0); + p1.SetHQIsTent(true); + + // place another HQ that is not a tent + MapPoint newHqPos = p1.GetHQPos(); + newHqPos.x += 3; + BuildingFactory::CreateBuilding(world, BuildingType::Headquarters, newHqPos, 0, Nation::Babylonians, false); + + BOOST_TEST_REQUIRE(p1.IsHQTent() == true); +} From d2e885c17eae9f6876bf6b6e3cf082f675aaf679 Mon Sep 17 00:00:00 2001 From: Jakub Audykowicz Date: Thu, 18 Jul 2024 23:39:28 +0200 Subject: [PATCH 2/5] Enabling setting number of players from LUA --- data/RTTR/campaigns/roman/MISS200.lua | 11 ++++------- libs/s25main/GameLobby.cpp | 5 +++++ libs/s25main/GameLobby.h | 1 + libs/s25main/desktops/dskGameLobby.cpp | 8 ++++++++ libs/s25main/lua/LuaInterfaceSettings.cpp | 10 ++++++++++ libs/s25main/lua/LuaInterfaceSettings.h | 1 + libs/s25main/network/GameServer.cpp | 5 +++++ libs/s25main/network/GameServer.h | 2 ++ 8 files changed, 36 insertions(+), 7 deletions(-) diff --git a/data/RTTR/campaigns/roman/MISS200.lua b/data/RTTR/campaigns/roman/MISS200.lua index 64ea3b4cb5..737ca24235 100644 --- a/data/RTTR/campaigns/roman/MISS200.lua +++ b/data/RTTR/campaigns/roman/MISS200.lua @@ -175,6 +175,10 @@ rttr:RegisterTranslations( }, }) +function getNumPlayers() + return 1 +end + -- format mission texts -- BUG: NewLine at the end is wrongly interpreted, adding 2x Space resolves this issue function MissionText(e) @@ -205,13 +209,6 @@ function onSettingsReady() rttr:GetPlayer(0):SetNation(NAT_ROMANS) -- nation rttr:GetPlayer(0):SetColor(0) -- 0:blue, 1:red, 2:yellow, - - rttr:GetPlayer(1):Close() - rttr:GetPlayer(2):Close() - rttr:GetPlayer(3):Close() - rttr:GetPlayer(4):Close() - rttr:GetPlayer(5):Close() - rttr:GetPlayer(6):Close() end function getAllowedChanges() diff --git a/libs/s25main/GameLobby.cpp b/libs/s25main/GameLobby.cpp index e1f3496b5b..11be3ae5dc 100644 --- a/libs/s25main/GameLobby.cpp +++ b/libs/s25main/GameLobby.cpp @@ -24,3 +24,8 @@ unsigned GameLobby::getNumPlayers() const { return players_.size(); } + +void GameLobby::setNumPlayers(unsigned num) +{ + players_.resize(num); +} diff --git a/libs/s25main/GameLobby.h b/libs/s25main/GameLobby.h index 171152d721..d97cf01e67 100644 --- a/libs/s25main/GameLobby.h +++ b/libs/s25main/GameLobby.h @@ -21,6 +21,7 @@ class GameLobby const JoinPlayerInfo& getPlayer(unsigned playerId) const; const std::vector& getPlayers() const { return players_; } unsigned getNumPlayers() const; + void setNumPlayers(unsigned num); GlobalGameSettings& getSettings() { return ggs_; } const GlobalGameSettings& getSettings() const { return ggs_; } diff --git a/libs/s25main/desktops/dskGameLobby.cpp b/libs/s25main/desktops/dskGameLobby.cpp index 10ac875659..bd3392a9cf 100644 --- a/libs/s25main/desktops/dskGameLobby.cpp +++ b/libs/s25main/desktops/dskGameLobby.cpp @@ -32,6 +32,7 @@ #include "ingameWindows/iwMsgbox.h" #include "lua/LuaInterfaceSettings.h" #include "network/GameClient.h" +#include "network/GameServer.h" #include "ogl/FontStyle.h" #include "gameData/GameConsts.h" #include "gameData/const_gui_ids.h" @@ -102,6 +103,13 @@ dskGameLobby::dskGameLobby(ServerType serverType, std::shared_ptr gam RTTR_Assert(gameLobby_->isHost()); LOG.write(_("Lua was disabled by the script itself\n")); lua.reset(); + } else + { + if(const auto num = lua->GetNumPlayersFromScript()) + { + GAMESERVER.SetNumPlayers(num); + gameLobby_->setNumPlayers(num); + } } if(!lua && gameLobby_->isHost()) lobbyController->RemoveLuaScript(); diff --git a/libs/s25main/lua/LuaInterfaceSettings.cpp b/libs/s25main/lua/LuaInterfaceSettings.cpp index 8f29d5658f..5b24778a53 100644 --- a/libs/s25main/lua/LuaInterfaceSettings.cpp +++ b/libs/s25main/lua/LuaInterfaceSettings.cpp @@ -247,3 +247,13 @@ bool LuaInterfaceSettings::IsMapPreviewEnabled() } return true; } + +unsigned LuaInterfaceSettings::GetNumPlayersFromScript() +{ + kaguya::LuaRef func = lua["getNumPlayers"]; + if(func.type() == LUA_TFUNCTION) + { + return func.call(); + } + return 0; +} diff --git a/libs/s25main/lua/LuaInterfaceSettings.h b/libs/s25main/lua/LuaInterfaceSettings.h index a577a3e1ed..931f50c91e 100644 --- a/libs/s25main/lua/LuaInterfaceSettings.h +++ b/libs/s25main/lua/LuaInterfaceSettings.h @@ -36,6 +36,7 @@ class LuaInterfaceSettings : public LuaInterfaceGameBase /// Get addons that are allowed to be changed std::vector GetAllowedAddons(); bool IsMapPreviewEnabled(); + unsigned GetNumPlayersFromScript(); private: IGameLobbyController& lobbyServerController_; diff --git a/libs/s25main/network/GameServer.cpp b/libs/s25main/network/GameServer.cpp index 4605f8a1c9..67dbea9263 100644 --- a/libs/s25main/network/GameServer.cpp +++ b/libs/s25main/network/GameServer.cpp @@ -526,6 +526,11 @@ bool GameServer::assignPlayersOfRandomTeams(std::vector& playerI return playerWasAssigned; } +void GameServer::SetNumPlayers(unsigned num) +{ + playerInfos.resize(num); +} + /** * startet das Spiel. */ diff --git a/libs/s25main/network/GameServer.h b/libs/s25main/network/GameServer.h index d4e921a7fd..cf8d3ea735 100644 --- a/libs/s25main/network/GameServer.h +++ b/libs/s25main/network/GameServer.h @@ -52,6 +52,8 @@ class GameServer : /// Assign players that do not have a fixed team, return true if any player was assigned. static bool assignPlayersOfRandomTeams(std::vector& playerInfos); + void SetNumPlayers(unsigned num); + private: bool StartGame(); From dbd105db3d0f9e0d95e1e8fd1996fed021614398 Mon Sep 17 00:00:00 2001 From: Jakub Audykowicz Date: Mon, 5 Aug 2024 18:48:15 +0200 Subject: [PATCH 3/5] Apply review remarks: - document PlaceHQ - document why LUA should be loaded before the map --- doc/lua/functions.md | 4 ++++ libs/s25main/network/GameClient.cpp | 4 +++- libs/s25main/world/MapLoader.cpp | 4 +--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/lua/functions.md b/doc/lua/functions.md index e142d2eb26..b7b65ba3ec 100644 --- a/doc/lua/functions.md +++ b/doc/lua/functions.md @@ -343,6 +343,10 @@ Ignored if the current player is not controlled by AI. **GetHQPos()** Return x,y of the players HQ +**PlaceHQ(x, y)** +Places this player's headquarters at map location {x, y}. +Does nothing if the player already has a valid headquarters position (e.g. set by the map or using a previous PlaceHQ call). + **Surrender(destroyBuildings)** Let the player give up either keeping its buildings or destroying them. Can be called multiple times e.g. to destroy buildings later. diff --git a/libs/s25main/network/GameClient.cpp b/libs/s25main/network/GameClient.cpp index 0c830e4b5b..504c086dce 100644 --- a/libs/s25main/network/GameClient.cpp +++ b/libs/s25main/network/GameClient.cpp @@ -324,7 +324,9 @@ void GameClient::StartGame(const unsigned random_init) MapLoader loader(gameWorld); if((!mapinfo.luaFilepath.empty() && !loader.LoadLuaScript(*game, *this, mapinfo.luaFilepath)) - || !loader.Load(mapinfo.filepath)) // do not reorder: load lua first, load map second + || !loader.Load(mapinfo.filepath)) // Do not reorder: load lua first, load map second. + // If the map is loaded first and it does not have a player HQ set, it + // will not load correctly, even though the HQ may be set using LUA. { OnError(ClientError::InvalidMap); return; diff --git a/libs/s25main/world/MapLoader.cpp b/libs/s25main/world/MapLoader.cpp index 40a40ae241..746f2a42d4 100644 --- a/libs/s25main/world/MapLoader.cpp +++ b/libs/s25main/world/MapLoader.cpp @@ -410,9 +410,7 @@ bool MapLoader::PlaceHQs(GameWorldBase& world, std::vector hqPositions if(i >= hqPositions.size() || !hqPositions[i].isValid()) { LOG.write(_("Player %u does not have a valid start position!\n")) % i; - if(world.HasLua()) // maybe the HQ is placed in the script? - continue; - else + if(!world.HasLua()) // HQ can be placed in the script, so don't signal error return false; } From 47b869fa1b1053b0d0be289284371c62c6ee1f5e Mon Sep 17 00:00:00 2001 From: Jakub Audykowicz Date: Mon, 5 Aug 2024 18:53:09 +0200 Subject: [PATCH 4/5] Apply review remark: don't const_cast inside GamePlayer::GetHQ --- libs/s25main/GamePlayer.cpp | 6 +++--- libs/s25main/GamePlayer.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/s25main/GamePlayer.cpp b/libs/s25main/GamePlayer.cpp index 30f9db615a..39fb077a05 100644 --- a/libs/s25main/GamePlayer.cpp +++ b/libs/s25main/GamePlayer.cpp @@ -421,7 +421,7 @@ bool GamePlayer::IsHQTent() const void GamePlayer::SetHQIsTent(bool isTent) { - if(nobHQ* hq = GetHQ()) + if(nobHQ* hq = const_cast(GetHQ())) hq->SetIsTent(isTent); } @@ -1414,10 +1414,10 @@ void GamePlayer::TestDefeat() Surrender(); } -nobHQ* GamePlayer::GetHQ() const +const nobHQ* GamePlayer::GetHQ() const { const MapPoint& hqPos = GetHQPos(); - return const_cast(hqPos.isValid() ? GetGameWorld().GetSpecObj(hqPos) : nullptr); + return hqPos.isValid() ? GetGameWorld().GetSpecObj(hqPos) : nullptr; } void GamePlayer::Surrender() diff --git a/libs/s25main/GamePlayer.h b/libs/s25main/GamePlayer.h index 5554c613a2..3f71b505bd 100644 --- a/libs/s25main/GamePlayer.h +++ b/libs/s25main/GamePlayer.h @@ -430,7 +430,7 @@ class GamePlayer : public GamePlayerInfo bool FindWarehouseForJob(Job job, noRoadNode* goal) const; /// Prüft, ob der Spieler besiegt wurde void TestDefeat(); - nobHQ* GetHQ() const; + const nobHQ* GetHQ() const; ////////////////////////////////////////////////////////////////////////// /// Unsynchronized state (e.g. lua, gui...) From 9ea584aa0340cedca50c27a5a20891e45afd4b82 Mon Sep 17 00:00:00 2001 From: Jakub Audykowicz Date: Mon, 5 Aug 2024 18:59:38 +0200 Subject: [PATCH 5/5] Remove IsHQTent tests (require features from another branch) --- tests/s25Main/integration/testGamePlayer.cpp | 25 -------------------- 1 file changed, 25 deletions(-) diff --git a/tests/s25Main/integration/testGamePlayer.cpp b/tests/s25Main/integration/testGamePlayer.cpp index 4fb8c4af11..438e0131fe 100644 --- a/tests/s25Main/integration/testGamePlayer.cpp +++ b/tests/s25Main/integration/testGamePlayer.cpp @@ -119,28 +119,3 @@ BOOST_FIXTURE_TEST_CASE(ProductivityStats, WorldFixtureEmpty1P) BOOST_TEST(buildingRegister.CalcProductivities() == expectedProductivity, per_element()); BOOST_TEST(buildingRegister.CalcAverageProductivity() == avgProd); } - -BOOST_FIXTURE_TEST_CASE(IsHQTent_ReturnsFalse_IfPrimaryHQIsNotTent, WorldFixtureEmpty1P) -{ - GamePlayer& p1 = world.GetPlayer(0); - - // place another HQ that is a tent - MapPoint newHqPos = p1.GetHQPos(); - newHqPos.x += 3; - BuildingFactory::CreateBuilding(world, BuildingType::Headquarters, newHqPos, 0, Nation::Babylonians, true); - - BOOST_TEST_REQUIRE(p1.IsHQTent() == false); -} - -BOOST_FIXTURE_TEST_CASE(IsHQTent_ReturnsTrue_IfPrimaryHQIsTent, WorldFixtureEmpty1P) -{ - GamePlayer& p1 = world.GetPlayer(0); - p1.SetHQIsTent(true); - - // place another HQ that is not a tent - MapPoint newHqPos = p1.GetHQPos(); - newHqPos.x += 3; - BuildingFactory::CreateBuilding(world, BuildingType::Headquarters, newHqPos, 0, Nation::Babylonians, false); - - BOOST_TEST_REQUIRE(p1.IsHQTent() == true); -}