Skip to content

Commit dc0ce04

Browse files
authored
Merge pull request #1679 from kubaau/cheats2
2 parents fccf0cc + ebadacf commit dc0ce04

22 files changed

Lines changed: 923 additions & 105 deletions

libs/s25main/CheatCommandTracker.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,26 @@ void CheatCommandTracker::onChatCommand(const std::string& cmd)
4545

4646
void CheatCommandTracker::onSpecialKeyEvent(const KeyEvent& ke)
4747
{
48+
if(ke.ctrl && ke.shift)
49+
{
50+
if(ke.kt >= KeyType::F1 && ke.kt <= KeyType::F8)
51+
cheats_.destroyBuildings({static_cast<unsigned>(ke.kt) - static_cast<unsigned>(KeyType::F1)});
52+
else if(ke.kt == KeyType::F9)
53+
cheats_.destroyAllAIBuildings();
54+
55+
return;
56+
}
57+
4858
switch(ke.kt)
4959
{
50-
case KeyType::F7: cheats_.toggleAllVisible(); break;
60+
case KeyType::F7:
61+
{
62+
if(ke.alt)
63+
cheats_.toggleResourceRevealMode();
64+
else
65+
cheats_.toggleAllVisible();
66+
}
67+
break;
5168
case KeyType::F10: cheats_.toggleHumanAIPlayer(); break;
5269
default: break;
5370
}

libs/s25main/CheatCommandTracker.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org)
1+
// Copyright (C) 2025 Settlers Freaks (sf-team at siedler25.org)
22
//
33
// SPDX-License-Identifier: GPL-2.0-or-later
44

@@ -15,11 +15,21 @@ class CheatCommandTracker
1515
public:
1616
CheatCommandTracker(Cheats& cheats);
1717

18+
/**
19+
* Tracks keyboard events related to cheats and triggers the actual cheats.
20+
* Calls related private methods of this class in order but returns at the first success (return true).
21+
*/
1822
void onKeyEvent(const KeyEvent& ke);
23+
/**
24+
* Tracks chat commands related to cheats and triggers the actual cheats.
25+
*/
1926
void onChatCommand(const std::string& cmd);
2027

2128
private:
29+
/// Handle possible cheat events triggered by Keys different than KeyType::Char (e.g. F-keys).
2230
void onSpecialKeyEvent(const KeyEvent& ke);
31+
/// Tracks keyboard events related to cheats and triggers the actual cheats for character keys,
32+
/// and e.g.enabling cheat mode by typing "winter"
2333
void onCharKeyEvent(const KeyEvent& ke);
2434

2535
Cheats& cheats_;

libs/s25main/Cheats.cpp

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org)
1+
// Copyright (C) 2024-2026 Settlers Freaks (sf-team at siedler25.org)
22
//
33
// SPDX-License-Identifier: GPL-2.0-or-later
44

55
#include "Cheats.h"
66
#include "GameInterface.h"
7+
#include "GamePlayer.h"
8+
#include "RttrForeachPt.h"
9+
#include "buildings/nobHQ.h"
10+
#include "factories/BuildingFactory.h"
11+
#include "factories/GameCommandFactory.h"
712
#include "network/GameClient.h"
813
#include "world/GameWorldBase.h"
914

10-
Cheats::Cheats(GameWorldBase& world) : world_(world) {}
15+
Cheats::Cheats(GameWorldBase& world, GameCommandFactory& gcFactory) : world_(world), gcFactory_(gcFactory) {}
1116

1217
bool Cheats::areCheatsAllowed() const
1318
{
@@ -56,6 +61,30 @@ void Cheats::toggleShowEnemyProductivityOverlay()
5661
shouldShowEnemyProductivityOverlay_ = !shouldShowEnemyProductivityOverlay_;
5762
}
5863

