Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
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
Loading