Skip to content

Commit

Permalink
Merge pull request #1688 from kubaau/cheats_refactoring
Browse files Browse the repository at this point in the history
Refactoring of existing cheats (see #1679)
  • Loading branch information
Flamefire authored Dec 14, 2024
2 parents 77372c5 + 9e790e3 commit 79fa5fe
Show file tree
Hide file tree
Showing 9 changed files with 342 additions and 42 deletions.
59 changes: 59 additions & 0 deletions libs/s25main/CheatCommandTracker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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 enableCheatsStr = makeCircularBuffer("winter");
} // namespace

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

void CheatCommandTracker::onKeyEvent(const KeyEvent& ke)
{
if(!cheats_.areCheatsAllowed())
return;

if(checkSpecialKeyEvent(ke))
lastChars_.clear();
else
onCharKeyEvent(ke);
}

void CheatCommandTracker::onChatCommand(const std::string& cmd)
{
if(!cheats_.areCheatsAllowed())
return;

if(cmd == "apocalypsis")
cheats_.armageddon();
}

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

switch(ke.kt)
{
case KeyType::F10: cheats_.toggleHumanAIPlayer(); break;
default: break;
}

return true;
}

void CheatCommandTracker::onCharKeyEvent(const KeyEvent& ke)
{
lastChars_.push_back(ke.c);

if(lastChars_ == enableCheatsStr)
cheats_.toggleCheatMode();
}
27 changes: 27 additions & 0 deletions libs/s25main/CheatCommandTracker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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);

void onKeyEvent(const KeyEvent& ke);
void onChatCommand(const std::string& cmd);

private:
bool checkSpecialKeyEvent(const KeyEvent& ke);
void onCharKeyEvent(const KeyEvent& ke);

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

#include "Cheats.h"
#include "network/GameClient.h"
#include "world/GameWorldBase.h"

Cheats::Cheats(GameWorldBase& world) : world_(world) {}

bool Cheats::areCheatsAllowed() const
{
return world_.IsSinglePlayer();
}

void Cheats::toggleCheatMode()
{
isCheatModeOn_ = !isCheatModeOn_;
}

void Cheats::toggleHumanAIPlayer() const
{
if(isCheatModeOn() && !GAMECLIENT.IsReplayModeOn())
GAMECLIENT.ToggleHumanAIPlayer(AI::Info{AI::Type::Default, AI::Level::Easy});
}

void Cheats::armageddon() const
{
if(isCheatModeOn())
GAMECLIENT.CheatArmageddon();
}
25 changes: 25 additions & 0 deletions libs/s25main/Cheats.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (C) 2024 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

class GameWorldBase;

class Cheats
{
public:
Cheats(GameWorldBase& world);

bool areCheatsAllowed() const;

void toggleCheatMode();
bool isCheatModeOn() const { return isCheatModeOn_; }

void toggleHumanAIPlayer() const;
void armageddon() const;

private:
bool isCheatModeOn_ = false;
GameWorldBase& world_;
};
2 changes: 0 additions & 2 deletions libs/s25main/GameInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

#include "gameTypes/MapCoordinates.h"

class Window;

