Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion libs/s25main/CheatCommandTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,26 @@ void CheatCommandTracker::onChatCommand(const std::string& cmd)

void CheatCommandTracker::onSpecialKeyEvent(const KeyEvent& ke)
{
if(ke.ctrl && ke.shift)
{
if(ke.kt >= KeyType::F1 && ke.kt <= KeyType::F8)
cheats_.destroyBuildings({static_cast<unsigned>(ke.kt) - static_cast<unsigned>(KeyType::F1)});
else if(ke.kt == KeyType::F9)
cheats_.destroyAllAIBuildings();

return;
}

switch(ke.kt)
{
case KeyType::F7: cheats_.toggleAllVisible(); break;
case KeyType::F7:
{
if(ke.alt)
cheats_.toggleResourceRevealMode();
else
cheats_.toggleAllVisible();
}
break;
case KeyType::F10: cheats_.toggleHumanAIPlayer(); break;
default: break;
}
Expand Down
12 changes: 11 additions & 1 deletion libs/s25main/CheatCommandTracker.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org)
// Copyright (C) 2025 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

Expand All @@ -15,11 +15,21 @@ class CheatCommandTracker
public:
CheatCommandTracker(Cheats& cheats);

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

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

Cheats& cheats_;
Expand Down
82 changes: 78 additions & 4 deletions libs/s25main/Cheats.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org)
// Copyright (C) 2024-2026 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#include "Cheats.h"
#include "GameInterface.h"
#include "GamePlayer.h"
#include "RttrForeachPt.h"
#include "buildings/nobHQ.h"
#include "factories/BuildingFactory.h"
#include "factories/GameCommandFactory.h"
#include "network/GameClient.h"
#include "world/GameWorldBase.h"

Cheats::Cheats(GameWorldBase& world) : world_(world) {}
Cheats::Cheats(GameWorldBase& world, GameCommandFactory& gcFactory) : world_(world), gcFactory_(gcFactory) {}

bool Cheats::areCheatsAllowed() const
{
Expand Down Expand Up @@ -56,6 +61,30 @@ void Cheats::toggleShowEnemyProductivityOverlay()
shouldShowEnemyProductivityOverlay_ = !shouldShowEnemyProductivityOverlay_;
}

bool Cheats::canPlaceCheatBuilding(const MapPoint& mp) const
{
if(!isCheatModeOn())
return false;

// It seems that in the original game you can only build headquarters in unoccupied territory at least 2 nodes
// away from any border markers and that it doesn't need more bq than a hut.
const MapNode& node = world_.GetNode(mp);
return !node.owner && !world_.IsAnyNeighborOwned(mp) && node.bq >= BuildingQuality::Hut;
}

void Cheats::placeCheatBuilding(const MapPoint& mp, const GamePlayer& player)
{
if(!canPlaceCheatBuilding(mp))
return;

// The new HQ will have default resources.
// In the original game, new HQs created in the Roman campaign had no resources.
world_.DestroyNO(mp, false); // if CanPlaceCheatBuilding is true then this must be safe to destroy
auto* hq =
BuildingFactory::CreateBuilding(world_, BuildingType::Headquarters, mp, player.GetPlayerId(), player.nation);
static_cast<nobHQ*>(hq)->SetIsTent(player.IsHQTent());
}

void Cheats::toggleHumanAIPlayer()
{
if(isCheatModeOn() && !GAMECLIENT.IsReplayModeOn())
Expand All @@ -65,10 +94,52 @@ void Cheats::toggleHumanAIPlayer()
}
}

void Cheats::armageddon() const
void Cheats::armageddon()
{
if(isCheatModeOn())
GAMECLIENT.CheatArmageddon();
gcFactory_.CheatArmageddon();
}

Cheats::ResourceRevealMode Cheats::getResourceRevealMode() const
{
return isCheatModeOn() ? resourceRevealMode_ : ResourceRevealMode::Nothing;
}

