Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/asm/heroes2_imports.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/cpp/shared/combat/army.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
20 changes: 20 additions & 0 deletions src/cpp/shared/combat/combat.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
294 changes: 294 additions & 0 deletions src/cpp/shared/combat/combat_ai.cpp
Original file line number Diff line number Diff line change
@@ -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:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a break here?

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
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As written, only AI_MASS_DEBUFF_SPELL will reach this line. What's with the rest of this?

|| 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)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know what this is for?

Copy link
Copy Markdown
Collaborator Author

@Kert Kert Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for area spells: fireball, fireblast, meteorshower and coldring

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I mean: what's special about hex 43?

targetHex = 1;
break;
}
continue;
}
break;
}
}

int combatManager::RawEffectSpellInfluence(army *stack, int eff) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The argument should be STACK_MODIFYING_EFFECT, not int.

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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь должно быть:
if(!(stack->owningSide))

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))
Copy link
Copy Markdown

@gandalfgray gandalfgray Dec 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, 1-owningSide и otherSide - одно и то же

hasAdjacentDragons = true;
}
}
double dragonsVal;
if(hasAdjacentDragons)
dragonsVal = 1.0;
else
dragonsVal = numDragons / this->numCreatures[otherSide];
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will basically always return 0. Should it be this way?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fightValue multiplier is missing, sorry.

My last comment concerned raw 211 (if(!(stack->owningSide)) instead of if(!this->currentActionSide))
After I see my comment was ignored and progress completely stopped I did not add new comments.

preResult = 0.28 * dragonsVal;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dragonsVal cannot be higher than 1.

Therefore preResult cannot be higher than 0.28.

preResult gets rounded to an int, i.e.: 0.

Therefore: The effect of dragon slayer is always 0 and the AI never casts it.

Is this right?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, looking at the decompiled code, this problem seems to have already been there.

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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, otherSide и 1-owningSide - то же самое :)

numShooters++;
}
double shooterVal = numShooters / this->numCreatures[otherSide];
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does integer division. It will basically always be zero. Is it supposed to be this way?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fightValue multiplier is missing.

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;
}
3 changes: 2 additions & 1 deletion src/cpp/shared/combat/combat_spells.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1827,4 +1827,5 @@ int combatManager::ViewSpells(int unused) {
return 0;
return 1;
}
}
}

1 change: 1 addition & 0 deletions src/vcxproj/ironfist/ironfist.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
<ClCompile Include="..\..\cpp\shared\combat\animation.cpp" />
<ClCompile Include="..\..\cpp\shared\combat\army.cpp" />
<ClCompile Include="..\..\cpp\shared\combat\combat.cpp" />
<ClCompile Include="..\..\cpp\shared\combat\combat_ai.cpp" />
<ClCompile Include="..\..\cpp\shared\combat\combat_spells.cpp" />
<ClCompile Include="..\..\cpp\shared\combat\creatures.cpp" />
<ClCompile Include="..\..\cpp\shared\compat.cpp" />
Expand Down
3 changes: 3 additions & 0 deletions src/vcxproj/ironfist/ironfist.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,9 @@
<ClCompile Include="..\..\cpp\shared\adventure\adv_globals.cpp">
<Filter>Source Files\adventure</Filter>
</ClCompile>
<ClCompile Include="..\..\cpp\shared\combat\combat_ai.cpp">
<Filter>Source Files\combat</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\cpp\shared\stdafx.h">
Expand Down