/// Interface, welches vom Spiel angesprocehn werden kann, um beispielsweise GUI wichtige Nachrichten
/// zu übermiteln
class GameInterface
Expand Down
45 changes: 7 additions & 38 deletions libs/s25main/desktops/dskGameInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ dskGameInterface::dskGameInterface(std::shared_ptr<Game> game, std::shared_ptr<c
worldViewer(playerIdx, const_cast<Game&>(*game_).world_),
gwv(worldViewer, Position(0, 0), VIDEODRIVER.GetRenderSize()), cbb(*LOADER.GetPaletteN("pal5")),
actionwindow(nullptr), roadwindow(nullptr), minimap(worldViewer), isScrolling(false), zoomLvl(ZOOM_DEFAULT_INDEX),
isCheatModeOn(false)
cheats_(const_cast<Game&>(*game_).world_), cheatCommandTracker_(cheats_)
{
road.mode = RoadBuildMode::Disabled;
road.point = MapPoint(0, 0);
Expand Down Expand Up @@ -424,7 +424,7 @@ void dskGameInterface::Msg_PaintAfter()
DrawPoint iconPos(VIDEODRIVER.GetRenderSize().x - 56, 32);

// Draw cheating indicator icon (WINTER)
if(isCheatModeOn)
if(cheats_.isCheatModeOn())
{
glArchivItem_Bitmap* cheatingImg = LOADER.GetImageN("io", 75);
cheatingImg->DrawFull(iconPos);
Expand Down Expand Up @@ -738,6 +738,8 @@ bool dskGameInterface::Msg_RightUp(const MouseCoords& /*mc*/) //-V524
*/
bool dskGameInterface::Msg_KeyDown(const KeyEvent& ke)
{
cheatCommandTracker_.onKeyEvent(ke);

switch(ke.kt)
{
default: break;
Expand Down Expand Up @@ -779,17 +781,6 @@ bool dskGameInterface::Msg_KeyDown(const KeyEvent& ke)
case KeyType::F9: // Readme
WINDOWMANAGER.ToggleWindow(std::make_unique<iwTextfile>("readme.txt", _("Readme!")));
return true;
case KeyType::F10:
{
#ifdef NDEBUG
const bool allowHumanAI = isCheatModeOn;
#else
const bool allowHumanAI = true;
#endif // !NDEBUG
if(GAMECLIENT.GetState() == ClientState::Game && allowHumanAI && !GAMECLIENT.IsReplayModeOn())
GAMECLIENT.ToggleHumanAIPlayer(AI::Info(AI::Type::Default, AI::Level::Easy));
return true;
}
case KeyType::F11: // Music player (midi files)
WINDOWMANAGER.ToggleWindow(std::make_unique<iwMusicPlayer>());
return true;
Expand All @@ -798,28 +789,6 @@ bool dskGameInterface::Msg_KeyDown(const KeyEvent& ke)
return true;
}

static std::string winterCheat = "winter";
switch(ke.c)
{
case 'w':
case 'i':
case 'n':
case 't':
case 'e':
case 'r':
curCheatTxt += char(ke.c);
if(winterCheat.find(curCheatTxt) == 0)
{
if(curCheatTxt == winterCheat)
{
isCheatModeOn = !isCheatModeOn;
curCheatTxt.clear();
}
} else
curCheatTxt.clear();
break;
}

switch(ke.c)
{
case '+':
Expand Down Expand Up @@ -1127,9 +1096,9 @@ void dskGameInterface::ShowActionWindow(const iwAction::Tabs& action_tabs, MapPo

void dskGameInterface::OnChatCommand(const std::string& cmd)
{
if(cmd == "apocalypsis")
GAMECLIENT.CheatArmageddon();
else if(cmd == "surrender")
cheatCommandTracker_.onChatCommand(cmd);

if(cmd == "surrender")
GAMECLIENT.Surrender();
else if(cmd == "async")
(void)RANDOM.Rand(RANDOM_CONTEXT2(0), 255);
Expand Down
7 changes: 5 additions & 2 deletions libs/s25main/desktops/dskGameInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#pragma once

#include "CheatCommandTracker.h"
#include "Cheats.h"
#include "Desktop.h"
#include "GameInterface.h"
#include "IngameMinimap.h"
Expand Down Expand Up @@ -162,7 +164,8 @@ class dskGameInterface :
bool isScrolling;
Position startScrollPt;
size_t zoomLvl;
bool isCheatModeOn;
std::string curCheatTxt;
Subscription evBld;

Cheats cheats_;
CheatCommandTracker cheatCommandTracker_;
};
126 changes: 126 additions & 0 deletions tests/s25Main/integration/testCheatCommandTracker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// 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"
#include "worldFixtures/CreateEmptyWorld.h"
#include "worldFixtures/WorldFixture.h"

BOOST_AUTO_TEST_SUITE(CheatCommandTrackerTests)

namespace {
template<unsigned T_numPlayers>
struct CheatCommandTrackerFixture : WorldFixture<CreateEmptyWorld, T_numPlayers>
{
Cheats cheats_{this->world};
CheatCommandTracker tracker_{cheats_};

KeyEvent makeKeyEvent(unsigned c) { return {KeyType::Char, c, false, false, false}; }
KeyEvent makeKeyEvent(KeyType kt) { return {kt, 0, false, false, false}; }

void trackString(const std::string& str)
{
for(char c : str)
tracker_.onKeyEvent(makeKeyEvent(c));
}
};
using CheatCommandTrackerFixture1P = CheatCommandTrackerFixture<1>;
using CheatCommandTrackerFixture2P = CheatCommandTrackerFixture<2>;
} // namespace

BOOST_FIXTURE_TEST_CASE(CheatModeIsOffByDefault, CheatCommandTrackerFixture1P)
{
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == false);
}

BOOST_FIXTURE_TEST_CASE(CheatModeCanBeTurnedOn, CheatCommandTrackerFixture1P)
{
trackString("winter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == true);
}

BOOST_FIXTURE_TEST_CASE(CheatModeCannotBeTurnedOn_InMultiplayer, CheatCommandTrackerFixture2P)
{
trackString("winter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == false);
}

BOOST_FIXTURE_TEST_CASE(CheatModeCanBeTurnedOff, CheatCommandTrackerFixture1P)
{
trackString("winter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == true);
trackString("winter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == false);
}

BOOST_FIXTURE_TEST_CASE(CheatModeCanBeTurnedOnAndOffRepeatedly, CheatCommandTrackerFixture1P)
{
trackString("winter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == true);
trackString("winter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == false);
trackString("winter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == true);
trackString("winter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == false);
}

BOOST_FIXTURE_TEST_CASE(CheatModeIsNotTurnedOn_WhenIncomplete, CheatCommandTrackerFixture1P)
{
trackString("winte");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == false);
}

BOOST_FIXTURE_TEST_CASE(CheatModeIsNotTurnedOn_WhenInterruptedByAnotherKeyType, CheatCommandTrackerFixture1P)
{
trackString("win");
tracker_.onKeyEvent(makeKeyEvent(KeyType::F10));
trackString("ter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == false);
}

BOOST_FIXTURE_TEST_CASE(CheatModeIsNotTurnedOn_WhenInterruptedByAnotherLetter, CheatCommandTrackerFixture1P)
{
trackString("wainter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == false);
}

BOOST_FIXTURE_TEST_CASE(CheatModeIsNotTurnedOn_WhenOrderOfCharactersIsWrong, CheatCommandTrackerFixture1P)
{
trackString("winetr");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == false);
}

BOOST_FIXTURE_TEST_CASE(CheatModeIsNotTurnedOn_WhenOrderOfCharactersIsWrong_Wraparound, CheatCommandTrackerFixture1P)
{
trackString("rwinte");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == false);
}

BOOST_FIXTURE_TEST_CASE(CheatModeIsNotTurnedOn_WhenACharacterIsRepeated, CheatCommandTrackerFixture1P)
{
trackString("winnter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == false);
}

BOOST_FIXTURE_TEST_CASE(CheatModeIsTurnedOn_WhenTheFirstCharacterIsRepeated, CheatCommandTrackerFixture1P)
{
trackString("wwwinter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == true);
}

BOOST_FIXTURE_TEST_CASE(CheatModeIsTurnedOn_EvenWhenWrongInputsWereProvidedBefore, CheatCommandTrackerFixture1P)
{
trackString("www");
auto ke = makeKeyEvent('1');
ke.alt = true;
tracker_.onKeyEvent(ke);
trackString("interwitter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == false);
trackString("winter");
BOOST_TEST_REQUIRE(cheats_.isCheatModeOn() == true);
}

BOOST_AUTO_TEST_SUITE_END()
Loading

0 comments on commit 79fa5fe

Please sign in to comment.