diff --git a/src/asm/heroes2_imports.inc b/src/asm/heroes2_imports.inc
index 97cc3b2c9..c207c6823 100644
--- a/src/asm/heroes2_imports.inc
+++ b/src/asm/heroes2_imports.inc
@@ -291,6 +291,10 @@ IMPORT_?CheckWin@combatManager@@QAEHPAUtag_message@@@Z = 1
IMPORT_?DoHydraAttack@army@@QAEXH@Z = 1
IMPORT_?ProcessNextAction@combatManager@@QAEHAAUtag_message@@@Z = 1
?ProcessNextAction@combatManager@@QAEHAAUtag_message@@@Z_clone EQU ?ProcessNextAction_orig@combatManager@@QAEHAAUtag_message@@@Z
+IMPORT_?RawEffectSpellInfluence@combatManager@@QAEHPAVarmy@@H@Z = 1
+?RawEffectSpellInfluence@combatManager@@QAEHPAVarmy@@H@Z_clone EQU ?RawEffectSpellInfluence_orig@combatManager@@QAEHPAVarmy@@H@Z
+IMPORT_?DetermineEffectOfSpell@combatManager@@QAEXHPAH0@Z = 1
+?DetermineEffectOfSpell@combatManager@@QAEXHPAH0@Z_clone EQU ?DetermineEffectOfSpell_orig@combatManager@@QAEXHPAH0@Z
;;heroes
IMPORT_?cHeroTypeInitial@@3PADA = 1
diff --git a/src/cpp/shared/combat/army.h b/src/cpp/shared/combat/army.h
index 2a65bf913..653423ac6 100644
--- a/src/cpp/shared/combat/army.h
+++ b/src/cpp/shared/combat/army.h
@@ -140,6 +140,7 @@ class army {
int GetPowBaseY();
int RightX();
int LeftX();
+ signed int OtherArmyAdjacent(int side, int idx);
private:
void RevertChargingMoveAnimation();
void SetChargingMoveAnimation(CHARGING_DIRECTION dir);
diff --git a/src/cpp/shared/combat/combat.h b/src/cpp/shared/combat/combat.h
index 5929c4049..dce3b370e 100644
--- a/src/cpp/shared/combat/combat.h
+++ b/src/cpp/shared/combat/combat.h
@@ -57,6 +57,17 @@ enum AOE_SPELL_DRAW_FLIP_TYPE {
AOE_SPELL_DRAW_FLIP
};
+enum AI_SPELL_TYPES {
+ AI_BATTLEFIELD_AFFECTING_SPELL = 0x0,
+ AI_MASS_BUFF_SPELL = 0x1,
+ AI_MASS_DEBUFF_SPELL = 0x2,
+ AI_BALL_SPALL = 0x3,
+ AI_TARGET_BUFF_SPELL = 0x4,
+ AI_TARGET_DAMAGE_OR_DEBUFF_SPELL = 0x5,
+ AI_RESURRECT_SPELL = 0x6,
+ AI_DISPEL_SPELL = 0x7,
+};
+
#pragma pack(push, 1)
class hexcell {
@@ -314,6 +325,13 @@ class combatManager : public baseManager
void PlasmaCone(int hexIdx);
void FireBomb(int hexIdx);
void ImplosionGrenade(int hexIdx);
+ void DetermineEffectOfSpell(int spell, int *value, int *target);
+ void DetermineEffectOfSpell_orig(int spell, int *value, int *target);
+ int RawEffectSpellInfluence(army *stack, int eff);
+ int RawEffectSpellInfluence_orig(army *stack, int eff);
+ signed int FirstArmy(int startHex, int side, int *resultHex);
+ signed int FirstResurrectable(int startHex, int *resultHex, int spell);
+ void NextPos(int *hexIdx);
};
extern combatManager* gpCombatManager;
@@ -328,12 +346,14 @@ extern int giNextAction;
extern int giNextActionExtra;
extern int gbProcessingCombatAction;
extern int giSpellEffectShowType;
+extern int giCurrSpellGroup;
extern unsigned char gColorTableGray[];
extern unsigned char gColorTableRed[];
extern unsigned char gColorTableDarkBrown[];
extern unsigned char gColorTableGray[];
extern unsigned char gColorTableLighten[];
extern char *gCombatFxNames[];
+extern unsigned char giSpellInfluenceToSpell[];
void __fastcall ModifyFrameInfo(struct SMonFrameInfo *frm, int creature);
signed int __fastcall GetAdjacentCellIndexNoArmy(int hexIdx, signed int neighborIdx);
bool IsCastleWall(int hexIdx);
diff --git a/src/cpp/shared/combat/combat_ai.cpp b/src/cpp/shared/combat/combat_ai.cpp
new file mode 100644
index 000000000..8357db099
--- /dev/null
+++ b/src/cpp/shared/combat/combat_ai.cpp
@@ -0,0 +1,294 @@
+#include "combat/combat.h"
+
+float heuristicModifierForDuration[] = {
+ 0.0,
+ 0.5,
+ 0.65,
+ 0.78,
+ 0.85,
+ 0.95,
+ 1.03,
+ 1.08,
+ 1.12,
+ 1.15,
+ 1.18
+};
+
+float durationBenefit[] = {
+ 0.0,
+ 0.33,
+ 0.55,
+ 0.72,
+ 0.85,
+ 0.95,
+ 1.03,
+ 1.08,
+ 1.12,
+ 1.15,
+ 1.18
+};
+
+void combatManager::DetermineEffectOfSpell(int spell, int *value, int *target) {
+ *value = 0;
+ AI_SPELL_TYPES aiSpellType;
+ int targetSide = 0;
+
+ switch(spell) {
+ case SPELL_MASS_SLOW:
+ case SPELL_MASS_CURSE:
+ aiSpellType = AI_MASS_DEBUFF_SPELL;
+ targetSide = 1 - this->currentActionSide;
+ break;
+ case SPELL_SLOW:
+ case SPELL_CURSE:
+ aiSpellType = AI_TARGET_DAMAGE_OR_DEBUFF_SPELL;
+ targetSide = 1 - this->currentActionSide;
+ default:
+ this->DetermineEffectOfSpell_orig(spell, value, target);
+ return;
+ }
+
+ int targetHex = 0;
+ int hex = 1;
+
+ if(aiSpellType == AI_TARGET_BUFF_SPELL
+ || aiSpellType == AI_TARGET_DAMAGE_OR_DEBUFF_SPELL
+ || aiSpellType == AI_MASS_DEBUFF_SPELL
+ || aiSpellType == AI_MASS_BUFF_SPELL
+ || aiSpellType == AI_DISPEL_SPELL)
+ targetHex = this->FirstArmy(1, targetSide, &hex);
+
+ bool targSpentTurn = 1;
+ float durBenefit = 1.0;
+ while(true) {
+ if(!targetHex) {
+ bool incapacitated = false;
+ bool underMyControl = false;
+ army *potTarg = nullptr;
+
+ if(this->combatGrid[hex].stackIdx < 0 || this->combatGrid[hex].unitOwner < 0) {
+ potTarg = 0;
+ } else {
+ potTarg = &this->creatures[this->combatGrid[hex].unitOwner][this->combatGrid[hex].stackIdx];
+ giCurrSpellGroup = this->combatGrid[hex].unitOwner;
+ targSpentTurn = (potTarg->creature.creature_flags & MAYBE_NOT_LOST_TURN) != 0;
+ int duration = this->heroSpellpowers[this->currentActionSide];
+ if(this->heroes[this->currentActionSide]->HasArtifact(ARTIFACT_ENCHANTED_HOURGLASS))
+ duration += 2;
+ if(this->heroes[this->currentActionSide]->HasArtifact(ARTIFACT_WIZARDS_HAT))
+ duration += 10;
+ int durationBenefitIdx = duration - targSpentTurn;
+ if(duration - targSpentTurn >= 10)
+ durationBenefitIdx = 10;
+ durBenefit = durationBenefit[durationBenefitIdx];
+ if(potTarg->effectStrengths[EFFECT_HYPNOTIZE] || potTarg->effectStrengths[EFFECT_BERSERKER])
+ underMyControl = true;
+ if(potTarg->effectStrengths[EFFECT_BLIND] || potTarg->effectStrengths[EFFECT_PARALYZE] || potTarg->effectStrengths[EFFECT_PETRIFY])
+ incapacitated = true;
+ }
+
+ int effectValue = 0;
+ switch(spell) {
+ case SPELL_SLOW:
+ case SPELL_MASS_SLOW:
+ if(!incapacitated) {
+ if(!underMyControl) {
+ if(!potTarg->effectStrengths[EFFECT_SLOW]) {
+ effectValue = -this->RawEffectSpellInfluence(potTarg, EFFECT_SLOW) * durBenefit;
+ if(potTarg->effectStrengths[EFFECT_HASTE]) {
+ int durModifierIdx = targSpentTurn + potTarg->effectStrengths[EFFECT_HASTE];
+ if(durModifierIdx >= 10)
+ durModifierIdx = 10;
+
+ effectValue += this->RawEffectSpellInfluence(potTarg, EFFECT_HASTE) * heuristicModifierForDuration[durModifierIdx];
+ }
+ }
+ }
+ }
+ break;
+ case SPELL_CURSE:
+ case SPELL_MASS_CURSE:
+ if(!incapacitated) {
+ if(!underMyControl) {
+ if(!potTarg->effectStrengths[EFFECT_CURSE]) {
+ effectValue = -this->RawEffectSpellInfluence(potTarg, EFFECT_CURSE) * durBenefit;
+ if(potTarg->effectStrengths[EFFECT_BLESS]) {
+ int durModifierIdx = targSpentTurn + potTarg->effectStrengths[EFFECT_BLESS];
+ if(durModifierIdx >= 10)
+ durModifierIdx = 10;
+ effectValue += this->RawEffectSpellInfluence(potTarg, EFFECT_BLESS) * heuristicModifierForDuration[durModifierIdx];
+ }
+ }
+ }
+ }
+ break;
+ default:
+ *value = 0;
+ break;
+ }
+
+ int finalEffectValue = 0;
+ switch(aiSpellType) {
+ case AI_MASS_BUFF_SPELL:
+ case AI_MASS_DEBUFF_SPELL:
+ finalEffectValue += effectValue;
+ break;
+ case AI_BATTLEFIELD_AFFECTING_SPELL:
+ case AI_BALL_SPALL:
+ case AI_TARGET_BUFF_SPELL:
+ case AI_TARGET_DAMAGE_OR_DEBUFF_SPELL:
+ case AI_RESURRECT_SPELL:
+ case AI_DISPEL_SPELL:
+ finalEffectValue = effectValue;
+ break;
+ default:
+ break;
+ }
+
+ if(aiSpellType == AI_MASS_BUFF_SPELL || aiSpellType == AI_MASS_DEBUFF_SPELL || *value < finalEffectValue) {
+ *value = finalEffectValue;
+ *target = hex;
+ }
+
+ switch(aiSpellType) {
+ case AI_BATTLEFIELD_AFFECTING_SPELL:
+ targetHex = 1;
+ break;
+ case AI_MASS_BUFF_SPELL:
+ case AI_MASS_DEBUFF_SPELL:
+ case AI_TARGET_BUFF_SPELL:
+ case AI_TARGET_DAMAGE_OR_DEBUFF_SPELL:
+ case AI_DISPEL_SPELL:
+ targetHex = this->FirstArmy(hex + 1, targetSide, &hex);
+ break;
+ case AI_RESURRECT_SPELL:
+ targetHex = this->FirstResurrectable(hex + 1, &hex, spell);
+ break;
+ case AI_BALL_SPALL:
+ this->NextPos(&hex);
+ if(hex > 43)
+ targetHex = 1;
+ break;
+ }
+ continue;
+ }
+ break;
+ }
+}
+
+int combatManager::RawEffectSpellInfluence(army *stack, int eff) {
+ double chance = stack->SpellCastWorkChance(giSpellInfluenceToSpell[eff]);
+ if(chance < 0.0001) {
+ return 0;
+ }
+
+ int preResult;
+ int fightValue = stack->quantity * stack->creature.fight_value;
+ int owningSide = stack->owningSide;
+ int otherSide = 1 - owningSide;
+ switch(eff) {
+ case EFFECT_SLOW:
+ case EFFECT_HASTE:
+ {
+ int speedWithEffect;
+ if(eff == EFFECT_SLOW)
+ speedWithEffect = (stack->creature.speed + 1) >> 1;
+ else
+ speedWithEffect = stack->creature.speed + 2;
+
+ if(eff == EFFECT_HASTE && stack->creature.creature_flags & FLYER)
+ return 0;
+
+ if(this->isCastleBattle && owningSide == 1)
+ return 0;
+
+ if(stack->creature.creature_flags & SHOOTER)
+ return 0;
+
+ if(stack->GetAttackMask(stack->occupiedHex, 1, -1) == 255) {
+ int hexXCoord = stack->occupiedHex % 13;
+ int distFromOptimalColumn;
+ if(!this->currentActionSide)
+ distFromOptimalColumn = 10 - hexXCoord;
+ else
+ distFromOptimalColumn = hexXCoord - 2;
+ if(distFromOptimalColumn < 0)
+ distFromOptimalColumn = 0;
+
+
+ int adjustedDistFromOtherSide = distFromOptimalColumn + 2;
+ if(this->isCastleBattle)
+ adjustedDistFromOtherSide += 3;
+ double currentTurnsToOtherSide;
+ if(stack->creature.creature_flags & FLYER)
+ currentTurnsToOtherSide = 1.0;
+ else
+ currentTurnsToOtherSide = adjustedDistFromOtherSide / stack->creature.speed;
+
+ double turnsToOtherSideAfterCast = adjustedDistFromOtherSide / speedWithEffect;
+ if(turnsToOtherSideAfterCast > 7.0)
+ turnsToOtherSideAfterCast = 7.0;
+ if(currentTurnsToOtherSide > 7.0)
+ currentTurnsToOtherSide = 7.0;
+ preResult = (currentTurnsToOtherSide - turnsToOtherSideAfterCast) / 10.0 * fightValue;
+ } else
+ return 0;
+ break;
+ }
+ case EFFECT_BLESS:
+ case EFFECT_CURSE: {
+ double avgDamage = (stack->creature.min_damage + stack->creature.max_damage) * 0.5;
+ preResult = (avgDamage - stack->creature.min_damage) / stack->creature.min_damage * fightValue * 0.45;
+ if(eff == EFFECT_CURSE)
+ preResult = -preResult;
+ break;
+ }
+ case EFFECT_DRAGON_SLAYER: {
+ bool hasAdjacentDragons = false;
+ int numDragons = 0;
+
+ for(int i = 0; this->numCreatures[otherSide] > i; ++i) {
+ army* cr = &this->creatures[otherSide][i];
+ int creatureIdx = cr->creatureIdx;
+ if(creatureIdx == CREATURE_GREEN_DRAGON
+ || creatureIdx == CREATURE_RED_DRAGON
+ || creatureIdx == CREATURE_BLACK_DRAGON
+ || creatureIdx == CREATURE_BONE_DRAGON) {
+ numDragons++;
+ if(stack->OtherArmyAdjacent(otherSide, cr->stackIdx))
+ hasAdjacentDragons = true;
+ }
+ }
+ double dragonsVal;
+ if(hasAdjacentDragons)
+ dragonsVal = 1.0;
+ else
+ dragonsVal = numDragons / this->numCreatures[otherSide];
+ preResult = 0.28 * dragonsVal;
+ break;
+ }
+ case EFFECT_SHIELD: {
+ int numShooters = 0;
+ for(int j = 0; this->numCreatures[otherSide] > j; ++j) {
+ if(this->creatures[otherSide][j].creature.creature_flags & SHOOTER)
+ numShooters++;
+ }
+ double shooterVal = numShooters / this->numCreatures[otherSide];
+ if(!owningSide && this->isCastleBattle) {
+ shooterVal += 0.3;
+ if(shooterVal > 1.0)
+ shooterVal = 1.0;
+ }
+ preResult = 0.45 * shooterVal;
+ break;
+ }
+ default:
+ return this->RawEffectSpellInfluence_orig(stack, eff);
+ }
+
+ if(stack->effectStrengths[EFFECT_BERSERKER] || stack->effectStrengths[EFFECT_HYPNOTIZE]) {
+ if(eff != EFFECT_ANTI_MAGIC)
+ return 0;
+ }
+ return preResult * chance;
+}
\ No newline at end of file
diff --git a/src/cpp/shared/combat/combat_spells.cpp b/src/cpp/shared/combat/combat_spells.cpp
index 9ef0aa58e..dbd1c0d32 100644
--- a/src/cpp/shared/combat/combat_spells.cpp
+++ b/src/cpp/shared/combat/combat_spells.cpp
@@ -1827,4 +1827,5 @@ int combatManager::ViewSpells(int unused) {
return 0;
return 1;
}
-}
\ No newline at end of file
+}
+
diff --git a/src/vcxproj/ironfist/ironfist.vcxproj b/src/vcxproj/ironfist/ironfist.vcxproj
index ea61815eb..1782741a9 100644
--- a/src/vcxproj/ironfist/ironfist.vcxproj
+++ b/src/vcxproj/ironfist/ironfist.vcxproj
@@ -115,6 +115,7 @@
+
diff --git a/src/vcxproj/ironfist/ironfist.vcxproj.filters b/src/vcxproj/ironfist/ironfist.vcxproj.filters
index 59373a7b8..8f1c85ebe 100644
--- a/src/vcxproj/ironfist/ironfist.vcxproj.filters
+++ b/src/vcxproj/ironfist/ironfist.vcxproj.filters
@@ -339,6 +339,9 @@
Source Files\adventure
+
+ Source Files\combat
+