From 11cb7c066e33c2b199d6e18fbbd3a9f9aca628f9 Mon Sep 17 00:00:00 2001 From: Jakub Audykowicz Date: Fri, 13 Dec 2024 22:50:46 +0100 Subject: [PATCH 1/2] Fix map "The Snake" # Conflicts: # data/RTTR/campaigns/roman/MISS206.lua --- data/RTTR/campaigns/roman/MISS200.lua | 2 - data/RTTR/campaigns/roman/MISS201.lua | 8 +- data/RTTR/campaigns/roman/MISS202.lua | 12 +- data/RTTR/campaigns/roman/MISS203.lua | 12 +- data/RTTR/campaigns/roman/MISS204.lua | 12 +- data/RTTR/campaigns/roman/MISS205.lua | 7 +- data/RTTR/campaigns/roman/MISS206.lua | 12 +- data/RTTR/campaigns/roman/MISS207.lua | 11 +- data/RTTR/campaigns/roman/MISS208.lua | 10 +- data/RTTR/campaigns/roman/MISS209.lua | 11 +- doc/lua/functions.md | 28 ++- libs/s25main/GamePlayer.cpp | 55 +++++- libs/s25main/GamePlayer.h | 10 +- libs/s25main/ai/AIInterface.h | 4 +- libs/s25main/ai/aijh/AIPlayerJH.cpp | 6 +- libs/s25main/buildings/nobBaseMilitary.cpp | 4 +- libs/s25main/buildings/nobBaseMilitary.h | 2 +- libs/s25main/buildings/nobMilitary.cpp | 12 +- libs/s25main/buildings/nobMilitary.h | 2 +- libs/s25main/buildings/nobStorehouse.h | 2 +- libs/s25main/figures/nofAttacker.cpp | 2 +- libs/s25main/figures/nofCatapultMan.cpp | 2 +- libs/s25main/gameTypes/GameTypesOutput.h | 2 +- libs/s25main/gameTypes/PactTypes.cpp | 4 +- libs/s25main/gameTypes/PactTypes.h | 5 +- libs/s25main/ingameWindows/iwDiplomacy.cpp | 1 + libs/s25main/lua/LuaInterfaceGame.cpp | 1 + libs/s25main/lua/LuaPlayer.cpp | 12 +- libs/s25main/lua/LuaPlayer.h | 3 +- libs/s25main/world/GameWorld.cpp | 22 ++- libs/s25main/world/GameWorldBase.cpp | 6 +- libs/s25main/world/GameWorldViewer.cpp | 2 +- tests/s25Main/integration/testPacts.cpp | 200 ++++++++++++++++++++- tests/s25Main/lua/testLua.cpp | 33 +++- 34 files changed, 422 insertions(+), 95 deletions(-) diff --git a/data/RTTR/campaigns/roman/MISS200.lua b/data/RTTR/campaigns/roman/MISS200.lua index 64ea3b4cb5..b0e22f0241 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 ------------------------------------------------------------------------------- diff --git a/data/RTTR/campaigns/roman/MISS201.lua b/data/RTTR/campaigns/roman/MISS201.lua index 2f75fe0623..02d06accf2 100644 --- a/data/RTTR/campaigns/roman/MISS201.lua +++ b/data/RTTR/campaigns/roman/MISS201.lua @@ -1,14 +1,13 @@ ------------------------------------------------------------------------------ -- LUA-Script for MISS201.WLD (mission 2 of the original "Roman Campaign" -- -- -- --- Authors: CrazyL, Spikeone, ArthurMurray47 -- +-- Authors: CrazyL, Spikeone, ArthurMurray47, kubaau -- ------------------------------------------------------------------------------ -------------------------------- TODO ----------------------------------------- -- EnableNextMissions() -- Set Portraits --- Set AI Agression Level -- RttR: AI doesn't go south ------------------------------------------------------------------------------- @@ -168,7 +167,6 @@ function onSettingsReady() rttr:GetPlayer(1):SetNation(NAT_AFRICANS) -- nation rttr:GetPlayer(1):SetColor(1) -- yellow rttr:GetPlayer(1):SetName('Shaka') -- Enemy Name - rttr:GetPlayer(1):SetTeam(TM_TEAM1) end function getAllowedChanges() @@ -185,6 +183,10 @@ end -- start callback function onStart(isFirstStart) + if isFirstStart then + rttr:GetPlayer(1):MakeOneSidedAllianceTo(0) -- !GLOBAL_SET_COMPUTER_ALLIANCE 1 0 + end + for i = 0, 1 do -- set resources addPlayerRes(i, not isFirstStart) addPlayerBld(i, not isFirstStart) diff --git a/data/RTTR/campaigns/roman/MISS202.lua b/data/RTTR/campaigns/roman/MISS202.lua index 01a8a19c24..d312eddd61 100644 --- a/data/RTTR/campaigns/roman/MISS202.lua +++ b/data/RTTR/campaigns/roman/MISS202.lua @@ -1,14 +1,13 @@ ------------------------------------------------------------------------------ -- LUA-Script for MISS202.WLD (mission 3 of the original "Roman Campaign" -- -- -- --- Authors: CrazyL, Spikeone, ArthurMurray47 -- +-- Authors: CrazyL, Spikeone, ArthurMurray47, kubaau -- ------------------------------------------------------------------------------ -------------------------------- TODO ----------------------------------------- -- EnableNextMissions() -- Set Portraits --- Set AI Agression Level ------------------------------------------------------------------------------- @@ -120,13 +119,11 @@ function onSettingsReady() rttr:GetPlayer(1):SetNation(NAT_AFRICANS) -- nation rttr:GetPlayer(1):SetColor(1) -- yellow rttr:GetPlayer(1):SetName('Mnga Tscha') -- Enemy Name - rttr:GetPlayer(1):SetTeam(TM_TEAM1) rttr:GetPlayer(2):SetAI(3) -- hard AI rttr:GetPlayer(2):SetNation(NAT_AFRICANS) -- nation rttr:GetPlayer(2):SetColor(2) -- red rttr:GetPlayer(2):SetName('Todo') -- Enemy Name - rttr:GetPlayer(2):SetTeam(TM_TEAM1) end function getAllowedChanges() @@ -143,6 +140,13 @@ end -- start callback function onStart(isFirstStart) + if isFirstStart then + rttr:GetPlayer(2):MakeOneSidedAllianceTo(1) -- !GLOBAL_SET_COMPUTER_ALLIANCE 2 1 + rttr:GetPlayer(1):MakeOneSidedAllianceTo(2) -- !GLOBAL_SET_COMPUTER_ALLIANCE 1 2 + rttr:GetPlayer(1):MakeOneSidedAllianceTo(0) -- !GLOBAL_SET_COMPUTER_ALLIANCE 1 0 + rttr:GetPlayer(2):MakeOneSidedAllianceTo(0) -- !GLOBAL_SET_COMPUTER_ALLIANCE 2 0 + end + for i = 0, 2 do -- set resources addPlayerRes(i, not isFirstStart) addPlayerBld(i, not isFirstStart) diff --git a/data/RTTR/campaigns/roman/MISS203.lua b/data/RTTR/campaigns/roman/MISS203.lua index 727ac32db5..0d883f98d5 100644 --- a/data/RTTR/campaigns/roman/MISS203.lua +++ b/data/RTTR/campaigns/roman/MISS203.lua @@ -1,14 +1,13 @@ ------------------------------------------------------------------------------ -- LUA-Script for MISS203.WLD (mission 4 of the original "Roman Campaign") -- -- -- --- Authors: CrazyL, Spikeone, ArthurMurray47 -- +-- Authors: CrazyL, Spikeone, ArthurMurray47, kubaau -- ------------------------------------------------------------------------------ -------------------------------- TODO ----------------------------------------- -- EnableNextMissions() -- Set Portraits --- Set AI Agression Level ------------------------------------------------------------------------------- @@ -119,13 +118,11 @@ function onSettingsReady() rttr:GetPlayer(1):SetNation(NAT_VIKINGS) -- nation rttr:GetPlayer(1):SetColor(1) -- yellow rttr:GetPlayer(1):SetName('Erik') -- Enemy Name - rttr:GetPlayer(1):SetTeam(TM_TEAM1) rttr:GetPlayer(2):SetAI(3) -- hard AI rttr:GetPlayer(2):SetNation(NAT_VIKINGS) -- nation rttr:GetPlayer(2):SetColor(2) -- red rttr:GetPlayer(2):SetName('Knut') -- Enemy Name - rttr:GetPlayer(2):SetTeam(TM_TEAM1) end function getAllowedChanges() @@ -142,6 +139,13 @@ end -- start callback function onStart(isFirstStart) + if isFirstStart then + rttr:GetPlayer(2):MakeOneSidedAllianceTo(1) -- !GLOBAL_SET_COMPUTER_ALLIANCE 2 1 + rttr:GetPlayer(1):MakeOneSidedAllianceTo(2) -- !GLOBAL_SET_COMPUTER_ALLIANCE 1 2 + rttr:GetPlayer(1):MakeOneSidedAllianceTo(0) -- !GLOBAL_SET_COMPUTER_ALLIANCE 1 0 + rttr:GetPlayer(2):MakeOneSidedAllianceTo(0) -- !GLOBAL_SET_COMPUTER_ALLIANCE 2 0 + end + for i = 0, 2 do -- set resources and buildings addPlayerRes(i, not isFirstStart) addPlayerBld(i, not isFirstStart) diff --git a/data/RTTR/campaigns/roman/MISS204.lua b/data/RTTR/campaigns/roman/MISS204.lua index 1556ba7362..3f1da9b496 100644 --- a/data/RTTR/campaigns/roman/MISS204.lua +++ b/data/RTTR/campaigns/roman/MISS204.lua @@ -1,14 +1,13 @@ ------------------------------------------------------------------------------ -- LUA-Script for MISS204.WLD (mission 5 of the original "Roman Campaign") -- -- -- --- Authors: CrazyL, Spikeone, ArthurMurray47 -- +-- Authors: CrazyL, Spikeone, ArthurMurray47, kubaau -- ------------------------------------------------------------------------------ -------------------------------- TODO ----------------------------------------- -- EnableNextMissions() -- Set Portraits --- Set AI Agression Level ------------------------------------------------------------------------------- @@ -113,13 +112,11 @@ function onSettingsReady() rttr:GetPlayer(1):SetNation(NAT_JAPANESE) -- nation rttr:GetPlayer(1):SetColor(1) -- yellow rttr:GetPlayer(1):SetName('Hakirawashi') -- Enemy Name - rttr:GetPlayer(1):SetTeam(TM_TEAM1) rttr:GetPlayer(2):SetAI(3) -- hard AI rttr:GetPlayer(2):SetNation(NAT_JAPANESE) -- nation rttr:GetPlayer(2):SetColor(2) -- red rttr:GetPlayer(2):SetName('Tsunami') -- Enemy Name - rttr:GetPlayer(2):SetTeam(TM_TEAM1) end function getAllowedChanges() @@ -136,6 +133,13 @@ end -- start callback function onStart(isFirstStart) + if isFirstStart then + rttr:GetPlayer(2):MakeOneSidedAllianceTo(1) -- !GLOBAL_SET_COMPUTER_ALLIANCE 2 1 + rttr:GetPlayer(1):MakeOneSidedAllianceTo(2) -- !GLOBAL_SET_COMPUTER_ALLIANCE 1 2 + rttr:GetPlayer(1):MakeOneSidedAllianceTo(0) -- !GLOBAL_SET_COMPUTER_ALLIANCE 1 0 + rttr:GetPlayer(2):MakeOneSidedAllianceTo(0) -- !GLOBAL_SET_COMPUTER_ALLIANCE 2 0 + end + for i = 0, 2 do -- set resources addPlayerRes(i, not isFirstStart) addPlayerBld(i, not isFirstStart) diff --git a/data/RTTR/campaigns/roman/MISS205.lua b/data/RTTR/campaigns/roman/MISS205.lua index a6ec52edf1..dfb859b3ad 100644 --- a/data/RTTR/campaigns/roman/MISS205.lua +++ b/data/RTTR/campaigns/roman/MISS205.lua @@ -1,14 +1,13 @@ ------------------------------------------------------------------------------ -- LUA-Script for MISS205.WLD (mission 6 of the original "Roman Campaign" -- -- -- --- Authors: CrazyL, Spikeone, ArthurMurray47 -- +-- Authors: CrazyL, Spikeone, ArthurMurray47, kubaau -- ------------------------------------------------------------------------------ -------------------------------- TODO ----------------------------------------- -- EnableNextMissions() -- Set Portraits --- Set AI Agression Level ------------------------------------------------------------------------------- @@ -113,13 +112,11 @@ function onSettingsReady() rttr:GetPlayer(1):SetNation(NAT_VIKINGS) -- nation rttr:GetPlayer(1):SetColor(1) -- yellow rttr:GetPlayer(1):SetName('Erik') -- Enemy Name - rttr:GetPlayer(1):SetTeam(TM_TEAM1) rttr:GetPlayer(2):SetAI(3) -- hard AI rttr:GetPlayer(2):SetNation(NAT_VIKINGS) -- nation rttr:GetPlayer(2):SetColor(2) -- red rttr:GetPlayer(2):SetName('Olof') -- Enemy Name - rttr:GetPlayer(2):SetTeam(TM_TEAM1) end function getAllowedChanges() @@ -136,6 +133,8 @@ end -- start callback function onStart(isFirstStart) + -- no alliances in this mission + for i = 0, 2 do -- set resources addPlayerRes(i, not isFirstStart) addPlayerBld(i, not isFirstStart) diff --git a/data/RTTR/campaigns/roman/MISS206.lua b/data/RTTR/campaigns/roman/MISS206.lua index 7e878824db..34b491ca6f 100644 --- a/data/RTTR/campaigns/roman/MISS206.lua +++ b/data/RTTR/campaigns/roman/MISS206.lua @@ -1,14 +1,13 @@ ------------------------------------------------------------------------------ -- LUA-Script for MISS206.WLD (mission 7 of the original "Roman Campaign" -- -- -- --- Authors: CrazyL, Spikeone, ArthurMurray47 -- +-- Authors: CrazyL, Spikeone, ArthurMurray47, kubaau -- ------------------------------------------------------------------------------ -------------------------------- TODO ----------------------------------------- -- EnableNextMissions() -- Set Portraits --- Set AI Agression Level ------------------------------------------------------------------------------- @@ -113,13 +112,11 @@ function onSettingsReady() rttr:GetPlayer(1):SetNation(NAT_VIKINGS) -- nation rttr:GetPlayer(1):SetColor(1) -- yellow rttr:GetPlayer(1):SetName('Erik') -- Enemy Name - rttr:GetPlayer(1):SetTeam(TM_TEAM1) rttr:GetPlayer(2):SetAI(3) -- hard AI rttr:GetPlayer(2):SetNation(NAT_VIKINGS) -- nation rttr:GetPlayer(2):SetColor(2) -- red rttr:GetPlayer(2):SetName('Olof') -- Enemy Name - rttr:GetPlayer(2):SetTeam(TM_TEAM1) end function getAllowedChanges() @@ -136,6 +133,13 @@ end -- start callback function onStart(isFirstStart) + if isFirstStart then + rttr:GetPlayer(2):MakeOneSidedAllianceTo(1) -- !GLOBAL_SET_COMPUTER_ALLIANCE 2 1 + rttr:GetPlayer(1):MakeOneSidedAllianceTo(2) -- !GLOBAL_SET_COMPUTER_ALLIANCE 1 2 + rttr:GetPlayer(1):MakeOneSidedAllianceTo(0) -- !GLOBAL_SET_COMPUTER_ALLIANCE 1 0 + rttr:GetPlayer(2):MakeOneSidedAllianceTo(0) -- !GLOBAL_SET_COMPUTER_ALLIANCE 2 0 + end + for i = 0, (rttr:GetPlayerCount() - 1) do -- set resources addPlayerRes(i, not isFirstStart) addPlayerBld(i, not isFirstStart) diff --git a/data/RTTR/campaigns/roman/MISS207.lua b/data/RTTR/campaigns/roman/MISS207.lua index 24f66c450d..cdbca3eab8 100644 --- a/data/RTTR/campaigns/roman/MISS207.lua +++ b/data/RTTR/campaigns/roman/MISS207.lua @@ -1,14 +1,13 @@ ------------------------------------------------------------------------------ -- LUA-Script for MISS207.WLD (mission 8 of the original "Roman Campaign") -- -- -- --- Authors: CrazyL, Spikeone, ArthurMurray47 -- +-- Authors: CrazyL, Spikeone, ArthurMurray47, kubaau -- ------------------------------------------------------------------------------ -------------------------------- TODO ----------------------------------------- -- EnableNextMissions() -- Set Portraits --- Set AI Agression Level ------------------------------------------------------------------------------- @@ -101,17 +100,21 @@ function onSettingsReady() rttr:GetPlayer(1):SetNation(NAT_AFRICANS) -- nation rttr:GetPlayer(1):SetColor(1) -- yellow rttr:GetPlayer(1):SetName('Mnga Tscha') -- Enemy Name - rttr:GetPlayer(1):SetTeam(TM_TEAM1) rttr:GetPlayer(2):SetAI(3) -- hard AI rttr:GetPlayer(2):SetNation(NAT_AFRICANS) -- nation rttr:GetPlayer(2):SetColor(2) -- red rttr:GetPlayer(2):SetName('Todo') -- Enemy Name - rttr:GetPlayer(2):SetTeam(TM_TEAM1) end -- start callback function onStart(isFirstStart) + if isFirstStart then + rttr:GetPlayer(1):MakeOneSidedAllianceTo(2) -- !GLOBAL_SET_COMPUTER_ALLIANCE 1 2 + rttr:GetPlayer(2):MakeOneSidedAllianceTo(1) -- !GLOBAL_SET_COMPUTER_ALLIANCE 2 1 + rttr:GetPlayer(2):MakeOneSidedAllianceTo(0) -- !GLOBAL_SET_COMPUTER_ALLIANCE 2 0 + end + for i = 0, (rttr:GetPlayerCount() - 1) do -- set resources addPlayerRes(i, not isFirstStart) addPlayerBld(i, not isFirstStart) diff --git a/data/RTTR/campaigns/roman/MISS208.lua b/data/RTTR/campaigns/roman/MISS208.lua index 269f256d4f..6ebc11f370 100644 --- a/data/RTTR/campaigns/roman/MISS208.lua +++ b/data/RTTR/campaigns/roman/MISS208.lua @@ -1,14 +1,13 @@ ------------------------------------------------------------------------------ -- LUA-Script for MISS208.WLD (mission 9 of the original "Roman Campaign") -- -- -- --- Authors: CrazyL, Spikeone, ArthurMurray47 -- +-- Authors: CrazyL, Spikeone, ArthurMurray47, kubaau -- ------------------------------------------------------------------------------ -------------------------------- TODO ----------------------------------------- -- EnableNextMissions() -- Set Portraits --- Set AI Agression Level ------------------------------------------------------------------------------- @@ -101,17 +100,20 @@ function onSettingsReady() rttr:GetPlayer(1):SetNation(NAT_JAPANESE) -- nation rttr:GetPlayer(1):SetColor(1) -- yellow rttr:GetPlayer(1):SetName('Yamauchi') -- Enemy Name - rttr:GetPlayer(1):SetTeam(TM_TEAM1) rttr:GetPlayer(2):SetAI(3) -- hard AI rttr:GetPlayer(2):SetNation(NAT_JAPANESE) -- nation rttr:GetPlayer(2):SetColor(2) -- red rttr:GetPlayer(2):SetName('Tsunami') -- Enemy Name - rttr:GetPlayer(2):SetTeam(TM_TEAM1) end -- start callback function onStart(isFirstStart) + if isFirstStart then + rttr:GetPlayer(2):MakeOneSidedAllianceTo(1) -- !GLOBAL_SET_COMPUTER_ALLIANCE 2 1 + rttr:GetPlayer(1):MakeOneSidedAllianceTo(2) -- !GLOBAL_SET_COMPUTER_ALLIANCE 1 2 + end + for i = 0, (rttr:GetPlayerCount() - 1) do -- set resources addPlayerRes(i, not isFirstStart) addPlayerBld(i, not isFirstStart) diff --git a/data/RTTR/campaigns/roman/MISS209.lua b/data/RTTR/campaigns/roman/MISS209.lua index f9cd271574..7355b3cc9a 100644 --- a/data/RTTR/campaigns/roman/MISS209.lua +++ b/data/RTTR/campaigns/roman/MISS209.lua @@ -1,14 +1,13 @@ ------------------------------------------------------------------------------ -- LUA-Script for MISS209.WLD (mission 10 of the original "Roman Campaign") -- -- -- --- Authors: CrazyL, Spikeone, ArthurMurray47 -- +-- Authors: CrazyL, Spikeone, ArthurMurray47, kubaau -- ------------------------------------------------------------------------------ -------------------------------- TODO ----------------------------------------- -- EnableNextMissions() -- Set Portraits --- Set AI Agression Level ------------------------------------------------------------------------------- @@ -110,17 +109,21 @@ function onSettingsReady() rttr:GetPlayer(1):SetNation(NAT_ROMANS) -- nation rttr:GetPlayer(1):SetColor(1) -- yellow rttr:GetPlayer(1):SetName('Brutus') -- Enemy Name - rttr:GetPlayer(1):SetTeam(TM_TEAM1) rttr:GetPlayer(2):SetAI(3) -- hard AI rttr:GetPlayer(2):SetNation(NAT_VIKINGS) -- nation rttr:GetPlayer(2):SetColor(2) -- red rttr:GetPlayer(2):SetName('Olof') -- Enemy Name - rttr:GetPlayer(2):SetTeam(TM_TEAM1) end -- start callback function onStart(isFirstStart) + if isFirstStart then + rttr:GetPlayer(2):MakeOneSidedAllianceTo(1) -- !GLOBAL_SET_COMPUTER_ALLIANCE 2 1 + rttr:GetPlayer(1):MakeOneSidedAllianceTo(2) -- !GLOBAL_SET_COMPUTER_ALLIANCE 1 2 + rttr:GetPlayer(1):MakeOneSidedAllianceTo(0) -- !GLOBAL_SET_COMPUTER_ALLIANCE 1 0 + end + for i = 0, (rttr:GetPlayerCount() - 1) do -- set resources addPlayerRes(i, not isFirstStart) addPlayerBld(i, not isFirstStart) diff --git a/doc/lua/functions.md b/doc/lua/functions.md index e142d2eb26..2130e5c397 100644 --- a/doc/lua/functions.md +++ b/doc/lua/functions.md @@ -329,8 +329,32 @@ Return true if the player is defeated **IsAlly(otherPlayerIdx)** Return true if the player is an ally of this player -**IsAttackable(otherPlayerIdx)** -Return true if the player can be attacked +**CanAttack(otherPlayerIdx)** +Return true if the player can attack the other player. + +**MakeOneSidedAllianceTo(otherPlayerIdx)** +Creates a one-sided alliance from the player to the other player. +This type of alliance is typical for classic S2 campaigns and enables situations where P1 can be allied to P2 without P2 being allied to P1. + +The rules of this type of alliance are: +- if P1 is allied to P2 `and` P1 is AI, `then` + - P1 cannot attack P2 +- if P1 attacks P2 `or` if P1 breaks their alliance towards P2, `then` + - P2 breaks their alliance towards P1 `and` + - all players who are allied to P2 `and` to whom P2 is allied to break their alliance towards P1 + +In the situation described above, only P2 is capable of starting a war. P1 can never initiate against P2 as long as they are allied. The exception to this rule is that the human player can always fight against anyone and can influence diplomatic relations between AI players by choosing when and whom to fight. + +The alliance rules have interesting implications. In the classic Roman campaign they are used to allow the player to choose when to start fighting in most of the missions, making them significantly easier. By allying an AI to the human, they will not attack the human until the human attacks them. In some missions, it is encouraged not to attack an enemy if one of their allies is also our ally, as this would cause their allies to break alliances with us. + +More advanced implications can be seen in custom campaigns where the whole design of some scenarios is based on the human player attacking the right AIs at the right time. Consider the following example: +- P1 (human) is allied to P2 and P3 and is weak +- P2 (AI) is allied to P1 and P3 and is moderately strong +- P3 (AI) is allied to P1 and is strong enough to defeat both players + +Assuming all players are in relatively close proximity, the only way P1 can survive is by attacking P2 before P3 attacks P2. +If P3 attacks P2 before P1, P1 will break their alliance towards P3 and P3 will retaliate in the same fashion. P3 will likely devastate P1 before they can level the playing field. +However, if P1 attacks P2 first, they break this weak alliance, allowing P3 to fight P2 without breaking its alliance to P1. **SuggestPact(otherPlayerIdx, PactType, duration)** Let the AI send a request to the other player for a new pact. diff --git a/libs/s25main/GamePlayer.cpp b/libs/s25main/GamePlayer.cpp index d00de03b98..1746181956 100644 --- a/libs/s25main/GamePlayer.cpp +++ b/libs/s25main/GamePlayer.cpp @@ -1194,17 +1194,47 @@ bool GamePlayer::IsAlly(const unsigned char playerId) const if(GetPlayerId() == playerId) return true; else - return GetPactState(PactType::TreatyOfAlliance, playerId) == PactState::Accepted; + return GetPactState(PactType::TreatyOfAlliance, playerId) == PactState::Accepted + || GetPactState(PactType::OneSidedAlliance, playerId) == PactState::Accepted; } -bool GamePlayer::IsAttackable(const unsigned char playerId) const +bool GamePlayer::CanAttack(const unsigned char otherPlayerId) const { + if(GetPactState(PactType::OneSidedAlliance, otherPlayerId) == PactState::Accepted) + return isHuman(); + // Verbündete dürfen nicht angegriffen werden - if(IsAlly(playerId)) + if(IsAlly(otherPlayerId)) return false; else // Ansonsten darf bei bestehendem Nichtangriffspakt ebenfalls nicht angegriffen werden - return GetPactState(PactType::NonAgressionPact, playerId) != PactState::Accepted; + return GetPactState(PactType::NonAgressionPact, otherPlayerId) != PactState::Accepted; +} + +void GamePlayer::OnAttackedBy(unsigned char attackerId) +{ + BreakOneSidedAllianceTo(attackerId); + + // also break alliances for our allies + const auto thisPlayerId = GetPlayerId(); + for(auto i = 0u; i < world.GetNumPlayers(); ++i) + { + // skip ourselves + if(i == thisPlayerId) + continue; + + // skip if we are not allied to them + if(!IsAlly(i)) + continue; + + auto& player = world.GetPlayer(i); + + // skip if they are not allied to us + if(!player.IsAlly(thisPlayerId)) + continue; + + player.BreakOneSidedAllianceTo(attackerId); + } } void GamePlayer::OrderTroops(nobMilitary* goal, std::array counts, @@ -1747,6 +1777,9 @@ void GamePlayer::MakeStartPacts() continue; for(const auto z : helpers::enumRange()) { + if(z == PactType::OneSidedAlliance) + continue; + pacts[i][z].duration = DURATION_INFINITE; pacts[i][z].start = 0; pacts[i][z].accepted = true; @@ -1755,6 +1788,20 @@ void GamePlayer::MakeStartPacts() } } +void GamePlayer::MakeOneSidedAllianceTo(unsigned char otherPlayerId) +{ + auto& pact = pacts[otherPlayerId][PactType::OneSidedAlliance]; + pact.duration = DURATION_INFINITE; + pact.accepted = true; +} + +void GamePlayer::BreakOneSidedAllianceTo(unsigned char otherPlayerId) +{ + pacts[otherPlayerId][PactType::OneSidedAlliance].duration = 0; + // you can make a one-sided alliance but breaking one side of it breaks the other as well + world.GetPlayer(otherPlayerId).pacts[GetPlayerId()][PactType::OneSidedAlliance].duration = 0; +} + bool GamePlayer::IsWareRegistred(const Ware& ware) { return helpers::contains(ware_list, &ware); diff --git a/libs/s25main/GamePlayer.h b/libs/s25main/GamePlayer.h index 59ee843473..b4a789b278 100644 --- a/libs/s25main/GamePlayer.h +++ b/libs/s25main/GamePlayer.h @@ -193,8 +193,10 @@ class GamePlayer : public GamePlayerInfo /// Setzt neue Baureihenfolge-Einstellungen void ChangeBuildOrder(bool useCustomBuildOrder, const BuildOrders& order_data); - /// Can this player and the other attack each other? - bool IsAttackable(unsigned char playerId) const; + /// Can this player attack the other player? + bool CanAttack(unsigned char otherPlayerId) const; + /// Called when the player is attacked + void OnAttackedBy(unsigned char attackerId); /// Are these players allied? (-> Teamview, attack support, ...) bool IsAlly(unsigned char playerId) const; /// Order troops of each rank according to `counts` without exceeding `total_max` in total @@ -274,6 +276,8 @@ class GamePlayer : public GamePlayerInfo unsigned GetRemainingPactTime(PactType pt, unsigned char other_player) const; /// Setzt die initialen Bündnisse anhand der Teams void MakeStartPacts(); + /// Creates a one-sided alliance from the player to the other player. + void MakeOneSidedAllianceTo(unsigned char otherPlayerId); /// Testet die Bündnisse, ob sie nicht schon abgelaufen sind void TestPacts(); @@ -422,6 +426,8 @@ class GamePlayer : public GamePlayerInfo void MakePact(PactType pt, unsigned char other_player, unsigned duration); /// Called after a pact was changed(added/removed) in both players void PactChanged(PactType pt); + /// Breaks a one-sided alliance from the player to the other player. + void BreakOneSidedAllianceTo(unsigned char otherPlayerId); // Sucht Weg für Job zu entsprechenden noRoadNode bool FindWarehouseForJob(Job job, noRoadNode* goal) const; /// Prüft, ob der Spieler besiegt wurde diff --git a/libs/s25main/ai/AIInterface.h b/libs/s25main/ai/AIInterface.h index ccdb9b985c..46eb7c6cca 100644 --- a/libs/s25main/ai/AIInterface.h +++ b/libs/s25main/ai/AIInterface.h @@ -93,8 +93,8 @@ class AIInterface : public GameCommandFactory bool CanBuildCatapult() const { return player_.CanBuildCatapult(); } /// checks if the player is allowed to build the building type (lua maybe later addon?) bool CanBuildBuildingtype(BuildingType bt) const { return player_.IsBuildingEnabled(bt); } - /// Test whether a player is attackable or not (alliances, etc) - bool IsPlayerAttackable(unsigned char playerID) const { return player_.IsAttackable(playerID); } + /// Can the current player (AI) attack the other player? + bool CanAttack(unsigned char otherPlayerId) const { return gwb.GetPlayer(GetPlayerId()).CanAttack(otherPlayerId); } /// player.FindWarehouse template nobBaseWarehouse* FindWarehouse(const noRoadNode& start, const T_IsWarehouseGood& isWarehouseGood, bool to_wh, diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index 9bd836dfb2..42e3109cbb 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -1555,7 +1555,7 @@ void AIPlayerJH::TryToAttack() if(target->GetGOT() == GO_Type::NobMilitary && static_cast(target)->IsNewBuilt()) continue; MapPoint dest = target->GetPos(); - if(gwb.CalcDistance(src, dest) < BASE_ATTACKING_DISTANCE && aii.IsPlayerAttackable(target->GetPlayer()) + if(gwb.CalcDistance(src, dest) < BASE_ATTACKING_DISTANCE && aii.CanAttack(target->GetPlayer()) && aii.IsVisible(dest)) { if(target->GetGOT() != GO_Type::NobMilitary && !target->DefendersAvailable()) @@ -1658,7 +1658,7 @@ void AIPlayerJH::TrySeaAttack() { if(aii.IsVisible(hb->GetPos())) { - if(aii.IsPlayerAttackable(hb->GetPlayer())) + if(aii.CanAttack(hb->GetPlayer())) { // attackers for this building? const std::vector testseaidswithattackers = @@ -1712,7 +1712,7 @@ void AIPlayerJH::TrySeaAttack() sortedMilitaryBlds buildings = gwb.LookForMilitaryBuildings(gwb.GetHarborPoint(searcharoundharborspots[i]), 2); for(const nobBaseMilitary* milBld : buildings) { - if(aii.IsPlayerAttackable(milBld->GetPlayer()) && aii.IsVisible(milBld->GetPos())) + if(aii.CanAttack(milBld->GetPlayer()) && aii.IsVisible(milBld->GetPos())) { const auto* enemyTarget = dynamic_cast((milBld)); diff --git a/libs/s25main/buildings/nobBaseMilitary.cpp b/libs/s25main/buildings/nobBaseMilitary.cpp index 9e9fe2e6f9..69ae4cf06f 100644 --- a/libs/s25main/buildings/nobBaseMilitary.cpp +++ b/libs/s25main/buildings/nobBaseMilitary.cpp @@ -321,7 +321,7 @@ bool nobBaseMilitary::SendSuccessor(const MapPoint pt, const unsigned short radi return false; } -bool nobBaseMilitary::IsAttackable(unsigned playerIdx) const +bool nobBaseMilitary::CanBeAttackedBy(unsigned playerIdx) const { // If we are in peaceful mode -> not attackable if(world->GetGGS().getSelection(AddonId::PEACEFULMODE)) @@ -331,7 +331,7 @@ bool nobBaseMilitary::IsAttackable(unsigned playerIdx) const if(world->CalcVisiblityWithAllies(pos, playerIdx) != Visibility::Visible) return false; // Else it depends on the team settings - return world->GetPlayer(player).IsAttackable(playerIdx); + return world->GetPlayer(playerIdx).CanAttack(player); } bool nobBaseMilitary::IsAggressor(const nofAttacker& attacker) const diff --git a/libs/s25main/buildings/nobBaseMilitary.h b/libs/s25main/buildings/nobBaseMilitary.h index 4b7741cf97..76367b9636 100644 --- a/libs/s25main/buildings/nobBaseMilitary.h +++ b/libs/s25main/buildings/nobBaseMilitary.h @@ -121,7 +121,7 @@ class nobBaseMilitary : public noBuilding bool IsUnderAttack() const { return !aggressors.empty(); }; /// Return whether this building can be attacked by the given player. - virtual bool IsAttackable(unsigned playerIdx) const; + virtual bool CanBeAttackedBy(unsigned playerIdx) const; /// Debugging bool IsAggressor(const nofAttacker& attacker) const; diff --git a/libs/s25main/buildings/nobMilitary.cpp b/libs/s25main/buildings/nobMilitary.cpp index 781eff0e83..4879266c90 100644 --- a/libs/s25main/buildings/nobMilitary.cpp +++ b/libs/s25main/buildings/nobMilitary.cpp @@ -345,7 +345,7 @@ void nobMilitary::LookForEnemyBuildings(const nobBaseMilitary* const exception) { // feindliches Militärgebäude? if(building != exception && building->GetPlayer() != player - && world->GetPlayer(building->GetPlayer()).IsAttackable(player)) + && world->GetPlayer(player).CanAttack(building->GetPlayer())) { unsigned distance = world->CalcDistance(pos, building->GetPos()); FrontierDistance newFrontierDistance = FrontierDistance::Far; @@ -590,10 +590,10 @@ bool nobMilitary::IsUseless() const return !world->DoesDestructionChangeTerritory(*this); } -bool nobMilitary::IsAttackable(unsigned playerIdx) const +bool nobMilitary::CanBeAttackedBy(unsigned playerIdx) const { // Cannot be attacked, if it is Being captured or not claimed yet (just built) - return nobBaseMilitary::IsAttackable(playerIdx) && !IsBeingCaptured() && !IsNewBuilt(); + return nobBaseMilitary::CanBeAttackedBy(playerIdx) && !IsBeingCaptured() && !IsNewBuilt(); } bool nobMilitary::IsInTroops(const nofPassiveSoldier& soldier) const @@ -966,7 +966,7 @@ void nobMilitary::Capture(const unsigned char new_owner) for(auto* building : buildings) { // verbündetes Gebäude? - if(world->GetPlayer(building->GetPlayer()).IsAttackable(old_player) + if(world->GetPlayer(old_player).CanAttack(building->GetPlayer()) && BuildingProperties::IsMilitary(building->GetBuildingType())) // Grenzflaggen von dem neu berechnen static_cast(building)->LookForEnemyBuildings(); @@ -996,7 +996,7 @@ void nobMilitary::Capture(const unsigned char new_owner) nofAttacker* attacker = *it; // dont remove attackers owned by players not allied with the new owner! unsigned char attPlayer = attacker->GetPlayer(); - if(attPlayer != player && !world->GetPlayer(attPlayer).IsAttackable(player)) + if(attPlayer != player && !world->GetPlayer(attPlayer).CanAttack(player)) { it = aggressors.erase(it); attacker->CapturedBuildingFull(); @@ -1102,7 +1102,7 @@ void nobMilitary::NeedOccupyingTroops() // Nicht gerade Soldaten löschen, die das Gebäude noch einnehmen! // also: dont remove attackers owned by players not allied with the new owner! if(attacker->GetState() != nofActiveSoldier::SoldierState::AttackingCapturingNext - && !world->GetPlayer(attacker->GetPlayer()).IsAttackable(player)) + && !world->GetPlayer(attacker->GetPlayer()).CanAttack(player)) { it = aggressors.erase(it); attacker->CapturedBuildingFull(); diff --git a/libs/s25main/buildings/nobMilitary.h b/libs/s25main/buildings/nobMilitary.h index 9e5fece1c4..ecdfa5b649 100644 --- a/libs/s25main/buildings/nobMilitary.h +++ b/libs/s25main/buildings/nobMilitary.h @@ -125,7 +125,7 @@ class nobMilitary : public nobBaseMilitary /// gibt setzt frontier_distance neu falls möglich und sendet ggf. Verstärkung void NewEnemyMilitaryBuilding(FrontierDistance distance); bool IsUseless() const; - bool IsAttackable(unsigned playerIdx) const override; + bool CanBeAttackedBy(unsigned playerIdx) const override; /// Gibt Distanz zurück FrontierDistance GetFrontierDistance() const { return frontier_distance; } diff --git a/libs/s25main/buildings/nobStorehouse.h b/libs/s25main/buildings/nobStorehouse.h index 0be4a644b3..8a0f82f31c 100644 --- a/libs/s25main/buildings/nobStorehouse.h +++ b/libs/s25main/buildings/nobStorehouse.h @@ -17,7 +17,7 @@ class nobStorehouse : public nobBaseWarehouse public: GO_Type GetGOT() const final { return GO_Type::NobStorehouse; } unsigned GetMilitaryRadius() const override { return 0; } - bool IsAttackable(unsigned /*playerIdx*/) const override { return false; } + bool CanBeAttackedBy(unsigned /*playerIdx*/) const override { return false; } void Draw(DrawPoint drawPt) override; diff --git a/libs/s25main/figures/nofAttacker.cpp b/libs/s25main/figures/nofAttacker.cpp index 9301a557cd..d51abd9dff 100644 --- a/libs/s25main/figures/nofAttacker.cpp +++ b/libs/s25main/figures/nofAttacker.cpp @@ -546,7 +546,7 @@ void nofAttacker::OrderAggressiveDefender() GamePlayer& bldOwner = world->GetPlayer(bldOwnerId); if(!bldOwner.IsAlly(attacked_goal->GetPlayer())) continue; - if(!bldOwner.IsAttackable(player)) + if(!bldOwner.CanAttack(player)) continue; // If player did not decide on sending do it now. // Doing this as late as here reduces chances, diff --git a/libs/s25main/figures/nofCatapultMan.cpp b/libs/s25main/figures/nofCatapultMan.cpp index 80b11c5a5e..7f6321db2a 100644 --- a/libs/s25main/figures/nofCatapultMan.cpp +++ b/libs/s25main/figures/nofCatapultMan.cpp @@ -111,7 +111,7 @@ void nofCatapultMan::HandleDerivedEvent(const unsigned /*id*/) { // Auch ein richtiges Militärgebäude (kein HQ usw.), if(building->GetGOT() == GO_Type::NobMilitary - && world->GetPlayer(player).IsAttackable(building->GetPlayer())) + && world->GetPlayer(player).CanAttack(building->GetPlayer())) { // Was nicht im Nebel liegt und auch schon besetzt wurde (nicht neu gebaut)? if(world->GetNode(building->GetPos()).fow[player].visibility == Visibility::Visible diff --git a/libs/s25main/gameTypes/GameTypesOutput.h b/libs/s25main/gameTypes/GameTypesOutput.h index 598b549fc2..5e37067a50 100644 --- a/libs/s25main/gameTypes/GameTypesOutput.h +++ b/libs/s25main/gameTypes/GameTypesOutput.h @@ -59,7 +59,7 @@ RTTR_ENUM_OUTPUT(GameSpeed, VerySlow, Slow, Normal, Fast, VeryFast) RTTR_ENUM_OUTPUT(MapType, OldMap, Savegame) RTTR_ENUM_OUTPUT(PlayerState, Free, Occupied, Locked, AI) RTTR_ENUM_OUTPUT(PointRoad, None, Normal, Donkey, Boat) -RTTR_ENUM_OUTPUT(PactType, TreatyOfAlliance, NonAgressionPact) +RTTR_ENUM_OUTPUT(PactType, TreatyOfAlliance, NonAgressionPact, OneSidedAlliance) RTTR_ENUM_OUTPUT(ResourceType, Nothing, Iron, Gold, Coal, Granite, Water, Fish) RTTR_ENUM_OUTPUT(RoadDir, East, SouthEast, SouthWest) RTTR_ENUM_OUTPUT(Species, PolarBear, RabbitWhite, RabbitGrey, Fox, Stag, Deer, Duck, Sheep) diff --git a/libs/s25main/gameTypes/PactTypes.cpp b/libs/s25main/gameTypes/PactTypes.cpp index 75b3cb2338..5628ac05f3 100644 --- a/libs/s25main/gameTypes/PactTypes.cpp +++ b/libs/s25main/gameTypes/PactTypes.cpp @@ -5,5 +5,5 @@ #include "gameTypes/PactTypes.h" #include "mygettext/mygettext.h" -const helpers::EnumArray PACT_NAMES = {gettext_noop("Treaty of alliance"), - gettext_noop("Non-aggression pact")}; +const helpers::EnumArray PACT_NAMES = { + gettext_noop("Treaty of alliance"), gettext_noop("Non-aggression pact"), gettext_noop("One-sided alliance")}; diff --git a/libs/s25main/gameTypes/PactTypes.h b/libs/s25main/gameTypes/PactTypes.h index 5df77799d0..1f375d6216 100644 --- a/libs/s25main/gameTypes/PactTypes.h +++ b/libs/s25main/gameTypes/PactTypes.h @@ -11,11 +11,12 @@ enum class PactType : uint8_t { TreatyOfAlliance, - NonAgressionPact + NonAgressionPact, + OneSidedAlliance, }; constexpr auto maxEnumValue(PactType) { - return PactType::NonAgressionPact; + return PactType::OneSidedAlliance; } constexpr unsigned DURATION_INFINITE = 0xFFFFFFFF; diff --git a/libs/s25main/ingameWindows/iwDiplomacy.cpp b/libs/s25main/ingameWindows/iwDiplomacy.cpp index aab931cc98..5decff5483 100644 --- a/libs/s25main/ingameWindows/iwDiplomacy.cpp +++ b/libs/s25main/ingameWindows/iwDiplomacy.cpp @@ -213,6 +213,7 @@ iwSuggestPact::iwSuggestPact(const PactType pt, const GamePlayer& player, GameCo { case PactType::TreatyOfAlliance: image = LOADER.GetImageN("io", 61); break; case PactType::NonAgressionPact: image = LOADER.GetImageN("io", 100); break; + case PactType::OneSidedAlliance: RTTR_Assert(false); // this should never be suggested } // Bild als Orientierung, welchen Vertrag wir gerade bearbeiten diff --git a/libs/s25main/lua/LuaInterfaceGame.cpp b/libs/s25main/lua/LuaInterfaceGame.cpp index 18c4d46cf7..8832eb9beb 100644 --- a/libs/s25main/lua/LuaInterfaceGame.cpp +++ b/libs/s25main/lua/LuaInterfaceGame.cpp @@ -158,6 +158,7 @@ LuaInterfaceGame::LuaInterfaceGame(Game& gameInstance, ILocalGameState& localGam #define ADD_LUA_CONST(name) lua[#name] = name lua["NON_AGGRESSION_PACT"] = PactType::NonAgressionPact; lua["TREATY_OF_ALLIANCE"] = PactType::TreatyOfAlliance; + lua["ONE_SIDED_ALLIANCE"] = PactType::OneSidedAlliance; // infinite pact duration, see GamePlayer::GetRemainingPactTime ADD_LUA_CONST(DURATION_INFINITE); #undef ADD_LUA_CONST diff --git a/libs/s25main/lua/LuaPlayer.cpp b/libs/s25main/lua/LuaPlayer.cpp index 3dd5158883..e5e06aae1f 100644 --- a/libs/s25main/lua/LuaPlayer.cpp +++ b/libs/s25main/lua/LuaPlayer.cpp @@ -51,9 +51,10 @@ void LuaPlayer::Register(kaguya::State& state) .addFunction("IsDefeated", &LuaPlayer::IsDefeated) .addFunction("Surrender", &LuaPlayer::Surrender) .addFunction("IsAlly", &LuaPlayer::IsAlly) - .addFunction("IsAttackable", &LuaPlayer::IsAttackable) + .addFunction("CanAttack", &LuaPlayer::CanAttack) .addFunction("SuggestPact", &LuaPlayer::SuggestPact) .addFunction("CancelPact", &LuaPlayer::CancelPact) + .addFunction("MakeOneSidedAllianceTo", &LuaPlayer::MakeOneSidedAllianceTo) // Old names .addFunction("GetBuildingCount", &LuaPlayer::GetNumBuildings) .addFunction("GetBuildingSitesCount", &LuaPlayer::GetNumBuildingSites) @@ -292,9 +293,9 @@ bool LuaPlayer::IsAlly(unsigned char otherPlayerId) return player.IsAlly(otherPlayerId); } -bool LuaPlayer::IsAttackable(unsigned char otherPlayerId) +bool LuaPlayer::CanAttack(unsigned char otherPlayerId) { - return player.IsAttackable(otherPlayerId); + return player.CanAttack(otherPlayerId); } void LuaPlayer::SuggestPact(unsigned char otherPlayerId, const lua::SafeEnum pt, const unsigned duration) @@ -310,3 +311,8 @@ void LuaPlayer::CancelPact(const lua::SafeEnum pt, unsigned char other if(ai != nullptr) ai->getAIInterface().CancelPact(pt, otherPlayerId); } + +void LuaPlayer::MakeOneSidedAllianceTo(unsigned char otherPlayerId) +{ + player.MakeOneSidedAllianceTo(otherPlayerId); +} diff --git a/libs/s25main/lua/LuaPlayer.h b/libs/s25main/lua/LuaPlayer.h index 3335084a51..f681910d5a 100644 --- a/libs/s25main/lua/LuaPlayer.h +++ b/libs/s25main/lua/LuaPlayer.h @@ -55,7 +55,8 @@ class LuaPlayer : public LuaPlayerBase void Surrender(bool destroyBlds); std::pair GetHQPos() const; bool IsAlly(unsigned char otherPlayerId); - bool IsAttackable(unsigned char otherPlayerId); + bool CanAttack(unsigned char otherPlayerId); void SuggestPact(unsigned char otherPlayerId, lua::SafeEnum pt, unsigned duration); void CancelPact(lua::SafeEnum pt, unsigned char otherPlayerId); + void MakeOneSidedAllianceTo(unsigned char otherPlayerId); }; diff --git a/libs/s25main/world/GameWorld.cpp b/libs/s25main/world/GameWorld.cpp index b02b01c3e7..ba0ab05658 100644 --- a/libs/s25main/world/GameWorld.cpp +++ b/libs/s25main/world/GameWorld.cpp @@ -726,7 +726,7 @@ void GameWorld::Attack(const unsigned char player_attacker, const MapPoint pt, c const bool strong_soldiers) { auto* attacked_building = GetSpecObj(pt); - if(!attacked_building || !attacked_building->IsAttackable(player_attacker)) + if(!attacked_building || !attacked_building->CanBeAttackedBy(player_attacker)) return; // Militärgebäude in der Nähe finden @@ -799,9 +799,13 @@ void GameWorld::Attack(const unsigned char player_attacker, const MapPoint pt, c curNumSoldiers++; } - if(curNumSoldiers > 0 && HasLua()) + if(curNumSoldiers > 0) { - GetLua().EventAttack(player_attacker, attacked_building->GetPlayer(), curNumSoldiers); + GetPlayer(attacked_building->GetPlayer()).OnAttackedBy(player_attacker); + if(HasLua()) + { + GetLua().EventAttack(player_attacker, attacked_building->GetPlayer(), curNumSoldiers); + } } } @@ -847,9 +851,13 @@ void GameWorld::AttackViaSea(const unsigned char player_attacker, const MapPoint counter++; } - if(counter > 0 && HasLua()) + if(counter > 0) { - GetLua().EventAttack(player_attacker, attacked_building.GetPlayer(), counter); + GetPlayer(attacked_building.GetPlayer()).OnAttackedBy(player_attacker); + if(HasLua()) + { + GetLua().EventAttack(player_attacker, attacked_building.GetPlayer(), counter); + } } } @@ -1224,7 +1232,7 @@ void GameWorld::RecalcMovingVisibilities(const MapPoint pt, const unsigned char if(current_owner && (old_vis == Visibility::Invisible || (old_vis == Visibility::FogOfWar && old_owner != current_owner))) { - if(GetPlayer(player).IsAttackable(current_owner - 1) && enemy_territory) + if(GetPlayer(player).CanAttack(current_owner - 1) && enemy_territory) { *enemy_territory = tt; } @@ -1248,7 +1256,7 @@ void GameWorld::RecalcMovingVisibilities(const MapPoint pt, const unsigned char if(current_owner && (old_vis == Visibility::Invisible || (old_vis == Visibility::FogOfWar && old_owner != current_owner))) { - if(GetPlayer(player).IsAttackable(current_owner - 1) && enemy_territory) + if(GetPlayer(player).CanAttack(current_owner - 1) && enemy_territory) { *enemy_territory = tt; } diff --git a/libs/s25main/world/GameWorldBase.cpp b/libs/s25main/world/GameWorldBase.cpp index 568b6333b4..46d045fec2 100644 --- a/libs/s25main/world/GameWorldBase.cpp +++ b/libs/s25main/world/GameWorldBase.cpp @@ -445,7 +445,7 @@ std::vector GameWorldBase::GetUsableTargetHarborsForAttack(const MapPo { // Does an enemy harbor exist at current harbor spot? -> Can't attack through this harbor spot const auto* hb = GetSpecObj(harborPt); - if(hb && GetPlayer(player_attacker).IsAttackable(hb->GetPlayer())) + if(hb && GetPlayer(player_attacker).CanAttack(hb->GetPlayer())) continue; } @@ -506,7 +506,7 @@ std::vector GameWorldBase::GetFilteredSeaIDsForAttack(const MapP { // Does an enemy harbor exist at current harbor spot? -> Can't attack through this harbor spot const auto* hb = GetSpecObj(harborPt); - if(hb && GetPlayer(player_attacker).IsAttackable(hb->GetPlayer())) + if(hb && GetPlayer(player_attacker).CanAttack(hb->GetPlayer())) continue; } @@ -620,7 +620,7 @@ GameWorldBase::GetSoldiersForSeaAttack(const unsigned char player_attacker, cons return attackers; // Do we have an attackble military building? const auto* milBld = GetSpecObj(pt); - if(!milBld || !milBld->IsAttackable(player_attacker)) + if(!milBld || !milBld->CanBeAttackedBy(player_attacker)) return attackers; std::vector use_seas(GetNumSeas()); diff --git a/libs/s25main/world/GameWorldViewer.cpp b/libs/s25main/world/GameWorldViewer.cpp index a51f6440be..ced060aecb 100644 --- a/libs/s25main/world/GameWorldViewer.cpp +++ b/libs/s25main/world/GameWorldViewer.cpp @@ -89,7 +89,7 @@ unsigned GameWorldViewer::GetNumSoldiersForAttack(const MapPoint pt) const { const auto* attacked_building = GetWorld().GetSpecObj(pt); // Can we actually attack this bld? - if(!attacked_building || !attacked_building->IsAttackable(playerId_)) + if(!attacked_building || !attacked_building->CanBeAttackedBy(playerId_)) return 0; // Militärgebäude in der Nähe finden diff --git a/tests/s25Main/integration/testPacts.cpp b/tests/s25Main/integration/testPacts.cpp index 6d936386fc..1138024c6c 100644 --- a/tests/s25Main/integration/testPacts.cpp +++ b/tests/s25Main/integration/testPacts.cpp @@ -35,11 +35,11 @@ BOOST_FIXTURE_TEST_CASE(InitialPactStates, WorldWithGCExecution3P) if(i == j) { BOOST_TEST(player.IsAlly(j)); - BOOST_TEST(!player.IsAttackable(j)); + BOOST_TEST(!player.CanAttack(j)); } else { BOOST_TEST(!player.IsAlly(j)); - BOOST_TEST(player.IsAttackable(j)); + BOOST_TEST(player.CanAttack(j)); } for(const auto p : helpers::enumRange()) { @@ -77,12 +77,12 @@ void CheckPactState(const GameWorldBase& world, unsigned playerIdFrom, unsigned == playerFrom.GetRemainingPactTime(pact, playerIdTo)); // Attackable must match - BOOST_TEST_REQUIRE(playerFrom.IsAttackable(playerIdTo) == playerTo.IsAttackable(playerIdFrom)); + BOOST_TEST_REQUIRE(playerFrom.CanAttack(playerIdTo) == playerTo.CanAttack(playerIdFrom)); // Ally must match BOOST_TEST_REQUIRE(playerFrom.IsAlly(playerIdTo) == playerTo.IsAlly(playerIdFrom)); // Attackable only when non-agg. pact not accepted - BOOST_TEST_REQUIRE(playerFrom.IsAttackable(playerIdTo) + BOOST_TEST_REQUIRE(playerFrom.CanAttack(playerIdTo) == (playerFrom.GetPactState(PactType::NonAgressionPact, playerIdTo) != PactState::Accepted)); // Ally when treaty of alliance accepted BOOST_TEST_REQUIRE(playerFrom.IsAlly(playerIdTo) @@ -261,4 +261,196 @@ BOOST_FIXTURE_TEST_CASE(SuggestNewPactAfterExpiration, PactCreatedFixture, CheckPactState(world, 1, 2, PactType::NonAgressionPact, PactState::Accepted); } +using EmptyWorldFixture2P = WorldFixture; + +BOOST_FIXTURE_TEST_CASE(OneSidedAlliances_AlliancesDoNotNeedToBeSymmetric, EmptyWorldFixture2P) +{ + auto& p0 = world.GetPlayer(0); + auto& p1 = world.GetPlayer(1); + + p0.MakeOneSidedAllianceTo(1); + + BOOST_TEST_REQUIRE(p0.IsAlly(1) == true); + BOOST_TEST_REQUIRE(p1.IsAlly(0) == false); +} + +BOOST_FIXTURE_TEST_CASE(OneSidedAlliances_AICanAttackAnyoneByDefault, EmptyWorldFixture2P) +{ + auto& p0 = world.GetPlayer(0); + auto& p1 = world.GetPlayer(1); + p0.ps = PlayerState::AI; + p1.ps = PlayerState::AI; + + BOOST_TEST_REQUIRE(p0.CanAttack(1) == true); + BOOST_TEST_REQUIRE(p1.CanAttack(0) == true); +} + +BOOST_FIXTURE_TEST_CASE(OneSidedAlliances_AICannotAttackAllies, EmptyWorldFixture2P) +{ + auto& p0 = world.GetPlayer(0); + auto& p1 = world.GetPlayer(1); + p0.ps = PlayerState::AI; + p1.ps = PlayerState::AI; + + p0.MakeOneSidedAllianceTo(1); + + BOOST_TEST_REQUIRE(p0.CanAttack(1) == false); + BOOST_TEST_REQUIRE(p1.CanAttack(0) == true); +} + +BOOST_FIXTURE_TEST_CASE(OneSidedAlliances_HumansCanAttackAnyone, EmptyWorldFixture2P) +{ + auto& p0 = world.GetPlayer(0); + auto& p1 = world.GetPlayer(1); + p1.ps = PlayerState::AI; + + p0.MakeOneSidedAllianceTo(1); + p1.MakeOneSidedAllianceTo(0); + + BOOST_TEST_REQUIRE(p0.CanAttack(1) == true); + BOOST_TEST_REQUIRE(p1.CanAttack(0) == false); +} + +BOOST_FIXTURE_TEST_CASE(OneSidedAlliances_AllianceIsBrokenWhenAttacked, EmptyWorldFixture2P) +{ + auto& p0 = world.GetPlayer(0); + auto& p1 = world.GetPlayer(1); + p0.ps = PlayerState::AI; + p1.ps = PlayerState::AI; + + p0.MakeOneSidedAllianceTo(1); + BOOST_TEST_REQUIRE(p0.IsAlly(1) == true); + BOOST_TEST_REQUIRE(p0.CanAttack(1) == false); + + p0.OnAttackedBy(1); + BOOST_TEST_REQUIRE(p0.IsAlly(1) == false); + BOOST_TEST_REQUIRE(p0.CanAttack(1) == true); +} + +BOOST_FIXTURE_TEST_CASE(OneSidedAlliances_AllianceIsBrokenBothWaysWhenAttacked, EmptyWorldFixture2P) +{ + auto& p0 = world.GetPlayer(0); + auto& p1 = world.GetPlayer(1); + p1.ps = PlayerState::AI; + + p0.MakeOneSidedAllianceTo(1); + p1.MakeOneSidedAllianceTo(0); + BOOST_TEST_REQUIRE(p0.IsAlly(1) == true); + BOOST_TEST_REQUIRE(p1.IsAlly(0) == true); + BOOST_TEST_REQUIRE(p0.CanAttack(1) == true); + BOOST_TEST_REQUIRE(p1.CanAttack(0) == false); + + p1.OnAttackedBy(0); + BOOST_TEST_REQUIRE(p0.IsAlly(1) == false); + BOOST_TEST_REQUIRE(p1.IsAlly(0) == false); + BOOST_TEST_REQUIRE(p0.CanAttack(1) == true); + BOOST_TEST_REQUIRE(p1.CanAttack(0) == true); +} + +using EmptyWorldFixture3P = WorldFixture; + +BOOST_FIXTURE_TEST_CASE( + OneSidedAlliances_BeingAttacked_AlsoBreaksAlliancesForPlayers_WhoAreAlliedToUs_AndWhoWeAreAlliedTo, + EmptyWorldFixture3P) +{ + auto& p0 = world.GetPlayer(0); + auto& p1 = world.GetPlayer(1); + auto& p2 = world.GetPlayer(2); + p0.ps = PlayerState::AI; + p1.ps = PlayerState::AI; + p2.ps = PlayerState::AI; + + p0.MakeOneSidedAllianceTo(1); + p0.MakeOneSidedAllianceTo(2); + p1.MakeOneSidedAllianceTo(0); + p1.MakeOneSidedAllianceTo(2); + p2.MakeOneSidedAllianceTo(0); + + BOOST_TEST_REQUIRE(p0.CanAttack(1) == false); + BOOST_TEST_REQUIRE(p0.CanAttack(2) == false); + BOOST_TEST_REQUIRE(p1.CanAttack(0) == false); + BOOST_TEST_REQUIRE(p1.CanAttack(2) == false); + BOOST_TEST_REQUIRE(p2.CanAttack(0) == false); + BOOST_TEST_REQUIRE(p2.CanAttack(1) == true); + + p1.OnAttackedBy(2); + + // alliance from p1 to p2 is broken + // but alliance between p0 and p2 is broken as well + BOOST_TEST_REQUIRE(p0.CanAttack(1) == false); + BOOST_TEST_REQUIRE(p0.CanAttack(2) == true); + BOOST_TEST_REQUIRE(p1.CanAttack(0) == false); + BOOST_TEST_REQUIRE(p1.CanAttack(2) == true); + BOOST_TEST_REQUIRE(p2.CanAttack(0) == true); + BOOST_TEST_REQUIRE(p2.CanAttack(1) == true); +} + +BOOST_FIXTURE_TEST_CASE(OneSidedAlliances_BeingAttacked_DoesNotAlsoBreakAlliancesForPlayers_WhoAreNotOurAlliedToUs, + EmptyWorldFixture3P) +{ + auto& p0 = world.GetPlayer(0); + auto& p1 = world.GetPlayer(1); + auto& p2 = world.GetPlayer(2); + p0.ps = PlayerState::AI; + p1.ps = PlayerState::AI; + p2.ps = PlayerState::AI; + + p0.MakeOneSidedAllianceTo(2); + p1.MakeOneSidedAllianceTo(0); + p1.MakeOneSidedAllianceTo(2); + p2.MakeOneSidedAllianceTo(0); + + BOOST_TEST_REQUIRE(p0.CanAttack(1) == true); + BOOST_TEST_REQUIRE(p0.CanAttack(2) == false); + BOOST_TEST_REQUIRE(p1.CanAttack(0) == false); + BOOST_TEST_REQUIRE(p1.CanAttack(2) == false); + BOOST_TEST_REQUIRE(p2.CanAttack(0) == false); + BOOST_TEST_REQUIRE(p2.CanAttack(1) == true); + + p1.OnAttackedBy(2); + + // alliance from p1 to p2 is broken + // alliance from p0 to p2 remains + BOOST_TEST_REQUIRE(p0.CanAttack(1) == true); + BOOST_TEST_REQUIRE(p0.CanAttack(2) == false); + BOOST_TEST_REQUIRE(p1.CanAttack(0) == false); + BOOST_TEST_REQUIRE(p1.CanAttack(2) == true); + BOOST_TEST_REQUIRE(p2.CanAttack(0) == false); + BOOST_TEST_REQUIRE(p2.CanAttack(1) == true); +} + +BOOST_FIXTURE_TEST_CASE(OneSidedAlliances_BeingAttacked_DoesNotAlsoBreakAlliancesForPlayers_WhoWeAreNotAlliedTo, + EmptyWorldFixture3P) +{ + auto& p0 = world.GetPlayer(0); + auto& p1 = world.GetPlayer(1); + auto& p2 = world.GetPlayer(2); + p0.ps = PlayerState::AI; + p1.ps = PlayerState::AI; + p2.ps = PlayerState::AI; + + p0.MakeOneSidedAllianceTo(1); + p0.MakeOneSidedAllianceTo(2); + p1.MakeOneSidedAllianceTo(2); + p2.MakeOneSidedAllianceTo(0); + + BOOST_TEST_REQUIRE(p0.CanAttack(1) == false); + BOOST_TEST_REQUIRE(p0.CanAttack(2) == false); + BOOST_TEST_REQUIRE(p1.CanAttack(0) == true); + BOOST_TEST_REQUIRE(p1.CanAttack(2) == false); + BOOST_TEST_REQUIRE(p2.CanAttack(0) == false); + BOOST_TEST_REQUIRE(p2.CanAttack(1) == true); + + p1.OnAttackedBy(2); + + // alliance from p1 to p2 is broken + // alliance from p0 to p2 remains + BOOST_TEST_REQUIRE(p0.CanAttack(1) == false); + BOOST_TEST_REQUIRE(p0.CanAttack(2) == false); + BOOST_TEST_REQUIRE(p1.CanAttack(0) == true); + BOOST_TEST_REQUIRE(p1.CanAttack(2) == true); + BOOST_TEST_REQUIRE(p2.CanAttack(0) == false); + BOOST_TEST_REQUIRE(p2.CanAttack(1) == true); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/s25Main/lua/testLua.cpp b/tests/s25Main/lua/testLua.cpp index 34fe0d42fb..d68a787acc 100644 --- a/tests/s25Main/lua/testLua.cpp +++ b/tests/s25Main/lua/testLua.cpp @@ -862,8 +862,8 @@ BOOST_AUTO_TEST_CASE(LuaPacts) game.executeAICommands(); BOOST_TEST_REQUIRE(player.IsAlly(1)); executeLua("assert(player:IsAlly(0))"); - BOOST_TEST_REQUIRE(!player.IsAttackable(1)); - executeLua("assert(not player:IsAttackable(0))"); + BOOST_TEST_REQUIRE(!player.CanAttack(1)); + executeLua("assert(not player:CanAttack(0))"); // check if callback onPactCreated was executed BOOST_TEST_REQUIRE(getLog() == "Pact created\n"); @@ -874,8 +874,8 @@ BOOST_AUTO_TEST_CASE(LuaPacts) player.CancelPact(PactType::TreatyOfAlliance, 1); BOOST_TEST_REQUIRE(!player.IsAlly(1)); executeLua("assert(not player:IsAlly(0))"); - BOOST_TEST_REQUIRE(player.IsAttackable(1)); - executeLua("assert(player:IsAttackable(0))"); + BOOST_TEST_REQUIRE(player.CanAttack(1)); + executeLua("assert(player:CanAttack(0))"); // check if callback onPactCanceled was executed BOOST_TEST_REQUIRE(getLog() == "Pact canceled\n"); @@ -885,12 +885,12 @@ BOOST_AUTO_TEST_CASE(LuaPacts) player.SuggestPact(1, PactType::NonAgressionPact, DURATION_INFINITE); game.executeAICommands(); // non aggression was created - BOOST_TEST_REQUIRE(!player.IsAttackable(1)); + BOOST_TEST_REQUIRE(!player.CanAttack(1)); BOOST_TEST_REQUIRE(getLog() == "Pact created\n"); // cancel pact by player and check state player.CancelPact(PactType::NonAgressionPact, 1); - BOOST_TEST_REQUIRE(player.IsAttackable(1)); + BOOST_TEST_REQUIRE(player.CanAttack(1)); BOOST_TEST_REQUIRE(getLog() == "Pact canceled\n"); const PostBox& postbox = world.GetPostMgr().AddPostBox(0); @@ -899,10 +899,27 @@ BOOST_AUTO_TEST_CASE(LuaPacts) game.executeAICommands(); const auto* msg = dynamic_cast(postbox.GetMsg(0)); this->AcceptPact(msg->GetPactId(), PactType::TreatyOfAlliance, 1); - BOOST_TEST_REQUIRE(!player.IsAttackable(1)); - executeLua("assert(not player:IsAttackable(0))"); + BOOST_TEST_REQUIRE(!player.CanAttack(1)); + executeLua("assert(not player:CanAttack(0))"); BOOST_TEST_REQUIRE(getLog() == "Pact created\n"); } +BOOST_AUTO_TEST_CASE(LuaOneSidedAlliance) +{ + initWorld(); + + auto& p0 = world.GetPlayer(0); + auto& p1 = world.GetPlayer(1); + p0.ps = PlayerState::AI; + p1.ps = PlayerState::AI; + + executeLua("rttr:GetPlayer(0):MakeOneSidedAllianceTo(1)"); + game.executeAICommands(); + BOOST_TEST_REQUIRE(p0.IsAlly(1) == true); + BOOST_TEST_REQUIRE(p1.IsAlly(0) == false); + BOOST_TEST_REQUIRE(p0.CanAttack(1) == false); + BOOST_TEST_REQUIRE(p1.CanAttack(0) == true); +} + BOOST_AUTO_TEST_SUITE_END() From b285d7ed22aef49e6b165c86268f1efbc3310df5 Mon Sep 17 00:00:00 2001 From: Jakub Audykowicz Date: Fri, 13 Dec 2024 22:52:02 +0100 Subject: [PATCH 2/2] Addon: catapults attack allies # Conflicts: # libs/s25main/addons/const_addons.h --- data/RTTR/campaigns/roman/MISS200.lua | 1 + data/RTTR/campaigns/roman/MISS201.lua | 1 + data/RTTR/campaigns/roman/MISS202.lua | 1 + data/RTTR/campaigns/roman/MISS203.lua | 1 + data/RTTR/campaigns/roman/MISS204.lua | 1 + data/RTTR/campaigns/roman/MISS205.lua | 1 + data/RTTR/campaigns/roman/MISS206.lua | 1 + data/RTTR/campaigns/roman/MISS207.lua | 1 + data/RTTR/campaigns/roman/MISS208.lua | 1 + data/RTTR/campaigns/roman/MISS209.lua | 1 + data/RTTR/campaigns/world/AFRICA.lua | 1 + data/RTTR/campaigns/world/AUSTRA.lua | 1 + data/RTTR/campaigns/world/EUROPE.lua | 1 + data/RTTR/campaigns/world/GREEN.lua | 1 + data/RTTR/campaigns/world/JAPAN.lua | 1 + data/RTTR/campaigns/world/NAMERICA.lua | 1 + data/RTTR/campaigns/world/NASIA.lua | 1 + data/RTTR/campaigns/world/SAMERICA.lua | 1 + data/RTTR/campaigns/world/SASIA.lua | 1 + libs/s25main/GlobalGameSettings.cpp | 1 + .../addons/AddonCatapultsAttackAllies.h | 18 +++++++++++++++ libs/s25main/addons/Addons.h | 1 + libs/s25main/addons/const_addons.h | 5 ++++- libs/s25main/figures/nofCatapultMan.cpp | 22 ++++++++++++++++--- libs/s25main/figures/nofCatapultMan.h | 2 ++ 25 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 libs/s25main/addons/AddonCatapultsAttackAllies.h diff --git a/data/RTTR/campaigns/roman/MISS200.lua b/data/RTTR/campaigns/roman/MISS200.lua index b0e22f0241..f4cfe12e8c 100644 --- a/data/RTTR/campaigns/roman/MISS200.lua +++ b/data/RTTR/campaigns/roman/MISS200.lua @@ -194,6 +194,7 @@ function onSettingsReady() checkVersion() rttr:Log("-----------------------\n MISS200.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/roman/MISS201.lua b/data/RTTR/campaigns/roman/MISS201.lua index 02d06accf2..f810663320 100644 --- a/data/RTTR/campaigns/roman/MISS201.lua +++ b/data/RTTR/campaigns/roman/MISS201.lua @@ -153,6 +153,7 @@ function onSettingsReady() checkVersion() rttr:Log("-----------------------\n MISS201.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/roman/MISS202.lua b/data/RTTR/campaigns/roman/MISS202.lua index d312eddd61..b738d450d9 100644 --- a/data/RTTR/campaigns/roman/MISS202.lua +++ b/data/RTTR/campaigns/roman/MISS202.lua @@ -105,6 +105,7 @@ function onSettingsReady() checkVersion() rttr:Log("-----------------------\n MISS202.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/roman/MISS203.lua b/data/RTTR/campaigns/roman/MISS203.lua index 0d883f98d5..57e0c9baa7 100644 --- a/data/RTTR/campaigns/roman/MISS203.lua +++ b/data/RTTR/campaigns/roman/MISS203.lua @@ -104,6 +104,7 @@ function onSettingsReady() checkVersion() rttr:Log("-----------------------\n MISS203.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/roman/MISS204.lua b/data/RTTR/campaigns/roman/MISS204.lua index 3f1da9b496..2eec58d4a7 100644 --- a/data/RTTR/campaigns/roman/MISS204.lua +++ b/data/RTTR/campaigns/roman/MISS204.lua @@ -98,6 +98,7 @@ function onSettingsReady() checkVersion() rttr:Log("-----------------------\n MISS204.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/roman/MISS205.lua b/data/RTTR/campaigns/roman/MISS205.lua index dfb859b3ad..d33dd7f348 100644 --- a/data/RTTR/campaigns/roman/MISS205.lua +++ b/data/RTTR/campaigns/roman/MISS205.lua @@ -98,6 +98,7 @@ function onSettingsReady() checkVersion() rttr:Log("-----------------------\n MISS205.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/roman/MISS206.lua b/data/RTTR/campaigns/roman/MISS206.lua index 34b491ca6f..ec0bc43803 100644 --- a/data/RTTR/campaigns/roman/MISS206.lua +++ b/data/RTTR/campaigns/roman/MISS206.lua @@ -98,6 +98,7 @@ function onSettingsReady() checkVersion() rttr:Log("-----------------------\n MISS206.lua loaded... \n-----------------------\n") rttr:ResetAddons() -- S2-settings + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/roman/MISS207.lua b/data/RTTR/campaigns/roman/MISS207.lua index cdbca3eab8..029d689923 100644 --- a/data/RTTR/campaigns/roman/MISS207.lua +++ b/data/RTTR/campaigns/roman/MISS207.lua @@ -86,6 +86,7 @@ function onSettingsReady() checkVersion() rttr:Log("-----------------------\n MISS207.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/roman/MISS208.lua b/data/RTTR/campaigns/roman/MISS208.lua index 6ebc11f370..1bd48d6b80 100644 --- a/data/RTTR/campaigns/roman/MISS208.lua +++ b/data/RTTR/campaigns/roman/MISS208.lua @@ -86,6 +86,7 @@ function onSettingsReady() checkVersion() rttr:Log("-----------------------\n MISS208.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/roman/MISS209.lua b/data/RTTR/campaigns/roman/MISS209.lua index 7355b3cc9a..404c0d1293 100644 --- a/data/RTTR/campaigns/roman/MISS209.lua +++ b/data/RTTR/campaigns/roman/MISS209.lua @@ -95,6 +95,7 @@ function onSettingsReady() checkVersion() rttr:Log("-----------------------\n MISS209.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/world/AFRICA.lua b/data/RTTR/campaigns/world/AFRICA.lua index 7f6a487085..6e47fe85d2 100644 --- a/data/RTTR/campaigns/world/AFRICA.lua +++ b/data/RTTR/campaigns/world/AFRICA.lua @@ -25,6 +25,7 @@ end function onSettingsReady() rttr:Log("-----------------------\n AFRICA.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/world/AUSTRA.lua b/data/RTTR/campaigns/world/AUSTRA.lua index 6cfbbdf515..b4f6714547 100644 --- a/data/RTTR/campaigns/world/AUSTRA.lua +++ b/data/RTTR/campaigns/world/AUSTRA.lua @@ -25,6 +25,7 @@ end function onSettingsReady() rttr:Log("-----------------------\n AUSTRA.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/world/EUROPE.lua b/data/RTTR/campaigns/world/EUROPE.lua index d18f7ecb06..3033b5cbe9 100644 --- a/data/RTTR/campaigns/world/EUROPE.lua +++ b/data/RTTR/campaigns/world/EUROPE.lua @@ -25,6 +25,7 @@ end function onSettingsReady() rttr:Log("-----------------------\n EUROPE.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/world/GREEN.lua b/data/RTTR/campaigns/world/GREEN.lua index ef14c6e500..d3bb222f56 100644 --- a/data/RTTR/campaigns/world/GREEN.lua +++ b/data/RTTR/campaigns/world/GREEN.lua @@ -25,6 +25,7 @@ end function onSettingsReady() rttr:Log("-----------------------\n GREEN.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/world/JAPAN.lua b/data/RTTR/campaigns/world/JAPAN.lua index 7777bfabea..5c8bda5feb 100644 --- a/data/RTTR/campaigns/world/JAPAN.lua +++ b/data/RTTR/campaigns/world/JAPAN.lua @@ -25,6 +25,7 @@ end function onSettingsReady() rttr:Log("-----------------------\n JAPAN.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/world/NAMERICA.lua b/data/RTTR/campaigns/world/NAMERICA.lua index 52c8264fe6..4a4ad22227 100644 --- a/data/RTTR/campaigns/world/NAMERICA.lua +++ b/data/RTTR/campaigns/world/NAMERICA.lua @@ -25,6 +25,7 @@ end function onSettingsReady() rttr:Log("-----------------------\n NAMERICA.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/world/NASIA.lua b/data/RTTR/campaigns/world/NASIA.lua index 29c88c73f7..8c3becb8e2 100644 --- a/data/RTTR/campaigns/world/NASIA.lua +++ b/data/RTTR/campaigns/world/NASIA.lua @@ -25,6 +25,7 @@ end function onSettingsReady() rttr:Log("-----------------------\n NASIA.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/world/SAMERICA.lua b/data/RTTR/campaigns/world/SAMERICA.lua index bc98c65f77..ad54e48cd8 100644 --- a/data/RTTR/campaigns/world/SAMERICA.lua +++ b/data/RTTR/campaigns/world/SAMERICA.lua @@ -25,6 +25,7 @@ end function onSettingsReady() rttr:Log("-----------------------\n SAMERICA.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/data/RTTR/campaigns/world/SASIA.lua b/data/RTTR/campaigns/world/SASIA.lua index f45868ba3f..80575475a2 100644 --- a/data/RTTR/campaigns/world/SASIA.lua +++ b/data/RTTR/campaigns/world/SASIA.lua @@ -25,6 +25,7 @@ end function onSettingsReady() rttr:Log("-----------------------\n SASIA.lua loaded... \n-----------------------\n") rttr:ResetAddons() + rttr:SetAddon(ADDON_CATAPULTS_ATTACK_ALLIES, true) rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true) rttr:SetGameSettings({ ["fow"] = EXP_CLASSIC, diff --git a/libs/s25main/GlobalGameSettings.cpp b/libs/s25main/GlobalGameSettings.cpp index 730af63fe2..4866fc386c 100644 --- a/libs/s25main/GlobalGameSettings.cpp +++ b/libs/s25main/GlobalGameSettings.cpp @@ -65,6 +65,7 @@ void GlobalGameSettings::registerAllAddons() AddonBattlefieldPromotion, AddonBurnDuration, AddonCatapultGraphics, + AddonCatapultsAttackAllies, AddonChangeGoldDeposits, AddonCharburner, AddonCoinsCapturedBld, diff --git a/libs/s25main/addons/AddonCatapultsAttackAllies.h b/libs/s25main/addons/AddonCatapultsAttackAllies.h new file mode 100644 index 0000000000..54c5613ab3 --- /dev/null +++ b/libs/s25main/addons/AddonCatapultsAttackAllies.h @@ -0,0 +1,18 @@ +// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "AddonBool.h" +#include "mygettext/mygettext.h" + +class AddonCatapultsAttackAllies : public AddonBool +{ +public: + AddonCatapultsAttackAllies() + : AddonBool(AddonId::CATAPULTS_ATTACK_ALLIES, AddonGroup::Military, _("Catapults attack allies"), + _("Catapults will throw stones at allied buildings\n" + "(as they did in S2)")) + {} +}; diff --git a/libs/s25main/addons/Addons.h b/libs/s25main/addons/Addons.h index f46b2a607d..67f53c9c2f 100644 --- a/libs/s25main/addons/Addons.h +++ b/libs/s25main/addons/Addons.h @@ -7,6 +7,7 @@ // Include with all addons #include "addons/AddonCatapultGraphics.h" +#include "addons/AddonCatapultsAttackAllies.h" #include "addons/AddonExhaustibleWater.h" #include "addons/AddonInexhaustibleMines.h" #include "addons/AddonLimitCatapults.h" diff --git a/libs/s25main/addons/const_addons.h b/libs/s25main/addons/const_addons.h index fbb3245090..84bfe2147d 100644 --- a/libs/s25main/addons/const_addons.h +++ b/libs/s25main/addons/const_addons.h @@ -26,6 +26,7 @@ // 00E Jonathan // 00F Jarno // 010 aztimh +// 011 kubaau // Do not forget to add your Addon to GlobalGameSettings::registerAllAddons @ GlobalGameSettings.cpp! // Never use a number twice! @@ -74,7 +75,9 @@ ENUM_WITH_STRING(AddonId, LIMIT_CATAPULTS = 0x00000000, INEXHAUSTIBLE_MINES = 0x AUTOFLAGS = 0x00F00000, - WINE = 0x01000000) + WINE = 0x01000000, + + CATAPULTS_ATTACK_ALLIES = 0x01100000) //-V:AddonId:801 enum class AddonGroup : unsigned diff --git a/libs/s25main/figures/nofCatapultMan.cpp b/libs/s25main/figures/nofCatapultMan.cpp index 7f6321db2a..3a92899f24 100644 --- a/libs/s25main/figures/nofCatapultMan.cpp +++ b/libs/s25main/figures/nofCatapultMan.cpp @@ -6,8 +6,10 @@ #include "CatapultStone.h" #include "EventManager.h" #include "GamePlayer.h" +#include "GlobalGameSettings.h" #include "Loader.h" #include "SerializedGameData.h" +#include "addons/AddonCatapultsAttackAllies.h" #include "buildings/nobMilitary.h" #include "buildings/nobUsual.h" #include "enum_cast.hpp" @@ -94,6 +96,22 @@ void nofCatapultMan::DrawWorking(DrawPoint drawPt) } } +bool nofCatapultMan::CanAttackBuilding(nobBaseMilitary* bld) const +{ + // only proper military buildings - no HQ's etc. + if(bld->GetGOT() != GO_Type::NobMilitary) + return false; + + if(bld->CanBeAttackedBy(player)) + return true; + + // anyone but ourselves + if(world->GetGGS().isEnabled(AddonId::CATAPULTS_ATTACK_ALLIES)) + return bld->GetPlayer() != player; + + return false; +} + void nofCatapultMan::HandleDerivedEvent(const unsigned /*id*/) { switch(state) @@ -109,9 +127,7 @@ void nofCatapultMan::HandleDerivedEvent(const unsigned /*id*/) sortedMilitaryBlds buildings = world->LookForMilitaryBuildings(pos, 3); for(auto* building : buildings) { - // Auch ein richtiges Militärgebäude (kein HQ usw.), - if(building->GetGOT() == GO_Type::NobMilitary - && world->GetPlayer(player).CanAttack(building->GetPlayer())) + if(CanAttackBuilding(building)) { // Was nicht im Nebel liegt und auch schon besetzt wurde (nicht neu gebaut)? if(world->GetNode(building->GetPos()).fow[player].visibility == Visibility::Visible diff --git a/libs/s25main/figures/nofCatapultMan.h b/libs/s25main/figures/nofCatapultMan.h index 737cf833d4..c19380a9fc 100644 --- a/libs/s25main/figures/nofCatapultMan.h +++ b/libs/s25main/figures/nofCatapultMan.h @@ -6,6 +6,7 @@ #include "nofBuildingWorker.h" class SerializedGameData; +class nobBaseMilitary; class nobUsual; /// Arbeiter im Katapult @@ -39,6 +40,7 @@ class nofCatapultMan : public nofBuildingWorker void DrawWorking(DrawPoint drawPt) override; /// Id in jobs.bob or carrier.bob when carrying a ware unsigned short GetCarryID() const override { return 0; } + bool CanAttackBuilding(nobBaseMilitary* bld) const; public: nofCatapultMan(MapPoint pos, unsigned char player, nobUsual* workplace);