64+
bool Cheats::canPlaceCheatBuilding(const MapPoint& mp) const
65+
{
66+
if(!isCheatModeOn())
67+
return false;
68+
69+
// It seems that in the original game you can only build headquarters in unoccupied territory at least 2 nodes
70+
// away from any border markers and that it doesn't need more bq than a hut.
71+
const MapNode& node = world_.GetNode(mp);
72+
return !node.owner && !world_.IsAnyNeighborOwned(mp) && node.bq >= BuildingQuality::Hut;
73+
}
74+
75+
void Cheats::placeCheatBuilding(const MapPoint& mp, const GamePlayer& player)
76+
{
77+
if(!canPlaceCheatBuilding(mp))
78+
return;
79+
80+
// The new HQ will have default resources.
81+
// In the original game, new HQs created in the Roman campaign had no resources.
82+
world_.DestroyNO(mp, false); // if CanPlaceCheatBuilding is true then this must be safe to destroy
83+
auto* hq =
84+
BuildingFactory::CreateBuilding(world_, BuildingType::Headquarters, mp, player.GetPlayerId(), player.nation);
85+
static_cast<nobHQ*>(hq)->SetIsTent(player.IsHQTent());
86+
}
87+
5988
void Cheats::toggleHumanAIPlayer()
6089
{
6190
if(isCheatModeOn() && !GAMECLIENT.IsReplayModeOn())
@@ -65,10 +94,52 @@ void Cheats::toggleHumanAIPlayer()
6594
}
6695
}
6796

68-
void Cheats::armageddon() const
97+
void Cheats::armageddon()
6998
{
7099
if(isCheatModeOn())
71-
GAMECLIENT.CheatArmageddon();
100+
gcFactory_.CheatArmageddon();
101+
}
102+
103+
Cheats::ResourceRevealMode Cheats::getResourceRevealMode() const
104+
{
105+
return isCheatModeOn() ? resourceRevealMode_ : ResourceRevealMode::Nothing;
106+
}
107+
108+
void Cheats::toggleResourceRevealMode()
109+
{
110+
switch(resourceRevealMode_)
111+
{
112+
case ResourceRevealMode::Nothing: resourceRevealMode_ = ResourceRevealMode::Ores; break;
113+
case ResourceRevealMode::Ores: resourceRevealMode_ = ResourceRevealMode::Fish; break;
114+
case ResourceRevealMode::Fish: resourceRevealMode_ = ResourceRevealMode::Water; break;
115+
default: resourceRevealMode_ = ResourceRevealMode::Nothing; break;
116+
}
117+
}
118+
119+
void Cheats::destroyBuildings(const PlayerIDSet& playerIds)
120+
{
121+
if(!isCheatModeOn())
122+
return;
123+
124+
RTTR_FOREACH_PT(MapPoint, world_.GetSize())
125+
{
126+
if(world_.GetNO(pt)->GetType() == NodalObjectType::Building && playerIds.count(world_.GetNode(pt).owner - 1))
127+
world_.DestroyNO(pt);
128+
}
129+
}
130+
131+
void Cheats::destroyAllAIBuildings()
132+
{
133+
if(!isCheatModeOn())
134+
return;
135+
136+
PlayerIDSet ais;
137+
for(auto i = 0u; i < world_.GetNumPlayers(); ++i)
138+
{
139+
if(!world_.GetPlayer(i).isHuman())
140+
ais.insert(i);
141+
}
142+
destroyBuildings(ais);
72143
}
73144

74145
void Cheats::turnAllCheatsOff()
@@ -80,5 +151,8 @@ void Cheats::turnAllCheatsOff()
80151
if(shouldShowEnemyProductivityOverlay_)
81152
toggleShowEnemyProductivityOverlay();
82153
if(isHumanAIPlayer_)
154+
{
83155
toggleHumanAIPlayer();
156+
isHumanAIPlayer_ = false;
157+
}
84158
}

libs/s25main/Cheats.h

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,43 @@
1-
// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org)
1+
// Copyright (C) 2025 Settlers Freaks (sf-team at siedler25.org)
22
//
33
// SPDX-License-Identifier: GPL-2.0-or-later
44

55
#pragma once
66

7+
#include "gameTypes/MapCoordinates.h"
8+
#include <memory>
9+
#include <string>
10+
#include <unordered_set>
11+
12+
class GamePlayer;
713
class GameWorldBase;
14+
class GameCommandFactory;
815