void Cheats::toggleResourceRevealMode()
{
switch(resourceRevealMode_)
{
case ResourceRevealMode::Nothing: resourceRevealMode_ = ResourceRevealMode::Ores; break;
case ResourceRevealMode::Ores: resourceRevealMode_ = ResourceRevealMode::Fish; break;
case ResourceRevealMode::Fish: resourceRevealMode_ = ResourceRevealMode::Water; break;
default: resourceRevealMode_ = ResourceRevealMode::Nothing; break;
}
}

void Cheats::destroyBuildings(const PlayerIDSet& playerIds)
{
if(!isCheatModeOn())
return;

RTTR_FOREACH_PT(MapPoint, world_.GetSize())
{
if(world_.GetNO(pt)->GetType() == NodalObjectType::Building && playerIds.count(world_.GetNode(pt).owner - 1))
world_.DestroyNO(pt);
Comment thread
Flamefire marked this conversation as resolved.
}
}

void Cheats::destroyAllAIBuildings()
{
if(!isCheatModeOn())
return;

PlayerIDSet ais;
for(auto i = 0u; i < world_.GetNumPlayers(); ++i)
{
if(!world_.GetPlayer(i).isHuman())
ais.insert(i);
Comment thread
Flamefire marked this conversation as resolved.
}
destroyBuildings(ais);
}

void Cheats::turnAllCheatsOff()
Expand All @@ -80,5 +151,8 @@ void Cheats::turnAllCheatsOff()
if(shouldShowEnemyProductivityOverlay_)
toggleShowEnemyProductivityOverlay();
if(isHumanAIPlayer_)
{
toggleHumanAIPlayer();
isHumanAIPlayer_ = false;
}
}
76 changes: 73 additions & 3 deletions libs/s25main/Cheats.h
Original file line number Diff line number Diff line change
@@ -1,22 +1,43 @@
// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org)
// Copyright (C) 2025 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "gameTypes/MapCoordinates.h"
#include <memory>
#include <string>
#include <unordered_set>

class GamePlayer;
class GameWorldBase;
class GameCommandFactory;

class Cheats
{
public:
Cheats(GameWorldBase& world);
Cheats(GameWorldBase& world, GameCommandFactory& gcFactory);

bool areCheatsAllowed() const;

/** Toggles cheat mode on and off.
* Cheat mode needs to be on for any cheats to trigger.
*/
void toggleCheatMode();
/** Check if cheat mode is on (e.g. to draw special sprites or enable or all buildings).
* Cheat mode needs to be on for any cheats to trigger.
*
* @return true if cheat mode is on, false otherwise
*/
bool isCheatModeOn() const { return isCheatModeOn_; }

// Classic S2 cheats

/** The classic F7 cheat.
* Does not modify game state, merely tricks clients into revealing the whole map.
* In the background, visibility is tracked as expected, i.e. if you reveal the map, send a scout and unreveal the
* map, you will see what was scouted.
*/
void toggleAllVisible();
bool isAllVisible() const { return isAllVisible_; }

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

/** The classic build headquarters cheat.
* Check if the cheat building can be placed at the chosen point.
*
* @param mp - The map point, e.g. where the user clicked to open the activity window.
* @return true if the building can be placed, false otherwise
*/
bool canPlaceCheatBuilding(const MapPoint& mp) const;
/** The classic build headquarters cheat.
* Place the cheat HQ building at the chosen point.
* The building is immediately fully built, there is no need for a building site.
*
* @param mp - The map point at which to place the building.
* @param player - The player to whom the building should belong.
*/
void placeCheatBuilding(const MapPoint& mp, const GamePlayer& player);

// RTTR cheats

/** Shares control of the (human) user's country with the AI. Both the user and the AI retain full control of the
* country, so the user can observe what the AI does or "cooperate" with it.
*/
void toggleHumanAIPlayer();
void armageddon() const;

void armageddon();

enum class ResourceRevealMode
{
// Order is important as each mode includes the previous ones
Nothing,
Ores,
Fish, /// Ores + Fish
Water /// Ores + Fish + Water
};
/** Tells clients which resources to reveal:
* Nothing - reveal nothing
* Ores - reveal ores
* Fish - reveal ores and fish
* Water - reveal ores, fish and water
*/
ResourceRevealMode getResourceRevealMode() const;
void toggleResourceRevealMode();

using PlayerIDSet = std::unordered_set<unsigned>;
/** Destroys all buildings of given players, effectively defeating them.
*
* @param playerIds - Set of IDs of players.
*/
void destroyBuildings(const PlayerIDSet& playerIds);
/// Destroys all buildings of AI players.
void destroyAllAIBuildings();

private:
void turnAllCheatsOff();
Expand All @@ -39,4 +107,6 @@ class Cheats
bool shouldShowEnemyProductivityOverlay_ = false;
bool isHumanAIPlayer_ = false;
GameWorldBase& world_;
Comment thread
kubaau marked this conversation as resolved.
GameCommandFactory& gcFactory_;
ResourceRevealMode resourceRevealMode_ = ResourceRevealMode::Nothing;
};
27 changes: 25 additions & 2 deletions libs/s25main/GamePlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,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"
Expand Down Expand Up @@ -412,6 +413,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());
Expand All @@ -431,8 +445,11 @@ void GamePlayer::AddBuilding(noBuilding* bld, BuildingType bldType)
for(noShip* ship : ships)
ship->NewHarborBuilt(static_cast<nobHarborBuilding*>(bld));
} else if(bldType == BuildingType::Headquarters)
hqPos = bld->GetPos();
else if(BuildingProperties::IsMilitary(bldType))
{
// If there is more than one HQ, keep the original position.
if(!hqPos.isValid())
hqPos = bld->GetPos();
} else if(BuildingProperties::IsMilitary(bldType))
{
auto* milBld = static_cast<nobMilitary*>(bld);
// New built? -> Calculate frontier distance
Expand Down Expand Up @@ -1401,6 +1418,12 @@ void GamePlayer::TestDefeat()
Surrender();
}

