Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use monster abilities instead of hardcoded monster IDs #8816

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
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
87 changes: 29 additions & 58 deletions src/fheroes2/battle/battle_troop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -570,34 +570,24 @@ uint32_t Battle::Unit::CalculateDamageUnit( const Unit & enemy, double dmg ) con
dmg /= 2;
}

switch ( GetID() ) {
case Monster::CRUSADER:
if ( enemy.isUndead() ) {
dmg *= 2;
}
break;
case Monster::FIRE_ELEMENT:
if ( enemy.GetID() == Monster::WATER_ELEMENT ) {
dmg *= 2;
}
break;
case Monster::WATER_ELEMENT:
if ( enemy.GetID() == Monster::FIRE_ELEMENT ) {
dmg *= 2;
}
break;
case Monster::AIR_ELEMENT:
if ( enemy.GetID() == Monster::EARTH_ELEMENT ) {
dmg *= 2;
}
break;
case Monster::EARTH_ELEMENT:
if ( enemy.GetID() == Monster::AIR_ELEMENT ) {
dmg *= 2;
}
break;
default:
break;
if ( isAbilityPresent( fheroes2::MonsterAbilityType::DOUBLE_DAMAGE_TO_UNDEAD ) && enemy.isAbilityPresent( fheroes2::MonsterAbilityType::UNDEAD ) ) {
dmg *= 2;
}

if ( isAbilityPresent( fheroes2::MonsterAbilityType::FIRE_SPELL_IMMUNITY ) && enemy.isWeaknessPresent( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_FIRE ) ) {
dmg *= 2;
}

if ( isAbilityPresent( fheroes2::MonsterAbilityType::COLD_SPELL_IMMUNITY ) && enemy.isWeaknessPresent( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_COLD ) ) {
dmg *= 2;
}

if ( isAbilityPresent( fheroes2::MonsterAbilityType::DOUBLE_DAMAGE_TO_AIR ) && enemy.isWeaknessPresent( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_AIR ) ) {
dmg *= 2;
}

if ( isAbilityPresent( fheroes2::MonsterAbilityType::DOUBLE_DAMAGE_TO_EARTH ) && enemy.isWeaknessPresent( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_EARTH ) ) {
dmg *= 2;
}

int r = GetAttack() - enemy.GetDefense();
Expand Down Expand Up @@ -1359,11 +1349,11 @@ void Battle::Unit::SpellModesAction( const Spell & spell, uint32_t duration, con
}
}

