1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-25 22:42:04 +02:00

Merge pull request #5110 from IvanSavenko/ai_optimize

[1.6.1] AI optimization
This commit is contained in:
Ivan Savenko
2024-12-25 00:12:38 +02:00
committed by GitHub
65 changed files with 517 additions and 296 deletions

View File

@@ -38,9 +38,6 @@ void CArmedInstance::randomizeArmy(FactionID type)
}
}
// Take Angelic Alliance troop-mixing freedom of non-evil units into account.
CSelector CArmedInstance::nonEvilAlignmentMixSelector = Selector::type()(BonusType::NONEVIL_ALIGNMENT_MIX);
CArmedInstance::CArmedInstance(IGameCallback *cb)
:CArmedInstance(cb, false)
{
@@ -49,7 +46,7 @@ CArmedInstance::CArmedInstance(IGameCallback *cb)
CArmedInstance::CArmedInstance(IGameCallback *cb, bool isHypothetic):
CGObjectInstance(cb),
CBonusSystemNode(isHypothetic),
nonEvilAlignmentMix(this, nonEvilAlignmentMixSelector),
nonEvilAlignmentMix(this, BonusType::NONEVIL_ALIGNMENT_MIX), // Take Angelic Alliance troop-mixing freedom of non-evil units into account.
battle(nullptr)
{
}

View File

@@ -336,7 +336,7 @@ void CGCreature::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
{
//calculate relative strength of hero and creatures armies
double relStrength = static_cast<double>(h->getTotalStrength()) / getArmyStrength();
double relStrength = static_cast<double>(h->getValueForDiplomacy()) / getArmyStrength();
int powerFactor;
if(relStrength >= 7)

View File

@@ -257,8 +257,7 @@ void CGHeroInstance::setMovementPoints(int points)
int CGHeroInstance::movementPointsLimit(bool onLand) const
{
TurnInfo ti(this);
return movementPointsLimitCached(onLand, &ti);
return valOfBonuses(BonusType::MOVEMENT, onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea);
}
int CGHeroInstance::getLowestCreatureSpeed() const
@@ -274,7 +273,7 @@ void CGHeroInstance::updateArmyMovementBonus(bool onLand, const TurnInfo * ti) c
lowestCreatureSpeed = realLowestSpeed;
//Let updaters run again
treeHasChanged();
ti->updateHeroBonuses(BonusType::MOVEMENT, Selector::subtype()(onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea));
ti->updateHeroBonuses(BonusType::MOVEMENT);
}
}
@@ -406,7 +405,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
putArtifact(ArtifactPosition::MACH4, artifact); //everyone has a catapult
}
if(!hasBonus(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)))
if(!hasBonusFrom(BonusSource::HERO_BASE_SKILL))
{
for(int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
{
@@ -682,7 +681,7 @@ void CGHeroInstance::pickRandomObject(vstd::RNG & rand)
void CGHeroInstance::recreateSecondarySkillsBonuses()
{
auto secondarySkillsBonuses = getBonuses(Selector::sourceType()(BonusSource::SECONDARY_SKILL));
auto secondarySkillsBonuses = getBonusesFrom(BonusSource::SECONDARY_SKILL);
for(const auto & bonus : *secondarySkillsBonuses)
removeBonus(bonus);
@@ -705,19 +704,46 @@ void CGHeroInstance::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
setStackCount(SlotID(0), identifier.getNum());
}
std::array<int, 4> CGHeroInstance::getPrimarySkills() const
{
std::array<int, 4> result;
auto allSkills = getBonusBearer()->getBonusesOfType(BonusType::PRIMARY_SKILL);
for (auto skill : PrimarySkill::ALL_SKILLS())
{
int ret = allSkills->valOfBonuses(Selector::subtype()(BonusSubtypeID(skill)));
int minSkillValue = VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, skill.getNum());
result[skill] = std::max(ret, minSkillValue); //otherwise, some artifacts may cause negative skill value effect
}
return result;
}
double CGHeroInstance::getFightingStrength() const
{
return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::ATTACK)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::DEFENSE)));
const auto & primarySkills = getPrimarySkills();
return getFightingStrengthImpl(primarySkills);
}
double CGHeroInstance::getFightingStrengthImpl(const std::array<int, 4> & primarySkills) const
{
return sqrt((1.0 + 0.05*primarySkills[PrimarySkill::ATTACK]) * (1.0 + 0.05*primarySkills[PrimarySkill::DEFENSE]));
}
double CGHeroInstance::getMagicStrength() const
{
const auto & primarySkills = getPrimarySkills();
return getMagicStrengthImpl(primarySkills);
}
double CGHeroInstance::getMagicStrengthImpl(const std::array<int, 4> & primarySkills) const
{
if (!hasSpellbook())
return 1;
bool atLeastOneCombatSpell = false;
for (auto spell : spells)
{
if (spellbookContainsSpell(spell) && spell.toSpell()->isCombat())
if (spell.toSpell()->isCombat())
{
atLeastOneCombatSpell = true;
break;
@@ -725,22 +751,40 @@ double CGHeroInstance::getMagicStrength() const
}
if (!atLeastOneCombatSpell)
return 1;
return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::KNOWLEDGE) * mana / manaLimit()) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::SPELL_POWER) * mana / manaLimit()));
}
double CGHeroInstance::getMagicStrengthForCampaign() const
{
return sqrt((1.0 + 0.05 * getPrimSkillLevel(PrimarySkill::KNOWLEDGE)) * (1.0 + 0.05 * getPrimSkillLevel(PrimarySkill::SPELL_POWER)));
return sqrt((1.0 + 0.05*primarySkills[PrimarySkill::KNOWLEDGE] * mana / manaLimit()) * (1.0 + 0.05*primarySkills[PrimarySkill::SPELL_POWER] * mana / manaLimit()));
}
double CGHeroInstance::getHeroStrength() const
{
return sqrt(pow(getFightingStrength(), 2.0) * pow(getMagicStrength(), 2.0));
const auto & primarySkills = getPrimarySkills();
return getFightingStrengthImpl(primarySkills) * getMagicStrengthImpl(primarySkills);
}
double CGHeroInstance::getHeroStrengthForCampaign() const
uint64_t CGHeroInstance::getValueForDiplomacy() const
{
return sqrt(pow(getFightingStrength(), 2.0) * pow(getMagicStrengthForCampaign(), 2.0));
// H3 formula for hero strength when considering diplomacy skill
uint64_t armyStrength = getArmyStrength();
double heroStrength = sqrt(
(1.0 + 0.05 * getPrimSkillLevel(PrimarySkill::ATTACK)) *
(1.0 + 0.05 * getPrimSkillLevel(PrimarySkill::DEFENSE))
);
return heroStrength * armyStrength;
}
uint32_t CGHeroInstance::getValueForCampaign() const
{
/// Determined by testing H3: hero is preferred for transfer in campaigns if total sum of his primary skills and his secondary skill levels is greatest
uint32_t score = 0;
score += getPrimSkillLevel(PrimarySkill::ATTACK);
score += getPrimSkillLevel(PrimarySkill::DEFENSE);
score += getPrimSkillLevel(PrimarySkill::SPELL_POWER);
score += getPrimSkillLevel(PrimarySkill::DEFENSE);
for (const auto& secondary : secSkills)
score += secondary.second;
return score;
}
ui64 CGHeroInstance::getTotalStrength() const
@@ -973,7 +1017,7 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b
// figure out what to raise - pick strongest creature meeting requirements
CreatureID creatureTypeRaised = CreatureID::NONE; //now we always have IMPROVED_NECROMANCY, no need for hardcode
int requiredCasualtyLevel = 1;
TConstBonusListPtr improvedNecromancy = getBonuses(Selector::type()(BonusType::IMPROVED_NECROMANCY));
TConstBonusListPtr improvedNecromancy = getBonusesOfType(BonusType::IMPROVED_NECROMANCY);
if(!improvedNecromancy->empty())
{
int maxCasualtyLevel = 1;
@@ -1141,9 +1185,8 @@ void CGHeroInstance::pushPrimSkill( PrimarySkill which, int val )
{
auto sel = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(which))
.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
if(hasBonus(sel))
removeBonuses(sel);
removeBonuses(sel);
addNewBonus(std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::HERO_BASE_SKILL, val, BonusSourceID(id), BonusSubtypeID(which)));
}
@@ -1281,7 +1324,7 @@ const std::set<SpellID> & CGHeroInstance::getSpellsInSpellbook() const
int CGHeroInstance::maxSpellLevel() const
{
return std::min(GameConstants::SPELL_LEVELS, valOfBonuses(Selector::type()(BonusType::MAX_LEARNABLE_SPELL_LEVEL)));
return std::min(GameConstants::SPELL_LEVELS, valOfBonuses(BonusType::MAX_LEARNABLE_SPELL_LEVEL));
}
void CGHeroInstance::attachToBoat(CGBoat* newBoat)
@@ -1655,11 +1698,11 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
{
auto primarySkills = handler.enterStruct("primarySkills");
for(auto i = PrimarySkill::BEGIN; i < PrimarySkill::END; ++i)
for(auto skill : PrimarySkill::ALL_SKILLS())
{
int value = valOfBonuses(Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(i)).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)));
int value = valOfBonuses(Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(skill)).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)));
handler.serializeInt(NPrimarySkill::names[i.getNum()], value, 0);
handler.serializeInt(NPrimarySkill::names[skill.getNum()], value, 0);
}
}
}
@@ -1850,7 +1893,7 @@ bool CGHeroInstance::isMissionCritical() const
void CGHeroInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance & stack) const
{
TConstBonusListPtr lista = getBonuses(Selector::typeSubtype(BonusType::SPECIAL_UPGRADE, BonusSubtypeID(stack.getId())));
TConstBonusListPtr lista = getBonusesOfType(BonusType::SPECIAL_UPGRADE, BonusSubtypeID(stack.getId()));
for(const auto & it : *lista)
{
auto nid = CreatureID(it->additionalInfo[0]);
@@ -1921,9 +1964,9 @@ const IOwnableObject * CGHeroInstance::asOwnable() const
int CGHeroInstance::getBasePrimarySkillValue(PrimarySkill which) const
{
std::string cachingStr = "type_PRIMARY_SKILL_base_" + std::to_string(static_cast<int>(which));
std::string cachingStr = "CGHeroInstance::getBasePrimarySkillValue" + std::to_string(static_cast<int>(which));
auto selector = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(which)).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
auto minSkillValue = VLC->engineSettings()->getVector(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS)[which.getNum()];
auto minSkillValue = VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, which.getNum());
return std::max(valOfBonuses(selector, cachingStr), minSkillValue);
}

