diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 65669ced877..ad0dc45c4b2 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -237,6 +237,7 @@ class SaveHelper { struct MonsterConversionData { int8_t monsterLevel; uint16_t experience; + uint8_t toHit; uint8_t toHitSpecial; }; @@ -678,16 +679,18 @@ void LoadMonster(LoadHelper *file, Monster &monster, MonsterConversionData *mons else file->Skip(2); // Skip exp - now calculated from monstdat when the monster dies - if (monster.isPlayerMinion()) // Don't skip for golems - monster.toHit = file->NextLE(); + if (monsterConversionData != nullptr) + monsterConversionData->toHit = file->NextLE(); + else if (monster.isPlayerMinion()) // Don't skip for golems + monster.golemToHit = file->NextLE(); else - file->Skip(1); // Skip hit as it's already initialized + file->Skip(1); // Skip toHit - now calculated on the fly monster.minDamage = file->NextLE(); monster.maxDamage = file->NextLE(); if (monsterConversionData != nullptr) monsterConversionData->toHitSpecial = file->NextLE(); else - file->Skip(1); // Skip toHitSpecial as it's already initialized + file->Skip(1); // Skip toHitSpecial - now calculated on the fly monster.minDamageSpecial = file->NextLE(); monster.maxDamageSpecial = file->NextLE(); monster.armorClass = file->NextLE(); @@ -1472,7 +1475,10 @@ void SaveMonster(SaveHelper *file, Monster &monster, MonsterConversionData *mons else file->WriteLE(static_cast(std::min(std::numeric_limits::max(), monster.exp(sgGameInitInfo.nDifficulty)))); - file->WriteLE(static_cast(std::min(monster.toHit, std::numeric_limits::max()))); // For backwards compatibility + if (monsterConversionData != nullptr) + file->WriteLE(monsterConversionData->toHit); + else + file->WriteLE(static_cast(std::min(monster.toHit(sgGameInitInfo.nDifficulty), std::numeric_limits::max()))); // For backwards compatibility file->WriteLE(monster.minDamage); file->WriteLE(monster.maxDamage); if (monsterConversionData != nullptr) diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 5fefafc3d6c..cd18fcebd93 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -1003,7 +1003,7 @@ bool PlayerMHit(Player &player, Monster *monster, int dist, int mind, int maxd, if (missileData.isArrow()) { int tac = player.GetArmor(); if (monster != nullptr) { - hper = monster->toHit + hper = monster->toHit(sgGameInitInfo.nDifficulty) + ((monster->level(sgGameInitInfo.nDifficulty) - player.getCharacterLevel()) * 2) + 30 - (dist * 2) - tac; diff --git a/Source/monster.cpp b/Source/monster.cpp index b09331f25b5..34b734394f6 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -178,7 +178,6 @@ void InitMonster(Monster &monster, Direction rd, size_t typeIndex, Point positio monster.rndItemSeed = AdvanceRndSeed(); monster.aiSeed = AdvanceRndSeed(); monster.whoHit = 0; - monster.toHit = monster.data().toHit; monster.minDamage = monster.data().minDamage; monster.maxDamage = monster.data().maxDamage; monster.minDamageSpecial = monster.data().minDamageSpecial; @@ -204,7 +203,6 @@ void InitMonster(Monster &monster, Direction rd, size_t typeIndex, Point positio else monster.maxHitPoints += 100 << 6; monster.hitPoints = monster.maxHitPoints; - monster.toHit += NightmareToHitBonus; monster.minDamage = 2 * (monster.minDamage + 2); monster.maxDamage = 2 * (monster.maxDamage + 2); monster.minDamageSpecial = 2 * (monster.minDamageSpecial + 2); @@ -217,7 +215,6 @@ void InitMonster(Monster &monster, Direction rd, size_t typeIndex, Point positio else monster.maxHitPoints += 200 << 6; monster.hitPoints = monster.maxHitPoints; - monster.toHit += HellToHitBonus; monster.minDamage = 4 * monster.minDamage + 6; monster.maxDamage = 4 * monster.maxDamage + 6; monster.minDamageSpecial = 4 * monster.minDamageSpecial + 6; @@ -1221,17 +1218,17 @@ void MonsterAttackEnemy(Monster &monster, int hit, int minDam, int maxDam) bool MonsterAttack(Monster &monster) { if (monster.animInfo.currentFrame == monster.data().animFrameNum - 1) { - MonsterAttackEnemy(monster, monster.toHit, monster.minDamage, monster.maxDamage); + MonsterAttackEnemy(monster, monster.toHit(sgGameInitInfo.nDifficulty), monster.minDamage, monster.maxDamage); if (monster.ai != MonsterAIID::Snake) PlayEffect(monster, MonsterSound::Attack); } if (IsAnyOf(monster.type().type, MT_NMAGMA, MT_YMAGMA, MT_BMAGMA, MT_WMAGMA) && monster.animInfo.currentFrame == 8) { - MonsterAttackEnemy(monster, monster.toHit + 10, monster.minDamage - 2, monster.maxDamage - 2); + MonsterAttackEnemy(monster, monster.toHit(sgGameInitInfo.nDifficulty) + 10, monster.minDamage - 2, monster.maxDamage - 2); PlayEffect(monster, MonsterSound::Attack); } if (IsAnyOf(monster.type().type, MT_STORM, MT_RSTORM, MT_STORML, MT_MAEL) && monster.animInfo.currentFrame == 12) { - MonsterAttackEnemy(monster, monster.toHit - 20, monster.minDamage + 4, monster.maxDamage + 4); + MonsterAttackEnemy(monster, monster.toHit(sgGameInitInfo.nDifficulty) - 20, monster.minDamage + 4, monster.maxDamage + 4); PlayEffect(monster, MonsterSound::Attack); } @@ -3229,15 +3226,6 @@ tl::expected PrepareUniqueMonst(Monster &monster, UniqueMonst RETURN_IF_ERROR(InitTRNForUniqueMonster(monster)); monster.uniqTrans = uniquetrans++; - if (uniqueMonsterData.customToHit != 0) { - monster.toHit = uniqueMonsterData.customToHit; - - if (sgGameInitInfo.nDifficulty == DIFF_NIGHTMARE) { - monster.toHit += NightmareToHitBonus; - } else if (sgGameInitInfo.nDifficulty == DIFF_HELL) { - monster.toHit += HellToHitBonus; - } - } if (uniqueMonsterData.customArmorClass != 0) { monster.armorClass = uniqueMonsterData.customArmorClass; @@ -4656,7 +4644,7 @@ void SpawnGolem(Player &player, Monster &golem, Point position, Missile &missile golem.maxHitPoints = 2 * (320 * missile._mispllvl + player._pMaxMana / 3); golem.hitPoints = golem.maxHitPoints; golem.armorClass = 25; - golem.toHit = 5 * (missile._mispllvl + 8) + 2 * player.getCharacterLevel(); + golem.golemToHit = 5 * (missile._mispllvl + 8) + 2 * player.getCharacterLevel(); golem.minDamage = 2 * (missile._mispllvl + 4); golem.maxDamage = 2 * (missile._mispllvl + 8); golem.flags |= MFLAG_GOLEM; @@ -4827,6 +4815,25 @@ MonsterMode Monster::getVisualMonsterMode() const return MonsterMode::Petrified; } +unsigned int Monster::toHit(_difficulty difficulty) const +{ + if (isPlayerMinion()) + return golemToHit; + + unsigned int baseToHit = data().toHit; + if (isUnique() && UniqueMonstersData[static_cast(uniqueType)].customToHit != 0) { + baseToHit = UniqueMonstersData[static_cast(uniqueType)].customToHit; + } + + if (difficulty == DIFF_NIGHTMARE) { + baseToHit += NightmareToHitBonus; + } else if (difficulty == DIFF_HELL) { + baseToHit += HellToHitBonus; + } + + return baseToHit; +} + unsigned int Monster::toHitSpecial(_difficulty difficulty) const { unsigned int baseToHitSpecial = data().toHitSpecial; diff --git a/Source/monster.h b/Source/monster.h index 1e8efb267e6..b77f52cbf14 100644 --- a/Source/monster.h +++ b/Source/monster.h @@ -222,7 +222,7 @@ struct Monster { // note: missing field _mAFNum uint32_t rndItemSeed; /** Seed used to determine AI behaviour/sync sounds in multiplayer games? */ uint32_t aiSeed; - uint16_t toHit; + uint16_t golemToHit; uint16_t resistance; _speech_id talkMsg; @@ -364,6 +364,14 @@ struct Monster { // note: missing field _mAFNum return monsterExp; } + /** + * @brief Calculates monster's chance to hit with normal attack. + * Fetches base value from @p MonstersData array or @p UniqueMonstersData. + * @param difficulty - difficulty on which calculation is performed + * @return Monster's chance to hit with normal attack, including bonuses from difficulty and monster being unique + */ + unsigned int toHit(_difficulty difficulty) const; + /** * @brief Calculates monster's chance to hit with special attack. * Fetches base value from @p MonstersData array or @p UniqueMonstersData.