void Battle::Unit::SpellApplyDamage( const Spell & spell, const uint32_t spellPoints, const HeroBase * applyingHero, TargetInfo & target )
void Battle::Unit::SpellApplyDamage( const Spell & spell, const uint32_t spellPower, const HeroBase * applyingHero, TargetInfo & target )
{
assert( spell.isDamage() );

const uint32_t dmg = CalculateSpellDamage( spell, spellPoints, applyingHero, target.damage, false /* ignore defending hero */ );
const uint32_t dmg = CalculateSpellDamage( spell, spellPower, applyingHero, target.damage, false /* ignore defending hero */ );

// apply damage
if ( dmg ) {
Expand All @@ -1372,52 +1362,37 @@ void Battle::Unit::SpellApplyDamage( const Spell & spell, const uint32_t spellPo
}
}

uint32_t Battle::Unit::CalculateSpellDamage( const Spell & spell, uint32_t spellPoints, const HeroBase * applyingHero, const uint32_t targetDamage,
uint32_t Battle::Unit::CalculateSpellDamage( const Spell & spell, uint32_t spellPower, const HeroBase * applyingHero, const uint32_t targetDamage,
const bool ignoreDefendingHero ) const
{
assert( spell.isDamage() );

// TODO: use fheroes2::getSpellDamage function to remove code duplication.
uint32_t dmg = spell.Damage() * spellPoints;
uint32_t dmg = spell.Damage() * spellPower;

switch ( GetID() ) {
case Monster::IRON_GOLEM:
case Monster::STEEL_GOLEM:
switch ( spell.GetID() ) {
// 50% damage
case Spell::COLDRAY:
case Spell::COLDRING:
case Spell::FIREBALL:
case Spell::FIREBLAST:
case Spell::LIGHTNINGBOLT:
case Spell::CHAINLIGHTNING:
case Spell::ELEMENTALSTORM:
case Spell::ARMAGEDDON:
if ( spell.isElementalSpell() || spell.GetID() == Spell::ARMAGEDDON ) {
dmg /= 2;
break;
default:
break;
}

break;

case Monster::WATER_ELEMENT:
switch ( spell.GetID() ) {
if ( spell.isFire() ) {
// 200% damage
case Spell::FIREBALL:
case Spell::FIREBLAST:
dmg *= 2;
break;
default:
break;
}

break;

case Monster::AIR_ELEMENT:
switch ( spell.GetID() ) {
// 200% damage
case Spell::CHAINLIGHTNING:
case Spell::ELEMENTALSTORM:
case Spell::LIGHTNINGBOLT:
case Spell::CHAINLIGHTNING:
dmg *= 2;
break;
default:
Expand All @@ -1426,15 +1401,11 @@ uint32_t Battle::Unit::CalculateSpellDamage( const Spell & spell, uint32_t spell
break;

case Monster::FIRE_ELEMENT:
switch ( spell.GetID() ) {
if ( spell.isCold() ) {
// 200% damage
case Spell::COLDRAY:
case Spell::COLDRING:
dmg *= 2;
break;
default:
break;
}

break;

default:
Expand Down
4 changes: 2 additions & 2 deletions src/fheroes2/battle/battle_troop.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ namespace Battle
// Sets whether a unit performs a retaliatory attack while being blinded (i.e. with reduced efficiency)
void SetBlindRetaliation( bool value );

uint32_t CalculateSpellDamage( const Spell & spell, uint32_t spellPoints, const HeroBase * applyingHero, const uint32_t targetDamage,
uint32_t CalculateSpellDamage( const Spell & spell, uint32_t spellPower, const HeroBase * applyingHero, const uint32_t targetDamage,
const bool ignoreDefendingHero ) const;

bool SwitchAnimation( int rule, bool reverse = false );
Expand Down Expand Up @@ -305,7 +305,7 @@ namespace Battle
uint32_t Resurrect( const uint32_t points, const bool allow_overflow, const bool skip_dead );

// Applies a damage-causing spell to this unit
void SpellApplyDamage( const Spell & spell, const uint32_t spellPoints, const HeroBase * applyingHero, TargetInfo & target );
void SpellApplyDamage( const Spell & spell, const uint32_t spellPower, const HeroBase * applyingHero, TargetInfo & target );
// Applies a restoring or reviving spell to this unit
void SpellRestoreAction( const Spell & spell, const uint32_t spellPoints, const HeroBase * applyingHero );
// Applies a spell to this unit that changes its parameters
Expand Down
7 changes: 5 additions & 2 deletions src/fheroes2/monster/monster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,12 @@ uint32_t Monster::GetRNDSize() const

bool Monster::isAbilityPresent( const fheroes2::MonsterAbilityType abilityType ) const
{
const std::vector<fheroes2::MonsterAbility> & abilities = fheroes2::getMonsterData( id ).battleStats.abilities;
return fheroes2::isAbilityPresent( fheroes2::getMonsterData( id ).battleStats.abilities, abilityType );
}

return std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbility( abilityType ) ) != abilities.end();
bool Monster::isWeaknessPresent( const fheroes2::MonsterWeaknessType weaknessType ) const
{
return fheroes2::isWeaknessPresent( fheroes2::getMonsterData( id ).battleStats.weaknesses, weaknessType );
}

Monster Monster::GetDowngrade() const
Expand Down
2 changes: 2 additions & 0 deletions src/fheroes2/monster/monster.h
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ class Monster

bool isAbilityPresent( const fheroes2::MonsterAbilityType abilityType ) const;

bool isWeaknessPresent( const fheroes2::MonsterWeaknessType weaknessType ) const;

double GetMonsterStrength( int attack = -1, int defense = -1 ) const;

int ICNMonh() const;
Expand Down
72 changes: 44 additions & 28 deletions src/fheroes2/monster/monster_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,68 +44,63 @@ namespace
{
std::vector<fheroes2::MonsterData> monsterData;

bool isAbilityPresent( const std::vector<fheroes2::MonsterAbility> & abilities, const fheroes2::MonsterAbilityType abilityType )
{
return std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbility( abilityType ) ) != abilities.end();
}

double getMonsterBaseStrength( const fheroes2::MonsterData & data )
{
const fheroes2::MonsterBattleStats & battleStats = data.battleStats;
const std::vector<fheroes2::MonsterAbility> & abilities = battleStats.abilities;

const double effectiveHP = battleStats.hp * ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ) ? 1.4 : 1 );
const double effectiveHP = battleStats.hp * ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ) ? 1.4 : 1 );
const bool isArchers = ( battleStats.shots > 0 );

double damagePotential = ( battleStats.damageMin + battleStats.damageMax ) / 2.0;

if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::DOUBLE_SHOOTING ) ) {
if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::DOUBLE_SHOOTING ) ) {
// How can it be that this ability is assigned not to a shooter?
assert( isArchers );

damagePotential *= 2;
}
else if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::DOUBLE_MELEE_ATTACK ) ) {
else if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::DOUBLE_MELEE_ATTACK ) ) {
// Melee attacker will lose potential on second attack after retaliation
damagePotential *= isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ) ? 2 : 1.75;
damagePotential *= fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ) ? 2 : 1.75;
}

