Browse Source

Merge pull request #2882 from Nordsoft91/ai-cheating

Add basic system to give more advantages for ai player
Nordsoft91 2 years ago
parent
commit
a36450e2d1

+ 70 - 0
config/difficulty.json

@@ -0,0 +1,70 @@
+//Configured difficulty
+{
+	"human":
+	{
+		"pawn":
+		{
+			"resources": { "wood" : 30, "mercury": 15, "ore": 30, "sulfur": 15, "crystal": 15, "gems": 15, "gold": 30000, "mithril": 0 },
+			"globalBonuses": [],
+			"battleBonuses": []
+		},
+		"knight":
+		{
+			"resources": { "wood" : 20, "mercury": 10, "ore": 20, "sulfur": 10, "crystal": 10, "gems": 10, "gold": 20000, "mithril": 0 },
+			"globalBonuses": [],
+			"battleBonuses": []
+		},
+		"rook":
+		{
+			"resources": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 15000, "mithril": 0 },
+			"globalBonuses": [],
+			"battleBonuses": []
+		},
+		"queen":
+		{
+			"resources": { "wood" : 10, "mercury": 4, "ore": 10, "sulfur": 4, "crystal": 4, "gems": 4, "gold": 10000, "mithril": 0 },
+			"globalBonuses": [],
+			"battleBonuses": []
+		},
+		"king":
+		{
+			"resources": { "wood" : 0, "mercury": 0, "ore": 0	, "sulfur": 0, "crystal": 0, "gems": 0, "gold": 0, "mithril": 0 },
+			"globalBonuses": [],
+			"battleBonuses": []
+		}
+	},
+	"ai":
+	{
+		"pawn":
+		{
+			"resources": { "wood" : 5, "mercury": 2, "ore": 5, "sulfur": 2, "crystal": 2, "gems": 2, "gold": 5000, "mithril": 0 },
+			"globalBonuses": [],
+			"battleBonuses": []
+		},
+		"knight":
+		{
+			"resources": { "wood" : 10, "mercury": 4, "ore": 10, "sulfur": 4, "crystal": 4, "gems": 4, "gold": 7500, "mithril": 0 },
+			"globalBonuses": [],
+			"battleBonuses": []
+		},
+		"rook":
+		{
+			"resources": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 10000, "mithril": 0 },
+			"globalBonuses": [],
+			"battleBonuses": []
+		},
+		"queen":
+		{
+			"resources": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 10000, "mithril": 0 },
+			"globalBonuses": [],
+			"battleBonuses": []
+		},
+		"king":
+		{
+			"resources": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 10000, "mithril": 0 },
+			"globalBonuses": [],
+			"battleBonuses": []
+		}
+	},
+}
+

+ 0 - 31
config/startres.json

@@ -1,31 +0,0 @@
-// Starting resources, ordered by difficulty level (0 to 4)
-{
-	"difficulty":
-		[
-			{
-			  "human": { "wood" : 30, "mercury": 15, "ore": 30, "sulfur": 15, "crystal": 15, "gems": 15, "gold": 30000, "mithril": 0 },
-			  "ai": { "wood" : 5, "mercury": 2, "ore": 5, "sulfur": 2, "crystal": 2, "gems": 2, "gold": 5000, "mithril": 0 }
-			},
-
-			{
-			  "human": { "wood" : 20, "mercury": 10, "ore": 20, "sulfur": 10, "crystal": 10, "gems": 10, "gold": 20000, "mithril": 0 },
-			  "ai": { "wood" : 10, "mercury": 4, "ore": 10, "sulfur": 4, "crystal": 4, "gems": 4, "gold": 7500, "mithril": 0 }
-			},
-
-			{
-			  "human": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 15000, "mithril": 0 },
-			  "ai": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 10000, "mithril": 0 }
-			},
-
-			{
-			  "human": { "wood" : 10, "mercury": 4, "ore": 10, "sulfur": 4, "crystal": 4, "gems": 4, "gold": 10000, "mithril": 0 },
-			  "ai": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 10000, "mithril": 0 }
-			},
-
-			{
-			  "human": { "wood" : 0, "mercury": 0, "ore": 0	, "sulfur": 0, "crystal": 0, "gems": 0, "gold": 0, "mithril": 0 },
-			  "ai": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 10000, "mithril": 0 }
-			}
-		]
-}
-

+ 67 - 0
docs/modders/Difficulty.md

