Kaynağa Gözat

Merge pull request #5815 from Laserlicht/cheats

add skill cheat / allow more than 8 skills
Ivan Savenko 4 ay önce
ebeveyn
işleme
efb31d19a6

+ 2 - 1
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -20,6 +20,7 @@
 #include "../../../lib/CCreatureHandler.h"
 #include "../../../lib/GameLibrary.h"
 #include "../../../lib/StartInfo.h"
+#include "../../../lib/GameSettings.h"
 #include "../../../lib/filesystem/Filesystem.h"
 #include "../Goals/ExecuteHeroChain.h"
 #include "../Goals/BuildThis.h"
@@ -581,7 +582,7 @@ float RewardEvaluator::evaluateWitchHutSkillScore(const CGObjectInstance * hut,
 		return role == HeroRole::SCOUT ? 2 : 0;
 
 	if(hero->getSecSkillLevel(skill) != MasteryLevel::NONE
-		|| hero->secSkills.size() >= GameConstants::SKILL_PER_HERO)
+		|| static_cast<int>(hero->secSkills.size()) >= cb->getSettings().getInteger(EGameSettings::HEROES_SKILL_PER_HERO))
 		return 0;
 
 	auto score = ai->heroManager->evaluateSecSkill(skill, hero);

+ 3 - 0
Mods/vcmi/Content/config/english.json

@@ -435,6 +435,9 @@
 	"vcmi.townWindow.upgradeAll.notAllUpgradable" : "Not enough resources to upgrade all creatures. Do you want to upgrade following creatures?",
 	"vcmi.townWindow.upgradeAll.notUpgradable" : "Not enough resources to upgrade any creature.",
 
+	"vcmi.kingdomOverview.secSkillOverflow.hover" : "More skills",
+	"vcmi.kingdomOverview.secSkillOverflow.help" : "{More skills}\n\nThis hero has more skills.\nYou can see all of them in the hero overview.",
+
 	"vcmi.logicalExpressions.anyOf"  : "Any of the following:",
 	"vcmi.logicalExpressions.allOf"  : "All of the following:",
 	"vcmi.logicalExpressions.noneOf" : "None of the following:",

+ 3 - 0
Mods/vcmi/Content/config/german.json

@@ -425,6 +425,9 @@
 	"vcmi.townWindow.upgradeAll.notAllUpgradable" : "Nicht genügend Ressourcen um alle Kreaturen aufzurüsten. Folgende Kreaturen aufrüsten?",
 	"vcmi.townWindow.upgradeAll.notUpgradable" : "Nicht genügend Ressourcen um mindestens eine Kreatur aufzurüsten.",
 
+	"vcmi.kingdomOverview.secSkillOverflow.hover" : "Mehr skills",
+	"vcmi.kingdomOverview.secSkillOverflow.help" : "{Mehr skills}\n\nDieser Held hat mehr Skills.\nIn der Heldenübersicht können alle eingesehen werden.",
+
 	"vcmi.logicalExpressions.anyOf"  : "Eines der folgenden:",
 	"vcmi.logicalExpressions.allOf"  : "Alles der folgenden:",
 	"vcmi.logicalExpressions.noneOf" : "Keines der folgenden:",

+ 25 - 2
client/windows/CExchangeWindow.cpp

@@ -81,7 +81,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 			primSkillValues[leftRight].push_back(std::make_shared<CLabel>(352 + (qeLayout ? 96 : 93) * leftRight, (qeLayout ? 22 : 35) + (qeLayout ? 26 : 36) * m, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE));
 
 
-		for(int m=0; m < hero->secSkills.size(); ++m)
+		for(int m=0; m < std::min(static_cast<int>(hero->secSkills.size()), 8); ++m)
 			secSkills[leftRight].push_back(std::make_shared<CSecSkillPlace>(Point(32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88), CSecSkillPlace::ImageSize::SMALL,
 				hero->secSkills[m].first, hero->secSkills[m].second));
 
