diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 9182395ee..6d7216ee5 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -234,6 +234,7 @@ public: struct MonsterConversionData { int8_t monsterLevel; uint16_t experience; + uint8_t toHit; uint8_t toHitSpecial; }; @@ -661,16 +662,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(); @@ -1477,7 +1480,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 a5dfe4a95..9fc0700c4 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -1007,7 +1007,7 @@ bool PlayerMHit(int pnum, Monster *monster, int dist, int mind, int maxd, Missil if (missileData.isArrow()) { int tac = player.GetArmor(); if (monster != nullptr) { - hper = monster->toHit + hper = monster->toHit(sgGameInitInfo.nDifficulty) + ((monster->level(sgGameInitInfo.nDifficulty) - player._pLevel) * 2) + 30 - (dist * 2) - tac; diff --git a/Source/monster.cpp b/Source/monster.cpp index 4080b4dff..cb978bca6 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -156,7 +156,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; @@ -182,7 +181,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); @@ -195,7 +193,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; @@ -1253,17 +1250,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); } @@ -3181,15 +3178,6 @@ void PrepareUniqueMonst(Monster &monster, UniqueMonsterType monsterType, size_t 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; @@ -4571,7 +4559,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._pLevel; + golem.golemToHit = 5 * (missile._mispllvl + 8) + 2 * player._pLevel; golem.minDamage = 2 * (missile._mispllvl + 4); golem.maxDamage = 2 * (missile._mispllvl + 8); golem.flags |= MFLAG_GOLEM; @@ -4742,6 +4730,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 a8dba5a46..cfba561a1 100644 --- a/Source/monster.h +++ b/Source/monster.h @@ -210,7 +210,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; @@ -352,6 +352,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.