@@ -0,0 +1,67 @@
+< [Documentation](../Readme.md) / [Modding](Readme.md) / Difficulty
+
+
+Since VCMI 1.4.0 there are more capabilities to configure difficulty parameters.
+It means, that modders can give different bonuses to AI or human players depending on selected difficulty
+
+Difficulty configuration is located in [config/difficulty.json](../config/difficulty.json) file and can be overriden by mods.
+
+## Format summary
+
+``` javascript
+{
+	"human": //parameters impacting human players only
+	{
+		"pawn": //parameters for specific difficulty
+		{
+			//starting resources
+			"resources": { "wood" : 30, "mercury": 15, "ore": 30, "sulfur": 15, "crystal": 15, "gems": 15, "gold": 30000, "mithril": 0 },
+			//bonuses will be given to player globaly
+			"globalBonuses": [],
+			//bonuses will be given to player every battle
+			"battleBonuses": []
+		},
+		"knight": {},
+		"rook": {},
+		"queen": {},
+		"king": {},
+	},
+	"ai": //parameters impacting AI players only
+	{
+		"pawn": {}, //parameters for specific difficulty 
+		"knight": {},
+		"rook": {},
+		"queen": {},
+		"king": {},
+	}
+}
+```
+
+## Bonuses
+
+It's possible to specify bonuses of two types: `globalBonuses` and `battleBonuses`.
+
+Both are arrays containing any amount of bonuses, each can be described as usual bonus. See details in [bonus documenation](Bonus_Format.md).
+
+`globalBonuses` are given to player on the begining and depending on bonus configuration, it can behave diffierently.
+
+`battleBonuses` are given to player during the battles, but *only for battles with neutral forces*. So it won't be provided to player for PvP battles and battles versus AI heroes/castles/garrisons. To avoid cumulative effects or unexpected behavior it's recommended to specify bonus `duration` as `ONE_BATTLE`.
+
+For both types of bonuses, `source` should be specified as `OTHER`.
+
+## Example
+
+```js
+{ //will give 150% extra health to all players' creatures if specified in "battleBonuses" array
+	"type" : "STACK_HEALTH",
+	"val" : 150,
+	"valueType" : "PERCENT_TO_ALL",
+	"duration" : "ONE_BATTLE",
+	"sourceType" : "OTHER"
+},
+```
+
+## Compatibility
+
+Starting from VCMI 1.4 `startres.json` is not available anymore and will be ignored if present in any mod.
+Thus, `Resourceful AI`  mod of version 1.2 won't work anymore.

+ 1 - 0
lib/CPlayerState.cpp

@@ -39,6 +39,7 @@ PlayerState::PlayerState(PlayerState && other) noexcept:
 	std::swap(towns, other.towns);
 	std::swap(dwellings, other.dwellings);
 	std::swap(quests, other.quests);
+	std::swap(battleBonuses, other.battleBonuses);
 }
 
 PlayerState::~PlayerState() = default;

+ 2 - 0
lib/CPlayerState.h

@@ -37,6 +37,7 @@ public:
 	std::vector<ConstTransitivePtr<CGTownInstance> > towns;
 	std::vector<ConstTransitivePtr<CGDwelling> > dwellings; //used for town growth
 	std::vector<QuestInfo> quests; //store info about all received quests
+	std::vector<Bonus> battleBonuses; //additional bonuses to be added during battle with neutrals
 
 	bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory
 	EPlayerStatus status;
@@ -82,6 +83,7 @@ public:
 		h & visitedObjects;
 		h & status;
 		h & daysWithoutCastle;
+		h & battleBonuses;
 		h & enteredLosingCheatCode;
 		h & enteredWinningCheatCode;
 		h & static_cast<CBonusSystemNode&>(*this);

+ 1 - 1
lib/battle/CBattleInfoCallback.cpp

@@ -1573,7 +1573,7 @@ int32_t CBattleInfoCallback::battleGetSpellCost(const spells::Spell * sp, const
 		}
 	}
 
-	return ret - manaReduction + manaIncrease;
+	return std::max(0, ret - manaReduction + manaIncrease);
 }
 
 bool CBattleInfoCallback::battleHasShootingPenalty(const battle::Unit * shooter, BattleHex destHex) const

+ 2 - 0
lib/constants/StringConstants.h

@@ -27,6 +27,8 @@ namespace GameConstants
 	};
 
 	const std::string ALIGNMENT_NAMES [3] = {"good", "evil", "neutral"};
+
+	const std::string DIFFICULTY_NAMES [5] = {"pawn", "knight", "rook", "queen", "king"};
 }
 
 namespace NPrimarySkill

+ 37 - 25
lib/gameState/CGameState.cpp

@@ -29,6 +29,7 @@
 #include "../VCMI_Lib.h"
 #include "../battle/BattleInfo.h"
 #include "../campaign/CampaignState.h"