@@ -377,6 +377,10 @@ void CExchangeWindow::questLogShortcut()
 
 void CExchangeWindow::update()
 {
+	const bool qeLayout = isQuickExchangeLayoutAvailable();
+
+	OBJECT_CONSTRUCTION;
+
 	CWindowWithArtifacts::update();
 
 	for(size_t leftRight : {0, 1})
@@ -389,8 +393,27 @@ void CExchangeWindow::update()
 			primSkillValues[leftRight][m]->setText(std::to_string(value));
 		}
 
-		for(int m=0; m < hero->secSkills.size(); ++m)
+		int slots = 8;
+		bool isMoreSkillsThanSlots = hero->secSkills.size() > slots;
+		for(int m=0; m < std::min(static_cast<int>(hero->secSkills.size()), 8); ++m)
 		{
+			if(m == slots - 1)
+			{
+				if(isMoreSkillsThanSlots)
+				{
+					Rect r(Point(32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88), Point(34, 28));
+					secSkillsFull[leftRight] = std::make_shared<CMultiLineLabel>(r, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, "...");
+					secSkillsFullArea[leftRight] = std::make_shared<LRClickableAreaWText>(r, LIBRARY->generaltexth->translate("vcmi.kingdomOverview.secSkillOverflow.hover"), LIBRARY->generaltexth->translate("vcmi.kingdomOverview.secSkillOverflow.help"));
+					secSkills[leftRight][m]->setSkill(SecondarySkill::NONE);
+					continue;
+				}
+				else
+				{
+					secSkillsFull[leftRight].reset();
+					secSkillsFullArea[leftRight].reset();
+				}
+			}
+
 			int id = hero->secSkills[m].first;
 			int level = hero->secSkills[m].second;
 

+ 4 - 0
client/windows/CExchangeWindow.h

@@ -13,6 +13,8 @@
 #include "../widgets/CExchangeController.h"
 
 class CGarrisonSlot;
+class CMultiLineLabel;
+class LRClickableAreaWText;
 
 class CExchangeWindow : public CStatusbarWindow, public IGarrisonHolder, public CWindowWithArtifacts
 {
@@ -27,6 +29,8 @@ class CExchangeWindow : public CStatusbarWindow, public IGarrisonHolder, public
 
 	std::vector<std::shared_ptr<LRClickableAreaWTextComp>> primSkillAreas;
 	std::array<std::vector<std::shared_ptr<CSecSkillPlace>>, 2> secSkills;
+	std::array<std::shared_ptr<CMultiLineLabel>, 2> secSkillsFull;
+	std::array<std::shared_ptr<LRClickableAreaWText>, 2> secSkillsFullArea;
 
 	std::array<std::shared_ptr<CHeroArea>, 2> heroAreas;
 	std::array<std::shared_ptr<LRClickableAreaWText>, 2> specialtyAreas;

+ 27 - 6
client/windows/CHeroWindow.cpp

@@ -28,6 +28,7 @@
 #include "../widgets/CGarrisonInt.h"
 #include "../widgets/TextControls.h"
 #include "../widgets/Buttons.h"
+#include "../widgets/Slider.h"
 #include "../render/IRenderHandler.h"
 
 #include "../lib/CConfigHandler.h"
@@ -148,16 +149,27 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
 	expValue = std::make_shared<CLabel>(68, 252);
 	manaValue = std::make_shared<CLabel>(211, 252);
 
+	if(hero->secSkills.size() > 8)
+	{
+		auto divisionRoundUp = [](int x, int y){ return (x + (y - 1)) / y; };
+		int lines = divisionRoundUp(hero->secSkills.size(), 2);
+		secSkillSlider = std::make_shared<CSlider>(Point(284, 276), 189, [this](int val){ CHeroWindow::update(); }, 4, lines, 0, Orientation::VERTICAL, CSlider::BROWN);
+		secSkillSlider->setPanningStep(48);
+		secSkillSlider->setScrollBounds(Rect(-266, 0, secSkillSlider->pos.x - pos.x + secSkillSlider->pos.w, secSkillSlider->pos.h));
+	}
+
 	for(int i = 0; i < std::min<size_t>(hero->secSkills.size(), 8u); ++i)
 	{
-		Rect r = Rect(i%2 == 0  ?  18  :  162,  276 + 48 * (i/2),  136,  42);
+		bool isSmallBox = (secSkillSlider && i%2 == 1);
+		Rect r = Rect(i%2 == 0  ?  18  :  162,  276 + 48 * (i/2), isSmallBox ? 120 : 136,  42);
 		secSkills.emplace_back(std::make_shared<CSecSkillPlace>(r.topLeft(), CSecSkillPlace::ImageSize::MEDIUM));
 
 		int x = (i % 2) ? 212 : 68;
 		int y = 280 + 48 * (i/2);
+		int width = isSmallBox ? 71 : 87;
 
-		secSkillValues.push_back(std::make_shared<CLabel>(x, y, FONT_SMALL, ETextAlignment::TOPLEFT));
-		secSkillNames.push_back(std::make_shared<CLabel>(x, y+20, FONT_SMALL, ETextAlignment::TOPLEFT));
+		secSkillValues.push_back(std::make_shared<CLabel>(x, y, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, "", width));
+		secSkillNames.push_back(std::make_shared<CLabel>(x, y+20, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, "", width));
 	}
 
 	// various texts
@@ -175,6 +187,8 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
 
 void CHeroWindow::update()
 {
+	OBJECT_CONSTRUCTION;
+
 	CWindowWithArtifacts::update();
 	auto & heroscrn = LIBRARY->generaltexth->heroscrn;
 	assert(curHero);
@@ -195,7 +209,6 @@ void CHeroWindow::update()
 	portraitImage->setFrame(curHero->getIconIndex());
 
 	{
-		OBJECT_CONSTRUCTION;
 		if(!garr)
 		{
 			bool removableTroops = curHero->getOwner() == GAME->interface()->playerID;
@@ -233,9 +246,17 @@ void CHeroWindow::update()
 	}
 
 	//secondary skills support
-	for(size_t g=0; g< secSkills.size(); ++g)
+	for(size_t g=0; g < secSkills.size(); ++g)
 	{
-		SecondarySkill skill = curHero->secSkills[g].first;
+		int offset = secSkillSlider ? secSkillSlider->getValue() * 2 : 0;
+		if(curHero->secSkills.size() < g + offset + 1)
+		{
+			secSkillNames[g]->setText("");
+			secSkillValues[g]->setText("");
+			secSkills[g]->setSkill(SecondarySkill::NONE);
+			break;
+		}
+		SecondarySkill skill = curHero->secSkills[g + offset].first;
 		int	level = curHero->getSecSkillLevel(skill);
 		std::string skillName = skill.toEntity(LIBRARY)->getNameTranslated();
 		std::string skillValue = LIBRARY->generaltexth->levels[level-1];

+ 2 - 0
client/windows/CHeroWindow.h

@@ -33,6 +33,7 @@ class CToggleGroup;
 class CGStatusBar;
 class CTextBox;
 class CGarrisonInt;
+class CSlider;
 
 /// Button which switches hero selection
 class CHeroSwitcher : public CIntObject
@@ -77,6 +78,7 @@ class CHeroWindow : public CStatusbarWindow, public IGarrisonHolder, public CWin
 	std::vector< std::shared_ptr<CSecSkillPlace>> secSkills;
 	std::vector<std::shared_ptr<CLabel>> secSkillNames;
 	std::vector<std::shared_ptr<CLabel>> secSkillValues;
+	std::shared_ptr<CSlider> secSkillSlider;
 
 	std::shared_ptr<CButton> quitButton;
 	std::shared_ptr<CTextBox> dismissLabel;

+ 12 - 2
client/windows/CKingdomInterface.cpp

@@ -41,7 +41,8 @@
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/MiscObjects.h"
-#include "../../lib/texts/CGeneralTextHandler.h"
+#include "texts/CGeneralTextHandler.h"
+#include "../../lib/GameSettings.h"
 
 static const std::string OVERVIEW_BACKGROUND = "OvCast.pcx";
 static const size_t OVERVIEW_SIZE = 4;
@@ -983,8 +984,17 @@ CHeroItem::CHeroItem(const CGHeroInstance * Hero)
 		heroInfo.push_back(std::make_shared<InfoBox>(Point(78+(int)i*36, 26), InfoBox::POS_DOWN, InfoBox::SIZE_SMALL, data));
 	}
 
-	for(size_t i=0; i<GameConstants::SKILL_PER_HERO; i++)
+	int slots = 8;
+	bool isMoreSkillsThanSlots = hero->secSkills.size() > slots;
+	for(size_t i=0; i<slots; i++)
 	{
+		if(isMoreSkillsThanSlots && i == slots - 1)
+		{
+			Rect r(Point(410+(int)i*36, 5), Point(34, 28));
+			heroInfoFull = std::make_shared<CMultiLineLabel>(r, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, "...");
+			heroInfoFullArea = std::make_shared<LRClickableAreaWText>(r, LIBRARY->generaltexth->translate("vcmi.kingdomOverview.secSkillOverflow.hover"), LIBRARY->generaltexth->translate("vcmi.kingdomOverview.secSkillOverflow.help"));
+			continue;
+		}
 		auto data = std::make_shared<InfoBoxHeroData>(IInfoBoxData::HERO_SECONDARY_SKILL, hero, (int)i);
 		heroInfo.push_back(std::make_shared<InfoBox>(Point(410+(int)i*36, 5), InfoBox::POS_NONE, InfoBox::SIZE_SMALL, data));
 	}

+ 3 - 0
client/windows/CKingdomInterface.h

@@ -32,6 +32,7 @@ class CTabbedInt;
 class CGStatusBar;
 class CGarrisonInt;
 class CMultiLineLabel;
+class LRClickableAreaWText;
 
 class CKingdHeroList;
 class CKingdTownList;
@@ -310,6 +311,8 @@ class CHeroItem : public CIntObject, public IGarrisonHolder
 
 	std::shared_ptr<CToggleGroup> artButtons;
 	std::vector<std::shared_ptr<InfoBox>> heroInfo;
+	std::shared_ptr<CMultiLineLabel> heroInfoFull;
+	std::shared_ptr<LRClickableAreaWText> heroInfoFullArea;
 	std::shared_ptr<MoraleLuckBox> morale;
 	std::shared_ptr<MoraleLuckBox> luck;
 

+ 3 - 0
config/gameConfig.json

@@ -507,6 +507,9 @@
 			"movementPointsLand" : [ 1500, 1500, 1500, 1500, 1560, 1630, 1700, 1760, 1830, 1900, 1960, 2000 ],
 			/// movement points hero can get on start of the turn when on sea, depending on speed of slowest creature (0-based list)
 			"movementPointsSea" : [ 1500 ],
+      
+			/// maximal secondary skills per hero
+			"skillPerHero" : 8,
 			
 			/// Base scouting range for hero without any range modifiers
 			"baseScoutingRange" : 5,

+ 1 - 0
config/schemas/gameSettings.json

@@ -48,6 +48,7 @@
 				"movementCostBase"  :         { "type" : "number" },
 				"movementPointsLand" :        { "type" : "array" },
 				"movementPointsSea" :         { "type" : "array" },
+				"skillPerHero" :              { "type" : "number" },
 				"specialtyCreatureGrowth" :    { "type" : "number" },
 				"specialtySecondarySkillGrowth" : { "type" : "number" },
 				"baseScoutingRange" :         { "type" : "number" }

+ 11 - 0
docs/players/Cheat_Codes.md

@@ -10,6 +10,17 @@ To use cheat code, press `Tab` key or click/tap on status bar to open game chat
 
 `nwcthereisnospoon`, `nwcmidichlorians`, `nwctim`, `vcmiistari` or `vcmispells` - give a spell book, all spells and 999 mana to currently selected hero. Also allows casting spell up to 100 times per combat round
 
+### Secondary Skills
+
+`vcmiskill <skillID> <mastery>` - give a secondary skill to currently selected hero
+
+Examples:
+`vcmiskill learning` - give expert level learning skill
+`vcmiskill leadership 2` - give advanced level leadership skill
+`vcmiskill wisdom 0` - remove wisdom skill
+`vcmiskill every` - give all skills on expert level
+`vcmiskill every 0` - remove all skills
+
 ### Army
 
 `nwctrinity`, `nwcpadme`, `nwcavertingoureyes`, `vcmiainur` or `vcmiarchangel` - give 5 Archangels in every empty slot (to currently selected hero)  

+ 1 - 0
lib/GameSettings.cpp

@@ -87,6 +87,7 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
 		{EGameSettings::HEROES_MOVEMENT_COST_BASE,                        "heroes",    "movementCostBase"                     },
 		{EGameSettings::HEROES_MOVEMENT_POINTS_LAND,                      "heroes",    "movementPointsLand"                   },
 		{EGameSettings::HEROES_MOVEMENT_POINTS_SEA,                       "heroes",    "movementPointsSea"                    },
+		{EGameSettings::HEROES_SKILL_PER_HERO,                            "heroes",    "skillPerHero"                         },
 		{EGameSettings::HEROES_SPECIALTY_CREATURE_GROWTH,                 "heroes",    "specialtyCreatureGrowth"              },
 		{EGameSettings::HEROES_SPECIALTY_SECONDARY_SKILL_GROWTH,          "heroes",    "specialtySecondarySkillGrowth"        },
 		{EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE,                     "mapFormat", "armageddonsBlade"                     },

+ 1 - 0
lib/IGameSettings.h

@@ -60,6 +60,7 @@ enum class EGameSettings
 	HEROES_MOVEMENT_COST_BASE,
 	HEROES_MOVEMENT_POINTS_LAND,
 	HEROES_MOVEMENT_POINTS_SEA,
+	HEROES_SKILL_PER_HERO,
 	HEROES_SPECIALTY_CREATURE_GROWTH,
 	HEROES_SPECIALTY_SECONDARY_SKILL_GROWTH,
 	INTERFACE_PLAYER_COLORED_BACKGROUND,

+ 0 - 1
lib/constants/NumericConstants.h

@@ -33,7 +33,6 @@ namespace GameConstants
 	constexpr int BATTLE_SHOOTING_PENALTY_DISTANCE = 10; //if the distance is > than this, then shooting stack has distance penalty
 	constexpr int BATTLE_SHOOTING_RANGE_DISTANCE = std::numeric_limits<uint8_t>::max(); // used when shooting stack has no shooting range limit
 	constexpr int ARMY_SIZE = 7;
-	constexpr int SKILL_PER_HERO = 8;
 	constexpr ui32 HERO_HIGH_LEVEL = 10; // affects primary skill upgrade order
 
 	constexpr int SKILL_QUANTITY=28;

+ 2 - 1
lib/mapObjects/CGHeroInstance.cpp

@@ -31,6 +31,7 @@
 #include "../CCreatureHandler.h"
 #include "../mapping/CMap.h"
 #include "../StartInfo.h"
+#include "../GameSettings.h"
 #include "CGTownInstance.h"
 #include "../entities/artifact/ArtifactUtils.h"
 #include "../entities/artifact/CArtifact.h"
@@ -167,7 +168,7 @@ int3 CGHeroInstance::convertFromVisitablePos(const int3 & position) const
 
 bool CGHeroInstance::canLearnSkill() const
 {
-	return secSkills.size() < GameConstants::SKILL_PER_HERO;
+	return secSkills.size() < cb->getSettings().getInteger(EGameSettings::HEROES_SKILL_PER_HERO);
 }
 
 bool CGHeroInstance::canLearnSkill(const SecondarySkill & which) const

+ 50 - 5
server/processors/PlayerMessageProcessor.cpp

@@ -17,7 +17,9 @@
 #include "../TurnTimerHandler.h"
 
 #include "../../lib/CPlayerState.h"
+#include "../../lib/CSkillHandler.h"
 #include "../../lib/StartInfo.h"
+#include "../../lib/constants/Enumerations.h"
 #include "../../lib/entities/artifact/CArtHandler.h"
 #include "../../lib/entities/building/CBuilding.h"
 #include "../../lib/entities/hero/CHeroHandler.h"
@@ -694,6 +696,46 @@ void PlayerMessageProcessor::cheatMaxMorale(PlayerColor player, const CGHeroInst
 	gameHandler->giveHeroBonus(&gb);
 }
 
+void PlayerMessageProcessor::cheatSkill(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words)
+{
+	if (!hero)
+		return;
+
+	std::string identifier;
+	try
+	{
+		identifier = words.at(0);
+	}
+	catch(std::logic_error&)
+	{
+		return;
+	}
+
+	MasteryLevel::Type mastery;
+	try
+	{
+		mastery = static_cast<MasteryLevel::Type>(std::stoi(words.at(1)));
+	}
+	catch(std::logic_error&)
+	{
+		mastery = MasteryLevel::Type::EXPERT;
+	}
+
+	if(identifier == "every")
+	{
+		for(const auto & skill : LIBRARY->skillh->objects)
+			gameHandler->changeSecSkill(hero, SecondarySkill(skill->getId()), mastery, ChangeValueMode::ABSOLUTE);
+		return;
+	}
+
+	std::optional<int32_t> skillId = LIBRARY->identifiers()->getIdentifier(ModScope::scopeGame(), "skill", identifier, false);
+	if(!skillId.has_value())
+		return;
+	
+	auto skill = SecondarySkill(skillId.value());
+	gameHandler->changeSecSkill(hero, skill, mastery, ChangeValueMode::ABSOLUTE);
+}
+
 bool PlayerMessageProcessor::handleCheatCode(const std::string & cheat, PlayerColor player, ObjectInstanceID currObj)
 {
 	std::vector<std::string> words;
@@ -736,7 +778,8 @@ bool PlayerMessageProcessor::handleCheatCode(const std::string & cheat, PlayerCo
 		                           "vcmiluck",        "nwcfollowthewhiterabbit",                       "nwccastleanthrax",
 		                           "vcmimorale",      "nwcmorpheus",                                   "nwcmuchrejoicing",
 		                           "vcmigod",         "nwctheone",
-		                           "vcmiscrolls"
+		                           "vcmiscrolls",
+		                           "vcmiskill"
 	};
 
 	if(vstd::contains(localCheats, cheatName))
@@ -829,7 +872,8 @@ void PlayerMessageProcessor::executeCheatCode(const std::string & cheatName, Pla
 		cheatMovement(player, hero, { });
 		cheatFly(player, hero);
 	};
-	const auto & doColorSchemeChange = [&](ColorScheme filter) { cheatColorSchemeChange(player, filter); };
+	const auto & doCheatColorSchemeChange = [&](ColorScheme filter) { cheatColorSchemeChange(player, filter); };
+	const auto & doCheatSkill = [&]() { cheatSkill(player, hero, words); };
 
 	std::map<std::string, std::function<void()>> callbacks = {
 		{"vcmiainur",               [&] () {doCheatGiveArmyFixed({ "archangel", "5" });}        },
@@ -909,9 +953,10 @@ void PlayerMessageProcessor::executeCheatCode(const std::string & cheatName, Pla
 		{"vcmigod",                 doCheatTheOne                                               },
 		{"nwctheone",               doCheatTheOne                                               },
 		{"vcmiscrolls",             doCheatGiveScrolls                                          },
-		{"vcmicolor",               [&] () {doColorSchemeChange(ColorScheme::H2_SCHEME);}       },
-		{"nwcphisherprice",         [&] () {doColorSchemeChange(ColorScheme::H2_SCHEME);}       },
-		{"vcmigray",                [&] () {doColorSchemeChange(ColorScheme::GRAYSCALE);}       },
+		{"vcmicolor",               [&] () {doCheatColorSchemeChange(ColorScheme::H2_SCHEME);}  },
+		{"nwcphisherprice",         [&] () {doCheatColorSchemeChange(ColorScheme::H2_SCHEME);}  },
+		{"vcmigray",                [&] () {doCheatColorSchemeChange(ColorScheme::GRAYSCALE);}  },
+		{"vcmiskill",               doCheatSkill                                                },
 	};
 
 	assert(callbacks.count(cheatName));

+ 1 - 0
server/processors/PlayerMessageProcessor.h

@@ -59,6 +59,7 @@ class PlayerMessageProcessor
 	void cheatMaxLuck(PlayerColor player, const CGHeroInstance * hero);
 	void cheatMaxMorale(PlayerColor player, const CGHeroInstance * hero);
 	void cheatFly(PlayerColor player, const CGHeroInstance * hero);
+	void cheatSkill(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
 
 	void commandExit(PlayerColor player, const std::vector<std::string> & words);
 	void commandKick(PlayerColor player, const std::vector<std::string> & words);