916
class Cheats
1017
{
1118
public:
12-
Cheats(GameWorldBase& world);
19+
Cheats(GameWorldBase& world, GameCommandFactory& gcFactory);
1320

1421
bool areCheatsAllowed() const;
1522

23+
/** Toggles cheat mode on and off.
24+
* Cheat mode needs to be on for any cheats to trigger.
25+
*/
1626
void toggleCheatMode();
27+
/** Check if cheat mode is on (e.g. to draw special sprites or enable or all buildings).
28+
* Cheat mode needs to be on for any cheats to trigger.
29+
*
30+
* @return true if cheat mode is on, false otherwise
31+
*/
1732
bool isCheatModeOn() const { return isCheatModeOn_; }
1833

1934
// Classic S2 cheats
35+
36+
/** The classic F7 cheat.
37+
* Does not modify game state, merely tricks clients into revealing the whole map.
38+
* In the background, visibility is tracked as expected, i.e. if you reveal the map, send a scout and unreveal the
39+
* map, you will see what was scouted.
40+
*/
2041
void toggleAllVisible();
2142
bool isAllVisible() const { return isAllVisible_; }
2243

@@ -26,9 +47,56 @@ class Cheats
2647
void toggleShowEnemyProductivityOverlay();
2748
bool shouldShowEnemyProductivityOverlay() const { return shouldShowEnemyProductivityOverlay_; }
2849

50+
/** The classic build headquarters cheat.
51+
* Check if the cheat building can be placed at the chosen point.
52+
*
53+
* @param mp - The map point, e.g. where the user clicked to open the activity window.
54+
* @return true if the building can be placed, false otherwise
55+
*/
56+
bool canPlaceCheatBuilding(const MapPoint& mp) const;
57+
/** The classic build headquarters cheat.
58+
* Place the cheat HQ building at the chosen point.
59+
* The building is immediately fully built, there is no need for a building site.
60+
*
61+
* @param mp - The map point at which to place the building.
62+
* @param player - The player to whom the building should belong.
63+
*/
64+
void placeCheatBuilding(const MapPoint& mp, const GamePlayer& player);
65+
2966
// RTTR cheats
67+
68+
/** Shares control of the (human) user's country with the AI. Both the user and the AI retain full control of the
69+
* country, so the user can observe what the AI does or "cooperate" with it.
70+
*/
3071
void toggleHumanAIPlayer();
31-
void armageddon() const;
72+
73+
void armageddon();
74+
75+
enum class ResourceRevealMode
76+
{
77+
// Order is important as each mode includes the previous ones
78+
Nothing,
79+
Ores,
80+
Fish, /// Ores + Fish
81+
Water /// Ores + Fish + Water
82+
};
83+
/** Tells clients which resources to reveal:
84+
* Nothing - reveal nothing
85+
* Ores - reveal ores
86+
* Fish - reveal ores and fish
87+
* Water - reveal ores, fish and water
88+
*/
89+
ResourceRevealMode getResourceRevealMode() const;
90+
void toggleResourceRevealMode();
91+
92+
using PlayerIDSet = std::unordered_set<unsigned>;
93+
/** Destroys all buildings of given players, effectively defeating them.
94+
*
95+
* @param playerIds - Set of IDs of players.
96+
*/
97+
void destroyBuildings(const PlayerIDSet& playerIds);
98+
/// Destroys all buildings of AI players.
99+
void destroyAllAIBuildings();
32100

33101
private:
34102
void turnAllCheatsOff();
@@ -39,4 +107,6 @@ class Cheats
39107
bool shouldShowEnemyProductivityOverlay_ = false;
40108
bool isHumanAIPlayer_ = false;
41109
GameWorldBase& world_;
110+
GameCommandFactory& gcFactory_;
111+
ResourceRevealMode resourceRevealMode_ = ResourceRevealMode::Nothing;
42112
};

libs/s25main/GamePlayer.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "WineLoader.h"
1616
#include "addons/const_addons.h"
1717
#include "buildings/noBuildingSite.h"
18+
#include "buildings/nobHQ.h"
1819
#include "buildings/nobHarborBuilding.h"
1920
#include "buildings/nobMilitary.h"
2021
#include "buildings/nobUsual.h"
@@ -412,6 +413,19 @@ void GamePlayer::RemoveBuildingSite(noBuildingSite* bldSite)
412413
buildings.Remove(bldSite);
413414
}
414415