+#include "../constants/StringConstants.h"
 #include "../filesystem/ResourcePath.h"
 #include "../mapObjectConstructors/AObjectTypeHandler.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
@@ -455,7 +456,7 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, Load::Prog
 	initRandomFactionsForPlayers();
 	randomizeMapObjects();
 	placeStartingHeroes();
-	initStartingResources();
+	initDifficulty();
 	initHeroes();
 	initStartingBonus();
 	initTowns();
@@ -657,6 +658,41 @@ void CGameState::initGlobalBonuses()
 	VLC->creh->loadCrExpBon(globalEffects);
 }
 
+void CGameState::initDifficulty()
+{
+	logGlobal->debug("\tLoading difficulty settings");
+	const JsonNode config = JsonUtils::assembleFromFiles("config/difficulty.json");
+	
+	const JsonNode & difficultyAI(config["ai"][GameConstants::DIFFICULTY_NAMES[scenarioOps->difficulty]]);
+	const JsonNode & difficultyHuman(config["human"][GameConstants::DIFFICULTY_NAMES[scenarioOps->difficulty]]);
+	
+	auto setDifficulty = [](PlayerState & state, const JsonNode & json)
+	{
+		//set starting resources
+		state.resources = TResources(json["resources"]);
+		
+		//set global bonuses
+		for(auto & jsonBonus : json["globalBonuses"].Vector())
+			if(auto bonus = JsonUtils::parseBonus(jsonBonus))
+				state.addNewBonus(bonus);
+		
+		//set battle bonuses
+		for(auto & jsonBonus : json["battleBonuses"].Vector())
+			if(auto bonus = JsonUtils::parseBonus(jsonBonus))
+				state.battleBonuses.push_back(*bonus);
+		
+	};
+
+	for (auto & elem : players)
+	{
+		PlayerState &p = elem.second;
+		setDifficulty(p, p.human ? difficultyHuman : difficultyAI);
+	}
+
+	if (campaign)
+		campaign->initStartingResources();
+}
+
 void CGameState::initGrailPosition()
 {
 	logGlobal->debug("\tPicking grail position");
@@ -813,30 +849,6 @@ void CGameState::removeHeroPlaceholders()
 	}
 }
 
-void CGameState::initStartingResources()
-{
-	logGlobal->debug("\tSetting up resources");
-	const JsonNode config(JsonPath::builtin("config/startres.json"));
-	const JsonVector &vector = config["difficulty"].Vector();
-	const JsonNode &level = vector[scenarioOps->difficulty];
-
-	TResources startresAI(level["ai"]);
-	TResources startresHuman(level["human"]);
-
-	for (auto & elem : players)
-	{
-		PlayerState &p = elem.second;
-
-		if (p.human)
-			p.resources = startresHuman;
-		else
-			p.resources = startresAI;
-	}
-
-	if (campaign)
-		campaign->initStartingResources();
-}
-
 void CGameState::initHeroes()
 {
 	for(auto hero : map->heroesOnMap)  //heroes instances initialization

+ 1 - 1
lib/gameState/CGameState.h

@@ -192,7 +192,7 @@ private:
 	void placeStartingHeroes();
 	void placeStartingHero(const PlayerColor & playerColor, const HeroTypeID & heroTypeId, int3 townPos);
 	void removeHeroPlaceholders();
-	void initStartingResources();
+	void initDifficulty();
 	void initHeroes();
 	void placeHeroesInTowns();
 	void initFogOfWar();

+ 14 - 0
server/battles/BattleProcessor.cpp

@@ -24,6 +24,7 @@
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/mapping/CMap.h"
 #include "../../lib/modding/IdentifierStorage.h"
+#include "../../lib/CPlayerState.h"
 
 BattleProcessor::BattleProcessor(CGameHandler * gameHandler)
 	: gameHandler(gameHandler)
@@ -113,6 +114,19 @@ void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArm
 
 	const auto * battle = gameHandler->gameState()->getBattle(battleID);
 	assert(battle);
+	
+	//add battle bonuses based from player state only when attacks neutral creatures
+	const auto * attackerInfo = gameHandler->getPlayerState(army1->getOwner(), false);
+	if(attackerInfo && !army2->getOwner().isValidPlayer())
+	{
+		for(auto bonus : attackerInfo->battleBonuses)
+		{
+			GiveBonus giveBonus(GiveBonus::ETarget::HERO);
+			giveBonus.id = hero1->id.getNum();
+			giveBonus.bonus = bonus;
+			gameHandler->sendAndApply(&giveBonus);
+		}
+	}
 
 	auto lastBattleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(battle->sides[0].color));