nobHQ* GamePlayer::GetHQ() const
{
const MapPoint& hqPos = GetHQPos();
return const_cast<nobHQ*>(hqPos.isValid() ? GetGameWorld().GetSpecObj<nobHQ>(hqPos) : nullptr);
}

void GamePlayer::Surrender()
{
if(isDefeated)
Expand Down
5 changes: 5 additions & 0 deletions libs/s25main/GamePlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class noShip;
class nobBaseMilitary;
class nobBaseWarehouse;
class nobHarborBuilding;
class nobHQ;
class nobMilitary;
class nofCarrier;
class nofFlagWorker;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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...)
Expand Down
22 changes: 18 additions & 4 deletions libs/s25main/buildings/noBaseBuilding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,21 @@ noBaseBuilding::noBaseBuilding(const NodalObjectType nop, const BuildingType typ
{
for(const Direction i : {Direction::West, Direction::NorthWest, Direction::NorthEast})
{
MapPoint pos2 = world->GetNeighbour(pos, i);
world->DestroyNO(pos2, false);
world->SetNO(pos2, new noExtension(this));
const MapPoint neighbor = world->GetNeighbour(pos, i);

if(type == BuildingType::Headquarters)
{
const NodalObjectType neighborNoType = world->GetNO(neighbor)->GetType();
// Don't replace nearby static objects or trees. Needed for "build headquarters" cheat to work like in
// the original. This situation shouldn't happen any other way (can't normally build big buildings right
// next to static objects or trees). Trees which be remain because of this will be replaced by
// extensions instead of stumps if they are cut while still right next to the HQ.
if(neighborNoType == NodalObjectType::Object || neighborNoType == NodalObjectType::Tree)
continue;
}

world->DestroyNO(neighbor, false);
world->SetNO(neighbor, new noExtension(this));
}
}
}
Expand Down Expand Up @@ -220,7 +232,9 @@ void noBaseBuilding::DestroyBuildingExtensions()
{
for(const Direction i : {Direction::West, Direction::NorthWest, Direction::NorthEast})
{
world->DestroyNO(world->GetNeighbour(pos, i));
const MapPoint neighbor = world->GetNeighbour(pos, i);
if(world->GetNO(neighbor)->GetType() == NodalObjectType::Extension)
world->DestroyNO(neighbor);
}
}
}
Expand Down
Loading
Loading