416+
bool GamePlayer::IsHQTent() const
417+
{
418+
if(const nobHQ* hq = GetHQ())
419+
return hq->IsTent();
420+
return false;
421+
}
422+
423+
void GamePlayer::SetHQIsTent(bool isTent)
424+
{
425+
if(nobHQ* hq = GetHQ())
426+
hq->SetIsTent(isTent);
427+
}
428+
415429
void GamePlayer::AddBuilding(noBuilding* bld, BuildingType bldType)
416430
{
417431
RTTR_Assert(bld->GetPlayer() == GetPlayerId());
@@ -431,8 +445,11 @@ void GamePlayer::AddBuilding(noBuilding* bld, BuildingType bldType)
431445
for(noShip* ship : ships)
432446
ship->NewHarborBuilt(static_cast<nobHarborBuilding*>(bld));
433447
} else if(bldType == BuildingType::Headquarters)
434-
hqPos = bld->GetPos();
435-
else if(BuildingProperties::IsMilitary(bldType))
448+
{
449+
// If there is more than one HQ, keep the original position.
450+
if(!hqPos.isValid())
451+
hqPos = bld->GetPos();
452+
} else if(BuildingProperties::IsMilitary(bldType))
436453
{
437454
auto* milBld = static_cast<nobMilitary*>(bld);
438455
// New built? -> Calculate frontier distance
@@ -1401,6 +1418,12 @@ void GamePlayer::TestDefeat()
14011418
Surrender();
14021419
}
14031420

1421+
nobHQ* GamePlayer::GetHQ() const
1422+
{
1423+
const MapPoint& hqPos = GetHQPos();
1424+
return const_cast<nobHQ*>(hqPos.isValid() ? GetGameWorld().GetSpecObj<nobHQ>(hqPos) : nullptr);
1425+
}
1426+
14041427
void GamePlayer::Surrender()
14051428
{
14061429
if(isDefeated)

libs/s25main/GamePlayer.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class noShip;
3131
class nobBaseMilitary;
3232
class nobBaseWarehouse;
3333
class nobHarborBuilding;
34+
class nobHQ;
3435
class nobMilitary;
3536
class nofCarrier;
3637
class nofFlagWorker;
@@ -91,6 +92,9 @@ class GamePlayer : public GamePlayerInfo
9192
const GameWorld& GetGameWorld() const { return world; }
9293

9394
const MapPoint& GetHQPos() const { return hqPos; }
95+
bool IsHQTent() const;
96+
void SetHQIsTent(bool isTent);
97+
9498
void AddBuilding(noBuilding* bld, BuildingType bldType);
9599
void RemoveBuilding(noBuilding* bld, BuildingType bldType);
96100
void AddBuildingSite(noBuildingSite* bldSite);
@@ -426,6 +430,7 @@ class GamePlayer : public GamePlayerInfo
426430
bool FindWarehouseForJob(Job job, noRoadNode* goal) const;
427431
/// Prüft, ob der Spieler besiegt wurde
428432
void TestDefeat();
433+
nobHQ* GetHQ() const;
429434

430435
//////////////////////////////////////////////////////////////////////////
431436
/// Unsynchronized state (e.g. lua, gui...)

libs/s25main/buildings/noBaseBuilding.cpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,21 @@ noBaseBuilding::noBaseBuilding(const NodalObjectType nop, const BuildingType typ
6060
{
6161
for(const Direction i : {Direction::West, Direction::NorthWest, Direction::NorthEast})
6262
{
63-
MapPoint pos2 = world->GetNeighbour(pos, i);
64-
world->DestroyNO(pos2, false);
65-
world->SetNO(pos2, new noExtension(this));
63+
const MapPoint neighbor = world->GetNeighbour(pos, i);
64+
65+
if(type == BuildingType::Headquarters)
66+
{
67+
const NodalObjectType neighborNoType = world->GetNO(neighbor)->GetType();
68+
// Don't replace nearby static objects or trees. Needed for "build headquarters" cheat to work like in
69+
// the original. This situation shouldn't happen any other way (can't normally build big buildings right
70+
// next to static objects or trees). Trees which be remain because of this will be replaced by
71+
// extensions instead of stumps if they are cut while still right next to the HQ.
72+
if(neighborNoType == NodalObjectType::Object || neighborNoType == NodalObjectType::Tree)
73+
continue;
74+
}
75+
76+
world->DestroyNO(neighbor, false);
77+
world->SetNO(neighbor, new noExtension(this));
6678
}
6779
}
6880
}
@@ -220,7 +232,9 @@ void noBaseBuilding::DestroyBuildingExtensions()
220232
{
221233
for(const Direction i : {Direction::West, Direction::NorthWest, Direction::NorthEast})
222234
{
223-
world->DestroyNO(world->GetNeighbour(pos, i));
235+
const MapPoint neighbor = world->GetNeighbour(pos, i);
236+
if(world->GetNO(neighbor)->GetType() == NodalObjectType::Extension)
237+
world->DestroyNO(neighbor);
224238
}
225239
}
226240
}

0 commit comments

Comments
 (0)