mirror of
https://github.com/vcmi/vcmi.git
synced 2025-03-17 20:58:07 +02:00
Merge pull request #4989 from IvanSavenko/ai_fix
Fixes for unfinished items from AI pull request review
This commit is contained in:
commit
99da0a15bb
@ -731,55 +731,6 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
|
||||
ps.value = scoreEvaluator.evaluateExchange(updatedAttack, cachedAttack.turn, *targets, innerCache, state);
|
||||
}
|
||||
//! Some units may be dead alltogether. So if they existed before but not now, we know they were killed by the spell
|
||||
for (const auto& unit : all)
|
||||
{
|
||||
if (!unit->isValidTarget())
|
||||
continue;
|
||||
bool isDead = true;
|
||||
for (const auto& remainingUnit : allUnits)
|
||||
{
|
||||
if (remainingUnit->unitId() == unit->unitId())
|
||||
isDead = false;
|
||||
}
|
||||
if (isDead)
|
||||
{
|
||||
auto newHealth = 0;
|
||||
auto oldHealth = vstd::find_or(healthOfStack, unit->unitId(), 0);
|
||||
if (oldHealth != newHealth)
|
||||
{
|
||||
auto damage = std::abs(oldHealth - newHealth);
|
||||
auto originalDefender = cb->getBattle(battleID)->battleGetUnitByID(unit->unitId());
|
||||
auto dpsReduce = AttackPossibility::calculateDamageReduce(
|
||||
nullptr,
|
||||
originalDefender && originalDefender->alive() ? originalDefender : unit,
|
||||
damage,
|
||||
innerCache,
|
||||
state);
|
||||
auto ourUnit = unit->unitSide() == side ? 1 : -1;
|
||||
auto goodEffect = newHealth > oldHealth ? 1 : -1;
|
||||
if (ourUnit * goodEffect == 1)
|
||||
{
|
||||
if (ourUnit && goodEffect && (unit->isClone() || unit->isGhost()))
|
||||
continue;
|
||||
ps.value += dpsReduce * scoreEvaluator.getPositiveEffectMultiplier();
|
||||
}
|
||||
else
|
||||
ps.value -= dpsReduce * scoreEvaluator.getNegativeEffectMultiplier();
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace(
|
||||
"Spell %s to %d affects %s (%d), dps: %2f oldHealth: %d newHealth: %d",
|
||||
ps.spell->getNameTranslated(),
|
||||
ps.dest.at(0).hexValue.hex,
|
||||
unit->creatureId().toCreature()->getNameSingularTranslated(),
|
||||
unit->getCount(),
|
||||
dpsReduce,
|
||||
oldHealth,
|
||||
newHealth);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
for(const auto & unit : allUnits)
|
||||
{
|
||||
if(!unit->isValidTarget(true))
|
||||
|
@ -28,6 +28,8 @@ namespace NKAI
|
||||
scoutHeroTurnDistanceLimit(5),
|
||||
maxGoldPressure(0.3f),
|
||||
maxpass(10),
|
||||
pathfinderBucketsCount(1),
|
||||
pathfinderBucketSize(32),
|
||||
allowObjectGraph(true),
|
||||
useTroopsFromGarrisons(false),
|
||||
openMap(true),
|
||||
@ -35,49 +37,16 @@ namespace NKAI
|
||||
{
|
||||
JsonNode node = JsonUtils::assembleFromFiles("config/ai/nkai/nkai-settings");
|
||||
|
||||
if(node.Struct()["maxRoamingHeroes"].isNumber())
|
||||
{
|
||||
maxRoamingHeroes = node.Struct()["maxRoamingHeroes"].Integer();
|
||||
}
|
||||
|
||||
if(node.Struct()["mainHeroTurnDistanceLimit"].isNumber())
|
||||
{
|
||||
mainHeroTurnDistanceLimit = node.Struct()["mainHeroTurnDistanceLimit"].Integer();
|
||||
}
|
||||
|
||||
if(node.Struct()["scoutHeroTurnDistanceLimit"].isNumber())
|
||||
{
|
||||
scoutHeroTurnDistanceLimit = node.Struct()["scoutHeroTurnDistanceLimit"].Integer();
|
||||
}
|
||||
|
||||
if(node.Struct()["maxpass"].isNumber())
|
||||
{
|
||||
maxpass = node.Struct()["maxpass"].Integer();
|
||||
}
|
||||
|
||||
if(node.Struct()["maxGoldPressure"].isNumber())
|
||||
{
|
||||
maxGoldPressure = node.Struct()["maxGoldPressure"].Float();
|
||||
}
|
||||
|
||||
if(!node.Struct()["allowObjectGraph"].isNull())
|
||||
{
|
||||
allowObjectGraph = node.Struct()["allowObjectGraph"].Bool();
|
||||
}
|
||||
|
||||
if(!node.Struct()["openMap"].isNull())
|
||||
{
|
||||
openMap = node.Struct()["openMap"].Bool();
|
||||
}
|
||||
|
||||
if (!node.Struct()["useFuzzy"].isNull())
|
||||
{
|
||||
useFuzzy = node.Struct()["useFuzzy"].Bool();
|
||||
}
|
||||
|
||||
if(!node.Struct()["useTroopsFromGarrisons"].isNull())
|
||||
{
|
||||
useTroopsFromGarrisons = node.Struct()["useTroopsFromGarrisons"].Bool();
|
||||
}
|
||||
maxRoamingHeroes = node["maxRoamingHeroes"].Integer();
|
||||
mainHeroTurnDistanceLimit = node["mainHeroTurnDistanceLimit"].Integer();
|
||||
scoutHeroTurnDistanceLimit = node["scoutHeroTurnDistanceLimit"].Integer();
|
||||
maxpass = node["maxpass"].Integer();
|
||||
pathfinderBucketsCount = node["pathfinderBucketsCount"].Integer();
|
||||
pathfinderBucketSize = node["pathfinderBucketSize"].Integer();
|
||||
maxGoldPressure = node["maxGoldPressure"].Float();
|
||||
allowObjectGraph = node["allowObjectGraph"].Bool();
|
||||
openMap = node["openMap"].Bool();
|
||||
useFuzzy = node["useFuzzy"].Bool();
|
||||
useTroopsFromGarrisons = node["useTroopsFromGarrisons"].Bool();
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ namespace NKAI
|
||||
int mainHeroTurnDistanceLimit;
|
||||
int scoutHeroTurnDistanceLimit;
|
||||
int maxpass;
|
||||
int pathfinderBucketsCount;
|
||||
int pathfinderBucketSize;
|
||||
float maxGoldPressure;
|
||||
bool allowObjectGraph;
|
||||
bool useTroopsFromGarrisons;
|
||||
@ -39,6 +41,8 @@ namespace NKAI
|
||||
int getMaxRoamingHeroes() const { return maxRoamingHeroes; }
|
||||
int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; }
|
||||
int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; }
|
||||
int getPathfinderBucketsCount() const { return pathfinderBucketsCount; }
|
||||
int getPathfinderBucketSize() const { return pathfinderBucketSize; }
|
||||
bool isObjectGraphAllowed() const { return allowObjectGraph; }
|
||||
bool isGarrisonTroopsUsageAllowed() const { return useTroopsFromGarrisons; }
|
||||
bool isOpenMap() const { return openMap; }
|
||||
|
@ -39,17 +39,17 @@ const uint64_t CHAIN_MAX_DEPTH = 4;
|
||||
|
||||
const bool DO_NOT_SAVE_TO_COMMITTED_TILES = false;
|
||||
|
||||
AISharedStorage::AISharedStorage(int3 sizes)
|
||||
AISharedStorage::AISharedStorage(int3 sizes, int numChains)
|
||||
{
|
||||
if(!shared){
|
||||
shared.reset(new boost::multi_array<AIPathNode, 4>(
|
||||
boost::extents[sizes.z][sizes.x][sizes.y][AIPathfinding::NUM_CHAINS]));
|
||||
boost::extents[sizes.z][sizes.x][sizes.y][numChains]));
|
||||
|
||||
nodes = shared;
|
||||
|
||||
foreach_tile_pos([&](const int3 & pos)
|
||||
{
|
||||
for(auto i = 0; i < AIPathfinding::NUM_CHAINS; i++)
|
||||
for(auto i = 0; i < numChains; i++)
|
||||
{
|
||||
auto & node = get(pos)[i];
|
||||
|
||||
@ -92,8 +92,18 @@ void AIPathNode::addSpecialAction(std::shared_ptr<const SpecialAction> action)
|
||||
}
|
||||
}
|
||||
|
||||
int AINodeStorage::getBucketCount() const
|
||||
{
|
||||
return ai->settings->getPathfinderBucketsCount();
|
||||
}
|
||||
|
||||
int AINodeStorage::getBucketSize() const
|
||||
{
|
||||
return ai->settings->getPathfinderBucketSize();
|
||||
}
|
||||
|
||||
AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes)
|
||||
: sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes)
|
||||
: sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes, ai->settings->getPathfinderBucketSize() * ai->settings->getPathfinderBucketsCount())
|
||||
{
|
||||
accessibility = std::make_unique<boost::multi_array<EPathAccessibility, 4>>(
|
||||
boost::extents[sizes.z][sizes.x][sizes.y][EPathfindingLayer::NUM_LAYERS]);
|
||||
@ -169,8 +179,8 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
|
||||
const EPathfindingLayer layer,
|
||||
const ChainActor * actor)
|
||||
{
|
||||
int bucketIndex = ((uintptr_t)actor + static_cast<uint32_t>(layer)) % AIPathfinding::BUCKET_COUNT;
|
||||
int bucketOffset = bucketIndex * AIPathfinding::BUCKET_SIZE;
|
||||
int bucketIndex = ((uintptr_t)actor + static_cast<uint32_t>(layer)) % ai->settings->getPathfinderBucketsCount();
|
||||
int bucketOffset = bucketIndex * ai->settings->getPathfinderBucketSize();
|
||||
auto chains = nodes.get(pos);
|
||||
|
||||
if(blocked(pos, layer))
|
||||
@ -178,7 +188,7 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for(auto i = AIPathfinding::BUCKET_SIZE - 1; i >= 0; i--)
|
||||
for(auto i = ai->settings->getPathfinderBucketSize() - 1; i >= 0; i--)
|
||||
{
|
||||
AIPathNode & node = chains[i + bucketOffset];
|
||||
|
||||
@ -486,8 +496,8 @@ public:
|
||||
AINodeStorage & storage, const std::vector<int3> & tiles, uint64_t chainMask, int heroChainTurn)
|
||||
:existingChains(), newChains(), delayedWork(), storage(storage), chainMask(chainMask), heroChainTurn(heroChainTurn), heroChain(), tiles(tiles)
|
||||
{
|
||||
existingChains.reserve(AIPathfinding::NUM_CHAINS);
|
||||
newChains.reserve(AIPathfinding::NUM_CHAINS);
|
||||
existingChains.reserve(storage.getBucketCount() * storage.getBucketSize());
|
||||
newChains.reserve(storage.getBucketCount() * storage.getBucketSize());
|
||||
}
|
||||
|
||||
void execute(const tbb::blocked_range<size_t>& r)
|
||||
|
@ -29,9 +29,6 @@ namespace NKAI
|
||||
{
|
||||
namespace AIPathfinding
|
||||
{
|
||||
const int BUCKET_COUNT = 1;
|
||||
const int BUCKET_SIZE = 32;
|
||||
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
|
||||
const int CHAIN_MAX_DEPTH = 4;
|
||||
}
|
||||
|
||||
@ -157,7 +154,7 @@ public:
|
||||
static boost::mutex locker;
|
||||
static uint32_t version;
|
||||
|
||||
AISharedStorage(int3 mapSize);
|
||||
AISharedStorage(int3 sizes, int numChains);
|
||||
~AISharedStorage();
|
||||
|
||||
STRONG_INLINE
|
||||
@ -197,6 +194,9 @@ public:
|
||||
bool selectFirstActor();
|
||||
bool selectNextActor();
|
||||
|
||||
int getBucketCount() const;
|
||||
int getBucketSize() const;
|
||||
|
||||
std::vector<CGPathNode *> getInitialNodes() override;
|
||||
|
||||
virtual void calculateNeighbours(
|
||||
@ -298,7 +298,7 @@ public:
|
||||
|
||||
inline int getBucket(const ChainActor * actor) const
|
||||
{
|
||||
return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT;
|
||||
return ((uintptr_t)actor * 395) % getBucketCount();
|
||||
}
|
||||
|
||||
void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
|
||||
|
@ -1112,6 +1112,6 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr<ElementInfo> info, bool
|
||||
labelName->alignment = ETextAlignment::CENTER;
|
||||
labelName->moveTo(Point(pos.x + LABEL_POS_X, labelName->pos.y));
|
||||
}
|
||||
labelName->setText(info->getNameForList());
|
||||
labelName->setText(info->name);
|
||||
labelName->setColor(color);
|
||||
}
|
||||
|
@ -6,5 +6,8 @@
|
||||
"maxGoldPressure" : 0.3,
|
||||
"useTroopsFromGarrisons" : true,
|
||||
"openMap": true,
|
||||
"allowObjectGraph": false
|
||||
"allowObjectGraph": false,
|
||||
"pathfinderBucketsCount" : 1, // old value: 3,
|
||||
"pathfinderBucketSize" : 32, // old value: 7,
|
||||
"useFuzzy" : false
|
||||
}
|
@ -488,6 +488,20 @@
|
||||
// if enabled flying will work like in original game, otherwise nerf similar to HotA flying is applied
|
||||
"originalFlyRules" : true
|
||||
},
|
||||
|
||||
"resources" : {
|
||||
// H3 mechanics - AI receives bonus (or malus, on easy) to his resource income
|
||||
// AI will receive specified values as percentage of his weekly income
|
||||
// So, "gems" : 200 will give AI player 200% of his daily income of gems over week, or, in other words,
|
||||
// giving AI player 2 additional gems per week for every owned Gem Pond
|
||||
"weeklyBonusesAI" : {
|
||||
"pawn" : { "gold" : -175 },
|
||||
"knight": {},
|
||||
"rook" : {},
|
||||
"queen" : { "wood" : 275 , "mercury" : 100, "ore" : 275, "sulfur" : 100, "crystal" : 100, "gems" : 100, "gold" : 175},
|
||||
"king" : { "wood" : 375 , "mercury" : 200, "ore" : 375, "sulfur" : 200, "crystal" : 200, "gems" : 200, "gold" : 350}
|
||||
}
|
||||
},
|
||||
|
||||
"spells":
|
||||
{
|
||||
|
@ -133,6 +133,14 @@
|
||||
"originalFlyRules" : { "type" : "boolean" }
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
"properties" : {
|
||||
"weeklyBonusesAI" : { "type" : "object" }
|
||||
}
|
||||
},
|
||||
|
||||
"spells": {
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
|
@ -89,6 +89,7 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
|
||||
{EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique" },
|
||||
{EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY, "pathfinder", "useMonolithTwoWay" },
|
||||
{EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" },
|
||||
{EGameSettings::RESOURCES_WEEKLY_BONUSES_AI, "resources", "weeklyBonusesAI" },
|
||||
{EGameSettings::TEXTS_ARTIFACT, "textData", "artifact" },
|
||||
{EGameSettings::TEXTS_CREATURE, "textData", "creature" },
|
||||
{EGameSettings::TEXTS_FACTION, "textData", "faction" },
|
||||
|
@ -67,6 +67,7 @@ enum class EGameSettings
|
||||
PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE,
|
||||
PATHFINDER_USE_MONOLITH_TWO_WAY,
|
||||
PATHFINDER_USE_WHIRLPOOL,
|
||||
RESOURCES_WEEKLY_BONUSES_AI,
|
||||
TEXTS_ARTIFACT,
|
||||
TEXTS_CREATURE,
|
||||
TEXTS_FACTION,
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "../../lib/IGameSettings.h"
|
||||
#include "../../lib/StartInfo.h"
|
||||
#include "../../lib/TerrainHandler.h"
|
||||
#include "../../lib/constants/StringConstants.h"
|
||||
#include "../../lib/entities/building/CBuilding.h"
|
||||
#include "../../lib/entities/faction/CTownHandler.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
@ -240,46 +241,22 @@ ResourceSet NewTurnProcessor::generatePlayerIncome(PlayerColor playerID, bool ne
|
||||
if (!state.isHuman())
|
||||
{
|
||||
// Initialize bonuses for different resources
|
||||
std::array<int, GameResID::COUNT> weeklyBonuses = {};
|
||||
|
||||
// Calculate weekly bonuses based on difficulty
|
||||
if (gameHandler->gameState()->getStartInfo()->difficulty == 0)
|
||||
{
|
||||
weeklyBonuses[EGameResID::GOLD] = static_cast<int>(std::round(incomeHandicapped[EGameResID::GOLD] * (0.75 - 1) * 7));
|
||||
}
|
||||
else if (gameHandler->gameState()->getStartInfo()->difficulty == 3)
|
||||
{
|
||||
weeklyBonuses[EGameResID::GOLD] = static_cast<int>(std::round(incomeHandicapped[EGameResID::GOLD] * 0.25 * 7));
|
||||
weeklyBonuses[EGameResID::WOOD] = static_cast<int>(std::round(incomeHandicapped[EGameResID::WOOD] * 0.39 * 7));
|
||||
weeklyBonuses[EGameResID::ORE] = static_cast<int>(std::round(incomeHandicapped[EGameResID::ORE] * 0.39 * 7));
|
||||
weeklyBonuses[EGameResID::MERCURY] = static_cast<int>(std::round(incomeHandicapped[EGameResID::MERCURY] * 0.14 * 7));
|
||||
weeklyBonuses[EGameResID::CRYSTAL] = static_cast<int>(std::round(incomeHandicapped[EGameResID::CRYSTAL] * 0.14 * 7));
|
||||
weeklyBonuses[EGameResID::SULFUR] = static_cast<int>(std::round(incomeHandicapped[EGameResID::SULFUR] * 0.14 * 7));
|
||||
weeklyBonuses[EGameResID::GEMS] = static_cast<int>(std::round(incomeHandicapped[EGameResID::GEMS] * 0.14 * 7));
|
||||
}
|
||||
else if (gameHandler->gameState()->getStartInfo()->difficulty == 4)
|
||||
{
|
||||
weeklyBonuses[EGameResID::GOLD] = static_cast<int>(std::round(incomeHandicapped[EGameResID::GOLD] * 0.5 * 7));
|
||||
weeklyBonuses[EGameResID::WOOD] = static_cast<int>(std::round(incomeHandicapped[EGameResID::WOOD] * 0.53 * 7));
|
||||
weeklyBonuses[EGameResID::ORE] = static_cast<int>(std::round(incomeHandicapped[EGameResID::ORE] * 0.53 * 7));
|
||||
weeklyBonuses[EGameResID::MERCURY] = static_cast<int>(std::round(incomeHandicapped[EGameResID::MERCURY] * 0.28 * 7));
|
||||
weeklyBonuses[EGameResID::CRYSTAL] = static_cast<int>(std::round(incomeHandicapped[EGameResID::CRYSTAL] * 0.28 * 7));
|
||||
weeklyBonuses[EGameResID::SULFUR] = static_cast<int>(std::round(incomeHandicapped[EGameResID::SULFUR] * 0.28 * 7));
|
||||
weeklyBonuses[EGameResID::GEMS] = static_cast<int>(std::round(incomeHandicapped[EGameResID::GEMS] * 0.28 * 7));
|
||||
}
|
||||
int difficultyIndex = gameHandler->gameState()->getStartInfo()->difficulty;
|
||||
const std::string & difficultyName = GameConstants::DIFFICULTY_NAMES[difficultyIndex];
|
||||
const JsonNode & weeklyBonusesConfig = gameHandler->gameState()->getSettings().getValue(EGameSettings::RESOURCES_WEEKLY_BONUSES_AI);
|
||||
const JsonNode & difficultyConfig = weeklyBonusesConfig[difficultyName];
|
||||
|
||||
// Distribute weekly bonuses over 7 days, depending on the current day of the week
|
||||
for (int i = 0; i < GameResID::COUNT; ++i)
|
||||
for (GameResID i : GameResID::ALL_RESOURCES())
|
||||
{
|
||||
int dailyBonus = weeklyBonuses[i] / 7;
|
||||
int remainderBonus = weeklyBonuses[i] % 7;
|
||||
|
||||
// Apply the daily bonus for each day, and distribute the remainder accordingly
|
||||
incomeHandicapped[static_cast<GameResID>(i)] += dailyBonus;
|
||||
if (gameHandler->gameState()->getDate(Date::DAY_OF_WEEK) - 1 < remainderBonus)
|
||||
{
|
||||
incomeHandicapped[static_cast<GameResID>(i)] += 1;
|
||||
}
|
||||
const std::string & name = GameConstants::RESOURCE_NAMES[i];
|
||||
int weeklyBonus = difficultyConfig[name].Integer();
|
||||
int dayOfWeek = gameHandler->gameState()->getDate(Date::DAY_OF_WEEK);
|
||||
int dailyIncome = incomeHandicapped[i];
|
||||
int amountTillToday = dailyIncome * weeklyBonus * (dayOfWeek-1) / 7 / 100;
|
||||
int amountAfterToday = dailyIncome * weeklyBonus * dayOfWeek / 7 / 100;
|
||||
int dailyBonusToday = amountAfterToday - amountTillToday;
|
||||
incomeHandicapped[static_cast<GameResID>(i)] += dailyBonusToday;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit b796f7d44681514f58a683a3a71ff17c94edb0c1
|
||||
Subproject commit b514bdc898e2951020cbdca1304b75f5950d1f59
|
Loading…
x
Reference in New Issue
Block a user