View File

@@ -62,6 +62,9 @@ private:
mutable int lowestCreatureSpeed;
ui32 movement; //remaining movement points
double getFightingStrengthImpl(const std::array<int, 4> & primarySkills) const;
double getMagicStrengthImpl(const std::array<int, 4> & primarySkills) const;
public:
//////////////////////////////////////////////////////////////////////////
@@ -201,6 +204,7 @@ public:
std::vector<SecondarySkill> getLevelUpProposedSecondarySkills(vstd::RNG & rand) const;
ui8 getSecSkillLevel(const SecondarySkill & skill) const; //0 - no skill
std::array<int, 4> getPrimarySkills() const;
/// Returns true if hero has free secondary skill slot.
bool canLearnSkill() const;
@@ -225,9 +229,11 @@ public:
double getFightingStrength() const; // takes attack / defense skill into account
double getMagicStrength() const; // takes knowledge / spell power skill but also current mana, whether the hero owns a spell-book and whether that books contains anything into account
double getMagicStrengthForCampaign() const; // takes knowledge / spell power skill into account
double getHeroStrength() const; // includes fighting and magic strength
double getHeroStrengthForCampaign() const; // includes fighting and the for-campaign-version of magic strength
uint32_t getValueForCampaign() const;
uint64_t getValueForDiplomacy() const;
ui64 getTotalStrength() const; // includes fighting strength and army strength
TExpType calculateXp(TExpType exp) const; //apply learning skill
int getBasePrimarySkillValue(PrimarySkill which) const; //the value of a base-skill without items or temporary bonuses

View File

@@ -161,7 +161,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
ret.entries.emplace_back(subID, BuildingID::HORDE_2, creature->getHorde());
//statue-of-legion-like bonus: % to base+castle
TConstBonusListPtr bonuses2 = getBonuses(Selector::type()(BonusType::CREATURE_GROWTH_PERCENT));
TConstBonusListPtr bonuses2 = getBonusesOfType(BonusType::CREATURE_GROWTH_PERCENT);
for(const auto & b : *bonuses2)
{
const auto growth = b->val * (base + castleBonus) / 100;
@@ -173,7 +173,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
//other *-of-legion-like bonuses (%d to growth cumulative with grail)
// Note: bonus uses 1-based levels (Pikeman is level 1), town list uses 0-based (Pikeman in 0-th creatures entry)
TConstBonusListPtr bonuses = getBonuses(Selector::typeSubtype(BonusType::CREATURE_GROWTH, BonusCustomSubtype::creatureLevel(level+1)));
TConstBonusListPtr bonuses = getBonusesOfType(BonusType::CREATURE_GROWTH, BonusCustomSubtype::creatureLevel(level+1));
for(const auto & b : *bonuses)
ret.entries.emplace_back(b->val, b->Description(cb));