Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add all classic S2 cheats and a few more #1679

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions extras/videoDrivers/SDL2/VideoSDL2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,12 @@ bool VideoSDL2::MessageLoop()
// Die 12 F-Tasten
if(ev.key.keysym.sym >= SDLK_F1 && ev.key.keysym.sym <= SDLK_F12)
ke.kt = static_cast<KeyType>(rttr::enum_cast(KeyType::F1) + ev.key.keysym.sym - SDLK_F1);

if((SDL_GetModState() & KMOD_ALT) && isdigit(ev.key.keysym.sym))
{
ke.kt = KeyType::Char;
ke.c = ev.key.keysym.sym;
}
}
break;
case SDLK_RETURN: ke.kt = KeyType::Return; break;
Expand Down
87 changes: 87 additions & 0 deletions libs/s25main/CheatCommandTracker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#include "CheatCommandTracker.h"
#include "Cheats.h"
#include "driver/KeyEvent.h"

namespace {
auto makeCircularBuffer(const std::string& str)
{
return boost::circular_buffer<char>{cbegin(str), cend(str)};
}
const auto cheatStr = makeCircularBuffer("winter");
} // namespace

CheatCommandTracker::CheatCommandTracker(Cheats& cheats) : cheats_(cheats), lastChars_(cheatStr.size()) {}

void CheatCommandTracker::trackKeyEvent(const KeyEvent& ke)
{
if(trackSpecialKeyEvent(ke) || trackSpeedKeyEvent(ke))
{
lastChars_.clear();
return;
}

trackCharKeyEvent(ke);
}

void CheatCommandTracker::trackChatCommand(const std::string& cmd)
{
if(cmd == "apocalypsis")
cheats_.armageddon();
}

bool CheatCommandTracker::trackSpecialKeyEvent(const KeyEvent& ke)
{
if(ke.kt == KeyType::Char)
return false;

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 true;
}

switch(ke.kt)
{
case KeyType::F7:
{
if(ke.alt)
cheats_.toggleResourceRevealMode();
else
cheats_.toggleAllVisible();
}
break;
case KeyType::F10: cheats_.toggleHumanAIPlayer(); break;
default: break;
}

return true;
}

bool CheatCommandTracker::trackSpeedKeyEvent(const KeyEvent& ke)
{
const char c = ke.c;
if(ke.alt && c >= '1' && c <= '6')
{
cheats_.setGameSpeed(c - '1');
return true;
}
return false;
}

bool CheatCommandTracker::trackCharKeyEvent(const KeyEvent& ke)
{
lastChars_.push_back(ke.c);

if(lastChars_ == cheatStr)
cheats_.toggleCheatMode();

return true;
}
57 changes: 57 additions & 0 deletions libs/s25main/CheatCommandTracker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <boost/circular_buffer.hpp>
#include <string>

class Cheats;
struct KeyEvent;

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).
*
* @param ke - The keyboard event encountered.
*/
void trackKeyEvent(const KeyEvent& ke);

/** Tracks chat commands related to cheats and triggers the actual cheats.
*
* @param cmd - The chat command to track.
*/
void trackChatCommand(const std::string& cmd);

private:
/** Tracks keyboard events related to cheats and triggers the actual cheats, but only tracks events of type
* different than KeyType::Char (e.g. F-keys).
*
* @param ke - The keyboard event encountered.
* @return true if keyboard event was NOT of type KeyType::Char, false otherwise
*/
bool trackSpecialKeyEvent(const KeyEvent& ke);

/** Tracks keyboard events related to game speed cheats (ALT+1..ALT+6) and triggers the actual cheats.
*
* @param ke - The keyboard event encountered.
* @return true if keyboard event was related to game speed cheats, false otherwise
*/
bool trackSpeedKeyEvent(const KeyEvent& ke);

/** Tracks keyboard events related to cheats and triggers the actual cheats, but only tracks events of type
* KeyType::Char (e.g. enabling cheat mode by typing "winter").
*
* @param ke - The keyboard event encountered.
* @return always true
*/
bool trackCharKeyEvent(const KeyEvent& ke);

Cheats& cheats_;
boost::circular_buffer<char> lastChars_;
};
150 changes: 150 additions & 0 deletions libs/s25main/Cheats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

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

Cheats::Cheats(GameWorldBase& world) : cheatCmdTracker_(std::make_unique<CheatCommandTracker>(*this)), world_(world) {}

Cheats::~Cheats() = default;

void Cheats::trackKeyEvent(const KeyEvent& ke)
{
if(!canCheatModeBeOn())
return;

cheatCmdTracker_->trackKeyEvent(ke);
}

void Cheats::trackChatCommand(const std::string& cmd)
{
if(!canCheatModeBeOn())
return;

cheatCmdTracker_->trackChatCommand(cmd);
}

void Cheats::toggleCheatMode()
{
if(!canCheatModeBeOn())
return;

isCheatModeOn_ = !isCheatModeOn_;
}

void Cheats::toggleAllVisible()
{
// This is actually the behavior of the original game.
// If you enabled cheats, revealed the map and disabled cheats you would be unable to unreveal the map.
if(!isCheatModeOn())
return;

isAllVisible_ = !isAllVisible_;

// The minimap in the original game is not updated immediately, but here this would cause complications.
if(GameInterface* gi = world_.GetGameInterface())
gi->GI_UpdateMapVisibility();
}

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.
constexpr auto checkExists = false;
world_.DestroyNO(mp, checkExists); // if CanPlaceCheatBuilding is true then this must be safe to destroy
BuildingFactory::CreateBuilding(world_, BuildingType::Headquarters, mp, player.GetPlayerId(), player.nation,
player.IsHQTent());
}

void Cheats::setGameSpeed(uint8_t speedIndex)
{
if(!isCheatModeOn())
return;

constexpr auto gfLengthInMs = 50;
GAMECLIENT.SetGFLengthReq(FramesInfo::milliseconds32_t{gfLengthInMs >> speedIndex});
// 50 -> 25 -> 12 -> 6 -> 3 -> 1
}

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

if(GAMECLIENT.IsReplayModeOn())
return;

GAMECLIENT.ToggleHumanAIPlayer(AI::Info{AI::Type::Default, AI::Level::Easy});
}

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

GAMECLIENT.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);
}

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

PlayerIDSet ais;
for(auto i = 0u; i < world_.GetNumPlayers(); ++i)
if(!world_.GetPlayer(i).isHuman())
ais.insert(i);
destroyBuildings(ais);
}

bool Cheats::canCheatModeBeOn() const
{
return world_.IsSinglePlayer();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can extend this to be a setting inside the create game screen, to allow cheats in multiplayer too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could certainly create an "enable cheats in multiplayer" addon or something similar, but it would be useful to ask which of these cheats make sense in multiplayer. I don't really know a reason to enable most of them since they are so game-breaking.

On the other hand, I guess they could help in debugging, giving someone a handicap (experienced player sees FOW, newbie sees everything; experienced needs geologists, newbie sees resources, etc.) or eliminating the need to send geologists anywhere.

}
Loading
Loading