if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::DOUBLE_DAMAGE_TO_UNDEAD ) ) {
if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::DOUBLE_DAMAGE_TO_UNDEAD ) ) {
damagePotential *= 1.15; // 15% of all Monsters are Undead, deals double damage.
}

if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK ) ) {
if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK ) ) {
damagePotential *= 1.2;
}

if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ALWAYS_RETALIATE ) ) {
if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ALWAYS_RETALIATE ) ) {
damagePotential *= 1.25;
}

if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ALL_ADJACENT_CELL_MELEE_ATTACK )
|| isAbilityPresent( abilities, fheroes2::MonsterAbilityType::AREA_SHOT ) ) {
if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ALL_ADJACENT_CELL_MELEE_ATTACK )
|| fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::AREA_SHOT ) ) {
damagePotential *= 1.3;
}

double monsterSpecial = 1.0;

if ( isArchers ) {
monsterSpecial += isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_MELEE_PENALTY ) ? 0.5 : 0.4;
monsterSpecial += fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_MELEE_PENALTY ) ? 0.5 : 0.4;
}

if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::FLYING ) ) {
if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::FLYING ) ) {
monsterSpecial += 0.3;
}

if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ENEMY_HALVING ) ) {
if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ENEMY_HALVING ) ) {
monsterSpecial += 1;
}

if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::SOUL_EATER ) ) {
if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::SOUL_EATER ) ) {
monsterSpecial += 2;
}

if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::HP_DRAIN ) ) {
if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::HP_DRAIN ) ) {
monsterSpecial += 0.3;
}

Expand Down Expand Up @@ -537,27 +532,31 @@ namespace

monsterData[Monster::AIR_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL );
monsterData[Monster::AIR_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::METEORSHOWER );
monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100,
Spell::LIGHTNINGBOLT );
monsterData[Monster::AIR_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_DAMAGE_TO_EARTH );
monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100,
Spell::CHAINLIGHTNING );
monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100,
Spell::ELEMENTALSTORM );
monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100,
Spell::LIGHTNINGBOLT );
monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_EARTH );

monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL );
monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::LIGHTNINGBOLT );
monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::CHAINLIGHTNING );
monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::ELEMENTALSTORM );
monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::LIGHTNINGBOLT );
monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_DAMAGE_TO_AIR );
monsterData[Monster::EARTH_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100,
Spell::METEORSHOWER );
monsterData[Monster::EARTH_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_AIR );

monsterData[Monster::FIRE_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL );
monsterData[Monster::FIRE_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FIRE_SPELL_IMMUNITY );
monsterData[Monster::FIRE_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_COLD_SPELL );
monsterData[Monster::FIRE_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_COLD );

monsterData[Monster::WATER_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL );
monsterData[Monster::WATER_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::COLD_SPELL_IMMUNITY );
monsterData[Monster::WATER_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_FIRE_SPELL );
monsterData[Monster::WATER_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_FIRE );

// Calculate base value of monster strength.
for ( fheroes2::MonsterData & data : monsterData ) {
Expand Down Expand Up @@ -692,6 +691,10 @@ namespace fheroes2
return _( "Soul Eater" );
case MonsterAbilityType::ELEMENTAL:
return ignoreBasicAbility ? _( "No Morale" ) : _( "Elemental" );
case MonsterAbilityType::DOUBLE_DAMAGE_TO_AIR:
return ignoreBasicAbility ? "" : _( "Double damage to Air" );
case MonsterAbilityType::DOUBLE_DAMAGE_TO_EARTH:
return ignoreBasicAbility ? "" : _( "Double damage to Earth" );
default:
break;
}
Expand All @@ -705,15 +708,19 @@ namespace fheroes2
switch ( weakness.type ) {
case MonsterWeaknessType::NONE:
return ignoreBasicAbility ? "" : _( "None" );
case MonsterWeaknessType::EXTRA_DAMAGE_FROM_FIRE_SPELL:
case MonsterWeaknessType::EXTRA_DAMAGE_FROM_FIRE:
return _( "200% damage from Fire spells" );
case MonsterWeaknessType::EXTRA_DAMAGE_FROM_COLD_SPELL:
case MonsterWeaknessType::EXTRA_DAMAGE_FROM_COLD:
return _( "200% damage from Cold spells" );
case MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL: {
std::string str = _( "% damage from %{spell} spell" );
StringReplace( str, "%{spell}", Spell( weakness.value ).GetName() );
return std::to_string( weakness.percentage + 100 ) + str;
}
case MonsterWeaknessType::EXTRA_DAMAGE_FROM_AIR:
return ignoreBasicAbility ? "" : _( "200% damage from Air spells" );
case MonsterWeaknessType::EXTRA_DAMAGE_FROM_EARTH:
return ignoreBasicAbility ? "" : _( "200% damage from Earth spells" );
default:
break;
}
Expand Down Expand Up @@ -917,8 +924,7 @@ namespace fheroes2
}
}

if ( spell == Spell::COLDRAY || spell == Spell::COLDRING || spell == Spell::FIREBALL || spell == Spell::FIREBLAST || spell == Spell::LIGHTNINGBOLT
|| spell == Spell::CHAINLIGHTNING || spell == Spell::ELEMENTALSTORM ) {
if ( spell.isElementalSpell() ) {
foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::ELEMENTAL_SPELL_IMMUNITY ) );
if ( foundAbility != abilities.end() ) {
return 100;
Expand Down Expand Up @@ -947,4 +953,14 @@ namespace fheroes2

return spellResistance;
}

bool isAbilityPresent( const std::vector<MonsterAbility> & abilities, const MonsterAbilityType abilityType )
{
return std::find( abilities.begin(), abilities.end(), MonsterAbility( abilityType ) ) != abilities.end();
}

bool isWeaknessPresent( const std::vector<MonsterWeakness> & weaknesses, const MonsterWeaknessType weaknessType )
{
return std::find( weaknesses.begin(), weaknesses.end(), MonsterWeakness( weaknessType ) ) != weaknesses.end();
}
}
Loading
Loading