diff --git a/Source/misdat.cpp b/Source/misdat.cpp index d006779713e..4b155fd7144 100644 --- a/Source/misdat.cpp +++ b/Source/misdat.cpp @@ -28,6 +28,192 @@ namespace devilution { namespace { +constexpr auto Physical = MissileDataFlags::Physical; +constexpr auto Fire = MissileDataFlags::Fire; +constexpr auto Lightning = MissileDataFlags::Lightning; +constexpr auto Magic = MissileDataFlags::Magic; +constexpr auto Acid = MissileDataFlags::Acid; +constexpr auto Arrow = MissileDataFlags::Arrow; +constexpr auto Invisible = MissileDataFlags::Invisible; +} // namespace + +/** Data related to each missile ID. */ +const MissileData MissilesData[] = { + // clang-format off +// id mAddProc, mProc, mlSFX, miSFX, mFileNum, flags, MovementDistribution; +/*Arrow*/ { &AddArrow, &ProcessArrow, SFX_NONE, SFX_NONE, MissileGraphicID::Arrow, Physical | Arrow, MissileMovementDistribution::Blockable }, +/*Firebolt*/ { &AddFirebolt, &ProcessGenericProjectile, LS_FBOLT1, LS_FIRIMP2, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Blockable }, +/*Guardian*/ { &AddGuardian, &ProcessGuardian, LS_GUARD, LS_GUARDLAN, MissileGraphicID::Guardian, Physical, MissileMovementDistribution::Disabled }, +/*Phasing*/ { &AddPhasing, &ProcessTeleport, LS_TELEPORT, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*NovaBall*/ { &AddNovaBall, &ProcessNovaBall, SFX_NONE, SFX_NONE, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Unblockable }, +/*FireWall*/ { &AddFireWall, &ProcessFireWall, LS_WALLLOOP, LS_FIRIMP2, MissileGraphicID::FireWall, Fire, MissileMovementDistribution::Disabled }, +/*Fireball*/ { &AddFireball, &ProcessFireball, LS_FBOLT1, LS_FIRIMP2, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Blockable }, +/*LightningControl*/ { &AddLightningControl, &ProcessLightningControl, SFX_NONE, SFX_NONE, MissileGraphicID::Lightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, +/*Lightning*/ { &AddLightning, &ProcessLightning, LS_LNING1, LS_ELECIMP1, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, +/*MagmaBallExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, SFX_NONE, SFX_NONE, MissileGraphicID::MagmaBallExplosion, Physical, MissileMovementDistribution::Disabled }, +/*TownPortal*/ { &AddTownPortal, &ProcessTownPortal, LS_SENTINEL, LS_ELEMENTL, MissileGraphicID::TownPortal, Magic, MissileMovementDistribution::Disabled }, +/*FlashBottom*/ { &AddFlashBottom, &ProcessFlashBottom, LS_NOVA, LS_ELECIMP1, MissileGraphicID::FlashBottom, Magic, MissileMovementDistribution::Disabled }, +/*FlashTop*/ { &AddFlashTop, &ProcessFlashTop, SFX_NONE, SFX_NONE, MissileGraphicID::FlashTop, Magic, MissileMovementDistribution::Disabled }, +/*ManaShield*/ { &AddManaShield, nullptr, LS_MSHIELD, SFX_NONE, MissileGraphicID::ManaShield, Magic | Invisible, MissileMovementDistribution::Disabled }, +/*FlameWave*/ { &AddFlameWave, &ProcessFlameWave, SFX_NONE, SFX_NONE, MissileGraphicID::FireWall, Fire, MissileMovementDistribution::Unblockable }, +/*ChainLightning*/ { &AddChainLightning, &ProcessChainLightning, LS_LNING1, LS_ELECIMP1, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, +/*ChainBall*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, +/*BloodHit*/ { nullptr, nullptr, LS_BLODSTAR, LS_BLSIMPT, MissileGraphicID::BloodHit, Physical, MissileMovementDistribution::Disabled }, +/*BoneHit*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::BoneHit, Physical, MissileMovementDistribution::Disabled }, +/*MetalHit*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::MetalHit, Physical, MissileMovementDistribution::Disabled }, +/*Rhino*/ { &AddRhino, &ProcessRhino, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical, MissileMovementDistribution::Blockable }, +/*MagmaBall*/ { &AddMagmaBall, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::MagmaBall, Fire, MissileMovementDistribution::Blockable }, +/*ThinLightningControl*/ { &AddLightningControl, &ProcessLightningControl, SFX_NONE, SFX_NONE, MissileGraphicID::ThinLightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, +/*ThinLightning*/ { &AddLightning, &ProcessLightning, SFX_NONE, SFX_NONE, MissileGraphicID::ThinLightning, Lightning, MissileMovementDistribution::Disabled }, +/*BloodStar*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::BloodStar, Magic, MissileMovementDistribution::Blockable }, +/*BloodStarExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, SFX_NONE, SFX_NONE, MissileGraphicID::BloodStarExplosion, Magic, MissileMovementDistribution::Disabled }, +/*Teleport*/ { &AddTeleport, &ProcessTeleport, LS_ELEMENTL, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*FireArrow*/ { &AddElementalArrow, &ProcessElementalArrow, SFX_NONE, SFX_NONE, MissileGraphicID::FireArrow, Fire | Arrow, MissileMovementDistribution::Blockable }, +/*DoomSerpents*/ { nullptr, nullptr, LS_DSERP, SFX_NONE, MissileGraphicID::DoomSerpents, Magic | Invisible, MissileMovementDistribution::Disabled }, +/*FireOnly*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::FireWall, Fire, MissileMovementDistribution::Disabled }, +/*StoneCurse*/ { &AddStoneCurse, &ProcessStoneCurse, LS_SCURIMP, SFX_NONE, MissileGraphicID::None, Magic | Invisible, MissileMovementDistribution::Disabled }, +/*BloodRitual*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical, MissileMovementDistribution::Disabled }, +/*Invisibility*/ { nullptr, nullptr, LS_INVISIBL, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Golem*/ { &AddGolem, nullptr, LS_GOLUM, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Etherealize*/ { nullptr, nullptr, LS_ETHEREAL, SFX_NONE, MissileGraphicID::Etherealize, Physical, MissileMovementDistribution::Disabled }, +/*Spurt*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::Spurt, Physical, MissileMovementDistribution::Disabled }, +/*ApocalypseBoom*/ { &AddApocalypseBoom, &ProcessApocalypseBoom, SFX_NONE, SFX_NONE, MissileGraphicID::ApocalypseBoom, Physical, MissileMovementDistribution::Disabled }, +/*Healing*/ { &AddHealing, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*FireWallControl*/ { &AddFireWallControl, &ProcessFireWallControl, SFX_NONE, SFX_NONE, MissileGraphicID::FireWall, Fire | Invisible, MissileMovementDistribution::Disabled }, +/*Infravision*/ { &AddInfravision, &ProcessInfravision, LS_INFRAVIS, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Identify*/ { &AddIdentify, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*FlameWaveControl*/ { &AddFlameWaveControl, &ProcessFlameWaveControl, LS_FLAMWAVE, SFX_NONE, MissileGraphicID::FireWall, Fire, MissileMovementDistribution::Disabled }, +/*Nova*/ { &AddNova, &ProcessNova, LS_NOVA, SFX_NONE, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, +/*Rage*/ { &AddRage, &ProcessRage, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Apocalypse*/ { &AddApocalypse, &ProcessApocalypse, LS_APOC, SFX_NONE, MissileGraphicID::ApocalypseBoom, Magic, MissileMovementDistribution::Disabled }, +/*ItemRepair*/ { &AddItemRepair, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*StaffRecharge*/ { &AddStaffRecharge, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*TrapDisarm*/ { &AddTrapDisarm, nullptr, LS_TRAPDIS, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Inferno*/ { &AddInferno, &ProcessInferno, LS_SPOUTSTR, SFX_NONE, MissileGraphicID::Inferno, Fire, MissileMovementDistribution::Disabled }, +/*InfernoControl*/ { &AddInfernoControl, &ProcessInfernoControl, SFX_NONE, SFX_NONE, MissileGraphicID::None, Fire | Invisible, MissileMovementDistribution::Disabled }, +/*FireMan*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical, MissileMovementDistribution::Blockable }, +/*Krull*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::Krull, Fire | Arrow, MissileMovementDistribution::Blockable }, +/*ChargedBolt*/ { &AddChargedBolt, &ProcessChargedBolt, LS_CBOLT, SFX_NONE, MissileGraphicID::ChargedBolt, Lightning, MissileMovementDistribution::Blockable }, +/*HolyBolt*/ { &AddHolyBolt, &ProcessHolyBolt, LS_HOLYBOLT, LS_ELECIMP1, MissileGraphicID::HolyBolt, Physical, MissileMovementDistribution::Blockable }, +/*Resurrect*/ { &AddResurrect, nullptr, SFX_NONE, LS_RESUR, MissileGraphicID::None, Magic | Invisible, MissileMovementDistribution::Disabled }, +/*Telekinesis*/ { &AddTelekinesis, nullptr, LS_ETHEREAL, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*LightningArrow*/ { &AddElementalArrow, &ProcessElementalArrow, SFX_NONE, SFX_NONE, MissileGraphicID::LightningArrow, Lightning | Arrow, MissileMovementDistribution::Blockable }, +/*Acid*/ { &AddAcid, &ProcessGenericProjectile, LS_ACID, SFX_NONE, MissileGraphicID::Acid, Acid, MissileMovementDistribution::Blockable }, +/*AcidSplat*/ { &AddMissileExplosion, &ProcessAcidSplate, SFX_NONE, SFX_NONE, MissileGraphicID::AcidSplat, Acid, MissileMovementDistribution::Disabled }, +/*AcidPuddle*/ { &AddAcidPuddle, &ProcessAcidPuddle, LS_PUDDLE, SFX_NONE, MissileGraphicID::AcidPuddle, Acid, MissileMovementDistribution::Disabled }, +/*HealOther*/ { &AddHealOther, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Elemental*/ { &AddElemental, &ProcessElemental, LS_ELEMENTL, SFX_NONE, MissileGraphicID::Elemental, Fire, MissileMovementDistribution::Unblockable }, +/*ResurrectBeam*/ { &AddResurrectBeam, &ProcessResurrectBeam, SFX_NONE, SFX_NONE, MissileGraphicID::Resurrect, Physical, MissileMovementDistribution::Disabled }, +/*BoneSpirit*/ { &AddBoneSpirit, &ProcessBoneSpirit, LS_BONESP, LS_BSIMPCT, MissileGraphicID::BoneSpirit, Magic, MissileMovementDistribution::Blockable }, +/*WeaponExplosion*/ { &AddWeaponExplosion, &ProcessWeaponExplosion, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical, MissileMovementDistribution::Disabled }, +/*RedPortal*/ { &AddRedPortal, &ProcessRedPortal, LS_SENTINEL, LS_ELEMENTL, MissileGraphicID::RedPortal, Physical, MissileMovementDistribution::Disabled }, +/*DiabloApocalypseBoom*/ { &AddApocalypseBoom, &ProcessApocalypseBoom, SFX_NONE, SFX_NONE, MissileGraphicID::DiabloApocalypseBoom, Physical, MissileMovementDistribution::Disabled }, +/*DiabloApocalypse*/ { &AddDiabloApocalypse, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Mana*/ { &AddMana, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Magi*/ { &AddMagi, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*LightningWall*/ { &AddLightningWall, &ProcessLightningWall, LS_LMAG, LS_ELECIMP1, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, +/*LightningWallControl*/ { &AddFireWallControl, &ProcessLightningWallControl, SFX_NONE, SFX_NONE, MissileGraphicID::Lightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, +/*Immolation*/ { &AddNova, &ProcessImmolation, LS_FBOLT1, LS_FIRIMP2, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Disabled }, +/*ItemMissile*/ { &AddItemMissile, &ProcessItemMissile, SFX_NONE, SFX_NONE, MissileGraphicID::Arrow, Physical | Arrow, MissileMovementDistribution::Disabled }, +/*ItemFireball*/ { &AddImmolation, &ProcessFireball, IS_FBALLBOW, LS_FIRIMP2, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Blockable }, +/*ItemLightning*/ { &AddItemLightning, &ProcessItemLightning, IS_FBALLBOW, SFX_NONE, MissileGraphicID::Lightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, +/*ItemChargedBolt*/ { &AddItemChargedBolt, &ProcessChargedBolt, LS_CBOLT, SFX_NONE, MissileGraphicID::ChargedBolt, Lightning, MissileMovementDistribution::Blockable }, +/*ItemHolyBolt*/ { &AddHolyBolt, &ProcessHolyBolt, LS_HOLYBOLT, LS_ELECIMP1, MissileGraphicID::HolyBolt, Physical, MissileMovementDistribution::Blockable }, +/*Warp*/ { &AddWarp, &ProcessTeleport, LS_ETHEREAL, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Reflect*/ { &AddReflect, nullptr, LS_MSHIELD, SFX_NONE, MissileGraphicID::Reflect, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Berserk*/ { &AddBerserk, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*RingOfFire*/ { &AddRingOfFire, &ProcessRingOfFire, SFX_NONE, SFX_NONE, MissileGraphicID::FireWall, Fire | Invisible, MissileMovementDistribution::Disabled }, +/*StealPotions*/ { &AddStealPotions, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*StealMana*/ { &AddStealMana, nullptr, IS_CAST7, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*RingOfLightning*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::Lightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, +/*Search*/ { &AddSearch, &ProcessSearch, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Aura*/ { nullptr, nullptr, SFX_NONE, LS_ELECIMP1, MissileGraphicID::FlashBottom, Magic | Invisible, MissileMovementDistribution::Disabled }, +/*Aura2*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::FlashTop, Magic | Invisible, MissileMovementDistribution::Disabled }, +/*SpiralFireball*/ { nullptr, nullptr, LS_FBOLT1, LS_FIRIMP2, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Disabled }, +/*RuneOfFire*/ { &AddRuneOfFire, &ProcessRune, SFX_NONE, SFX_NONE, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, +/*RuneOfLight*/ { &AddRuneOfLight, &ProcessRune, SFX_NONE, SFX_NONE, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, +/*RuneOfNova*/ { &AddRuneOfNova, &ProcessRune, SFX_NONE, SFX_NONE, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, +/*RuneOfImmolation*/ { &AddRuneOfImmolation, &ProcessRune, SFX_NONE, SFX_NONE, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, +/*RuneOfStone*/ { &AddRuneOfStone, &ProcessRune, SFX_NONE, SFX_NONE, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, +/*BigExplosion*/ { &AddBigExplosion, &ProcessBigExplosion, LS_NESTXPLD, LS_NESTXPLD, MissileGraphicID::BigExplosion, Fire, MissileMovementDistribution::Disabled }, +/*HorkSpawn*/ { &AddHorkSpawn, &ProcessHorkSpawn, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Jester*/ { &AddJester, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*OpenNest*/ { &AddOpenNest, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*OrangeFlare*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::OrangeFlare, Magic, MissileMovementDistribution::Blockable }, +/*BlueFlare*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::BlueFlare2, Magic, MissileMovementDistribution::Blockable }, +/*RedFlare*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::RedFlare, Magic, MissileMovementDistribution::Blockable }, +/*YellowFlare*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::YellowFlare, Magic, MissileMovementDistribution::Blockable }, +/*BlueFlare2*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::BlueFlare2, Magic, MissileMovementDistribution::Blockable }, +/*YellowExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, LS_FIRIMP2, SFX_NONE, MissileGraphicID::YellowFlareExplosion, Physical, MissileMovementDistribution::Disabled }, +/*RedExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, LS_FIRIMP2, SFX_NONE, MissileGraphicID::RedFlareExplosion, Physical, MissileMovementDistribution::Disabled }, +/*BlueExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, LS_FIRIMP2, SFX_NONE, MissileGraphicID::BlueFlareExplosion, Physical, MissileMovementDistribution::Disabled }, +/*BlueExplosion2*/ { &AddMissileExplosion, &ProcessMissileExplosion, LS_FIRIMP2, SFX_NONE, MissileGraphicID::BlueFlareExplosion2, Physical, MissileMovementDistribution::Disabled }, +/*OrangeExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, LS_FIRIMP2, SFX_NONE, MissileGraphicID::OrangeFlareExplosion, Physical, MissileMovementDistribution::Disabled }, + // clang-format on +}; + +namespace { + +constexpr std::array Repeat(uint8_t v) // NOLINT(readability-identifier-length) +{ + return { v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v }; +} + +const std::array MissileAnimDelays[] { + {}, + Repeat(1), + Repeat(2), + { 0, 1 }, + { 1 }, +}; + +const std::array MissileAnimLengths[] { + {}, + Repeat(1), + Repeat(4), + Repeat(5), + Repeat(6), + Repeat(7), + Repeat(8), + Repeat(9), + Repeat(10), + Repeat(12), + Repeat(13), + Repeat(14), + Repeat(15), + Repeat(16), + Repeat(17), + Repeat(19), + Repeat(20), + { 9, 4 }, + { 15, 14, 3 }, + { 13, 11 }, + { 16, 16, 16, 16, 16, 16, 16, 16, 8 } +}; + +constexpr uint8_t AnimLen_0 = 0; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_1 = 1; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_4 = 2; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_5 = 3; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_6 = 4; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_7 = 5; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_8 = 6; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_9 = 7; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_10 = 8; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_12 = 9; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_13 = 10; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_14 = 11; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_15 = 12; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_16 = 13; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_17 = 14; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_19 = 15; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_20 = 16; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_9_4 = 17; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_15_14_3 = 18; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_13_11 = 19; // NOLINT(readability-identifier-naming) +constexpr uint8_t AnimLen_16x8_8 = 20; // NOLINT(readability-identifier-naming) + +} // namespace /** Data related to each missile graphic ID. */ std::vector MissileSpriteData; diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 2074a1ed863..50bfac59bbf 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -1395,6 +1395,58 @@ void AddStealMana(Missile &missile, AddMissileParameter & /*parameter*/) missile._miDelFlag = true; } +void AddItemMissile(Missile &missile, AddMissileParameter ¶meter) +{ + if (missile.sourceType() != MissileSource::Player) + return; + + Player &player = *missile.sourcePlayer(); + uint8_t itemMissileID = 0; + uint8_t spllvl = 0; + + for (Item &item : EquippedPlayerItemsRange { player }) { + if (item._iMagical == ITEM_QUALITY_UNIQUE) { + const UniqueItem &uitem = UniqueItems[item._iUid]; + assert(uitem.UINumPL <= sizeof(uitem.powers) / sizeof(*uitem.powers)); + for (const auto &power : uitem.powers) { + switch (power.type) { + case IPL_FIREBALL: // Flambeau + itemMissileID = IPL_FIREBALL; + goto end_loop; + case IPL_ADDACLIFE: // Blitzen + itemMissileID = IPL_ADDACLIFE; + goto end_loop; + case IPL_ADDMANAAC: // Thunderclap + itemMissileID = IPL_ADDMANAAC; + goto end_loop; + default: + app_fatal(StrCat("wrong itemMissileID: ", itemMissileID)); + } + } + } + } + +end_loop: // Grab the first spectralID we get and proceed + + switch (player._pClass) { + case HeroClass::Rogue: + spllvl += (player._pLevel - 1) / 4; + break; + case HeroClass::Warrior: + case HeroClass::Bard: + spllvl += (player._pLevel - 1) / 8; + break; + default: + break; + } + + missile._mirange = 1; + missile.var1 = parameter.dst.x; + missile.var2 = parameter.dst.y; + missile.var3 = spllvl; + missile.var4 = itemMissileID; +} + void AddSpectralArrow(Missile &missile, AddMissileParameter ¶meter) { int av = 0; @@ -1552,7 +1604,7 @@ void AddImmolation(Missile &missile, AddMissileParameter ¶meter) missile._mlid = AddLight(missile.position.start, 8); } -void AddLightningBow(Missile &missile, AddMissileParameter ¶meter) +void AddItemLightning(Missile &missile, AddMissileParameter ¶meter) { WorldTilePosition dst = parameter.dst; if (missile.position.start == parameter.dst) { @@ -1636,7 +1688,7 @@ void AddSearch(Missile &missile, AddMissileParameter & /*parameter*/) } } -void AddChargedBoltBow(Missile &missile, AddMissileParameter ¶meter) +void AddItemChargedBolt(Missile &missile, AddMissileParameter ¶meter) { WorldTilePosition dst = parameter.dst; missile._mirnd = GenerateRnd(15) + 1; @@ -1661,9 +1713,12 @@ void AddElementalArrow(Missile &missile, AddMissileParameter ¶meter) if (missile.position.start == dst) { dst += parameter.midir; } + int av = 32; - if (missile._micaster == TARGET_MONSTERS) { - const Player &player = Players[missile._misource]; + + if (missile.sourceType() == MissileSource::Player) { + const auto &player = *missile.sourcePlayer(); + if (player._pClass == HeroClass::Rogue) av += (player.getCharacterLevel()) / 4; else if (IsAnyOf(player._pClass, HeroClass::Warrior, HeroClass::Bard)) @@ -1682,7 +1737,39 @@ void AddElementalArrow(Missile &missile, AddMissileParameter ¶meter) if (IsAnyOf(player._pClass, HeroClass::Rogue, HeroClass::Warrior, HeroClass::Bard)) av -= 1; } + + missile._midam = player._pIMinDam; // min physical damage + missile.var3 = player._pIMaxDam; // max physical damage + + switch (missile._mitype) { + case MissileID::LightningArrow: + missile.var4 = player._pILMinDam; // min lightning damage + missile.var5 = player._pILMaxDam; // max lightning damage + break; + case MissileID::FireArrow: + missile.var4 = player._pIFMinDam; // min fire damage + missile.var5 = player._pIFMaxDam; // max fire damage + break; + default: + app_fatal(StrCat("wrong missile ID ", static_cast(missile._mitype))); + break; + } + } else if (missile.sourceType() == MissileSource::Trap) { + missile._midam = currlevel + GenerateRnd(10) + 1; // min physical damage + missile.var3 = (currlevel * 2) + GenerateRnd(10) + 1; // max physical damage + + switch (missile._mitype) { + case MissileID::LightningArrow: + case MissileID::FireArrow: + missile.var4 = currlevel + GenerateRnd(10) + 1; // min elemental damage + missile.var5 = (currlevel * 2) + GenerateRnd(10) + 1; // max elemental damage + break; + default: + app_fatal(StrCat("wrong missile ID ", static_cast(missile._mitype))); + break; + } } + UpdateMissileVelocity(missile, dst, av); SetMissDir(missile, GetDirection16(missile.position.start, dst)); @@ -1724,6 +1811,23 @@ void AddArrow(Missile &missile, AddMissileParameter ¶meter) UpdateMissileVelocity(missile, dst, av); missile._miAnimFrame = static_cast(GetDirection16(missile.position.start, dst)) + 1; missile._mirange = 256; + + switch (missile.sourceType()) { + case MissileSource::Player: { + const Player &player = *missile.sourcePlayer(); + missile._midam = player._pIMinDam; // min damage + missile.var1 = player._pIMaxDam; // max damage + } break; + case MissileSource::Monster: { + const Monster &monster = *missile.sourceMonster(); + missile._midam = monster.minDamage; // min damage + missile.var1 = monster.maxDamage; // max damage + } break; + case MissileSource::Trap: + missile._midam = currlevel; // min damage + missile.var1 = 2 * currlevel; // max damage + break; + } } void UpdateVileMissPos(Missile &missile, Point dst) @@ -1919,6 +2023,20 @@ void AddLightningControl(Missile &missile, AddMissileParameter ¶meter) UpdateMissileVelocity(missile, parameter.dst, 32); missile._miAnimFrame = RandomIntBetween(1, 8); missile._mirange = 256; + + switch (missile.sourceType()) { + case MissileSource::Player: { + const Player &player = *missile.sourcePlayer(); + missile._midam = (GenerateRnd(2) + GenerateRnd(player._pLevel) + 2) << 6; + } break; + case MissileSource::Monster: { + const Monster &monster = *missile.sourceMonster(); + missile._midam = 2 * (monster.minDamage + GenerateRnd(monster.maxDamage - monster.minDamage + 1)); + } break; + case MissileSource::Trap: + missile._midam = (2 * currlevel) + GenerateRnd(currlevel); + break; + } } void AddLightning(Missile &missile, AddMissileParameter ¶meter) @@ -1973,10 +2091,19 @@ void AddMissileExplosion(Missile &missile, AddMissileParameter ¶meter) void AddWeaponExplosion(Missile &missile, AddMissileParameter ¶meter) { missile.var2 = parameter.dst.x; - if (parameter.dst.x == 1) + + const Player &player = *missile.sourcePlayer(); + + if (missile.var2 == 1) { + missile._midam = player._pIFMinDam; // min fire damage + missile.var3 = player._pIFMaxDam; // max fire damage SetMissAnim(missile, MissileGraphicID::MagmaBallExplosion); - else + } else { + missile._midam = player._pILMinDam; // min lightning damage + missile.var3 = player._pILMaxDam; // max lightning damage SetMissAnim(missile, MissileGraphicID::ChargedBolt); + } + missile._mirange = missile._miAnimLen - 1; } @@ -2768,69 +2895,31 @@ void ProcessElementalArrow(Missile &missile) if (missile._miAnimType == MissileGraphicID::ChargedBolt || missile._miAnimType == MissileGraphicID::MagmaBallExplosion) { ChangeLight(missile._mlid, missile.position.tile, missile._miAnimFrame + 5); } else { - int mind; - int maxd; - int p = missile._misource; missile._midist++; - if (!missile.IsTrap()) { - if (missile._micaster == TARGET_MONSTERS) { - // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. - const Player &player = Players[p]; - mind = player._pIMinDam; - maxd = player._pIMaxDam; - } else { - // BUGFIX: damage of missile should be encoded in missile struct; monster can be dead before missile arrives. - Monster &monster = Monsters[p]; - mind = monster.minDamage; - maxd = monster.maxDamage; - } - } else { - mind = GenerateRnd(10) + 1 + currlevel; - maxd = GenerateRnd(10) + 1 + currlevel * 2; - } - MoveMissileAndCheckMissileCol(missile, DamageType::Physical, mind, maxd, true, false); + + MoveMissileAndCheckMissileCol(missile, DamageType::Physical, missile._midam, missile.var3, true, false); + if (missile._mirange == 0) { missile._mimfnum = 0; missile._mirange = missile._miAnimLen - 1; missile.position.StopMissile(); - int eMind; - int eMaxd; MissileGraphicID eAnim; DamageType damageType; + switch (missile._mitype) { case MissileID::LightningArrow: - if (!missile.IsTrap()) { - // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. - const Player &player = Players[p]; - eMind = player._pILMinDam; - eMaxd = player._pILMaxDam; - } else { - eMind = GenerateRnd(10) + 1 + currlevel; - eMaxd = GenerateRnd(10) + 1 + currlevel * 2; - } eAnim = MissileGraphicID::ChargedBolt; damageType = DamageType::Lightning; break; case MissileID::FireArrow: - if (!missile.IsTrap()) { - // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. - const Player &player = Players[p]; - eMind = player._pIFMinDam; - eMaxd = player._pIFMaxDam; - } else { - eMind = GenerateRnd(10) + 1 + currlevel; - eMaxd = GenerateRnd(10) + 1 + currlevel * 2; - } eAnim = MissileGraphicID::MagmaBallExplosion; damageType = DamageType::Fire; break; - default: - app_fatal(StrCat("wrong missile ID ", static_cast(missile._mitype))); - break; } + SetMissAnim(missile, eAnim); - CheckMissileCol(missile, damageType, eMind, eMaxd, false, missile.position.tile, true); + CheckMissileCol(missile, damageType, missile.var4, missile.var5, false, missile.position.tile, true); } else { if (missile.position.tile != Point { missile.var1, missile.var2 }) { missile.var1 = missile.position.tile.x; @@ -2851,29 +2940,11 @@ void ProcessArrow(Missile &missile) missile._mirange--; missile._midist++; - int mind; - int maxd; - switch (missile.sourceType()) { - case MissileSource::Player: { - // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. - const Player &player = *missile.sourcePlayer(); - mind = player._pIMinDam; - maxd = player._pIMaxDam; - } break; - case MissileSource::Monster: { - // BUGFIX: damage of missile should be encoded in missile struct; monster can be dead before missile arrives. - const Monster &monster = *missile.sourceMonster(); - mind = monster.minDamage; - maxd = monster.maxDamage; - } break; - case MissileSource::Trap: - mind = currlevel; - maxd = 2 * currlevel; - break; - } - MoveMissileAndCheckMissileCol(missile, GetMissileData(missile._mitype).damageType(), mind, maxd, true, false); + MoveMissileAndCheckMissileCol(missile, GetMissileData(missile._mitype).damageType(), missile._midam, missile.var1, true, false); + if (missile._mirange == 0) missile._miDelFlag = true; + PutMissile(missile); } @@ -3128,7 +3199,7 @@ void ProcessBigExplosion(Missile &missile) PutMissile(missile); } -void ProcessLightningBow(Missile &missile) +void ProcessItemLightning(Missile &missile) { SpawnLightning(missile, missile._midam); } @@ -3203,7 +3274,7 @@ void ProcessNovaCommon(Missile &missile, MissileID projectileType) void ProcessImmolation(Missile &missile) { - ProcessNovaCommon(missile, MissileID::FireballBow); + ProcessNovaCommon(missile, MissileID::ItemFireball); } void ProcessNova(Missile &missile) @@ -3211,42 +3282,43 @@ void ProcessNova(Missile &missile) ProcessNovaCommon(missile, MissileID::NovaBall); } -void ProcessSpectralArrow(Missile &missile) +void ProcessItemMissile(Missile &missile) { - int id = missile._misource; - int dam = missile._midam; + if (missile.sourceType() != MissileSource::Player) + return; + Point src = missile.position.tile; Point dst = { missile.var1, missile.var2 }; - int spllvl = missile.var3; + const Player &player = *missile.sourcePlayer(); + Direction dir = player._pdir; MissileID mitype = MissileID::Arrow; - Direction dir = Direction::South; - mienemy_type micaster = TARGET_PLAYERS; - if (!missile.IsTrap()) { - const Player &player = Players[id]; - dir = player._pdir; - micaster = TARGET_MONSTERS; + mienemy_type micaster = TARGET_MONSTERS; + int id = missile._misource; + int dam = missile._midam; + int spllvl = missile.var3; - switch (player._pILMinDam) { - case 0: - mitype = MissileID::FireballBow; - break; - case 1: - mitype = MissileID::LightningBow; - break; - case 2: - mitype = MissileID::ChargedBoltBow; - break; - case 3: - mitype = MissileID::HolyBoltBow; - break; - } - } - AddMissile(src, dst, dir, mitype, micaster, id, dam, spllvl); - if (mitype == MissileID::ChargedBoltBow) { + switch (missile.var4) { + case IPL_FIREBALL: + mitype = MissileID::ItemFireball; AddMissile(src, dst, dir, mitype, micaster, id, dam, spllvl); + break; + case IPL_ADDACLIFE: + mitype = MissileID::ItemLightning; AddMissile(src, dst, dir, mitype, micaster, id, dam, spllvl); + break; + case IPL_ADDMANAAC: + mitype = MissileID::ItemChargedBolt; + + for (int i = 0; i < 3; i++) { + AddMissile(src, dst, dir, mitype, micaster, id, dam, spllvl); + } + break; + default: + break; } + missile._mirange--; + if (missile._mirange == 0) missile._miDelFlag = true; } @@ -3255,19 +3327,7 @@ void ProcessLightningControl(Missile &missile) { missile._mirange--; - int dam; - if (missile.IsTrap()) { - // BUGFIX: damage of missile should be encoded in missile struct; monster can be dead before missile arrives. - dam = GenerateRnd(currlevel) + 2 * currlevel; - } else if (missile._micaster == TARGET_MONSTERS) { - // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. - dam = (GenerateRnd(2) + GenerateRnd(Players[missile._misource].getCharacterLevel()) + 2) << 6; - } else { - Monster &monster = Monsters[missile._misource]; - dam = 2 * (monster.minDamage + GenerateRnd(monster.maxDamage - monster.minDamage + 1)); - } - - SpawnLightning(missile, dam); + SpawnLightning(missile, missile._midam); } void ProcessLightning(Missile &missile) @@ -3508,29 +3568,27 @@ void ProcessWeaponExplosion(Missile &missile) constexpr int ExpLight[10] = { 9, 10, 11, 12, 11, 10, 8, 6, 4, 2 }; missile._mirange--; - const Player &player = Players[missile._misource]; - int mind; - int maxd; + + const Player &player = *missile.sourcePlayer(); DamageType damageType; + if (missile.var2 == 1) { - // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. - mind = player._pIFMinDam; - maxd = player._pIFMaxDam; damageType = DamageType::Fire; } else { - // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. - mind = player._pILMinDam; - maxd = player._pILMaxDam; damageType = DamageType::Lightning; } - CheckMissileCol(missile, damageType, mind, maxd, false, missile.position.tile, false); + + CheckMissileCol(missile, damageType, missile._midam, missile.var3, false, missile.position.tile, false); + if (missile.var1 == 0) { missile._mlid = AddLight(missile.position.tile, 9); } else { if (missile._mirange != 0) ChangeLight(missile._mlid, missile.position.tile, ExpLight[missile.var1]); } + missile.var1++; + if (missile._mirange == 0) { missile._miDelFlag = true; AddUnLight(missile._mlid); diff --git a/Source/missiles.h b/Source/missiles.h index a1927bf9b22..435c57a506e 100644 --- a/Source/missiles.h +++ b/Source/missiles.h @@ -264,17 +264,17 @@ void AddHorkSpawn(Missile &missile, AddMissileParameter ¶meter); void AddJester(Missile &missile, AddMissileParameter ¶meter); void AddStealPotions(Missile &missile, AddMissileParameter ¶meter); void AddStealMana(Missile &missile, AddMissileParameter ¶meter); -void AddSpectralArrow(Missile &missile, AddMissileParameter ¶meter); +void AddItemMissile(Missile &missile, AddMissileParameter ¶meter); void AddWarp(Missile &missile, AddMissileParameter ¶meter); void AddLightningWall(Missile &missile, AddMissileParameter ¶meter); void AddBigExplosion(Missile &missile, AddMissileParameter ¶meter); void AddImmolation(Missile &missile, AddMissileParameter ¶meter); -void AddLightningBow(Missile &missile, AddMissileParameter ¶meter); +void AddItemLightning(Missile &missile, AddMissileParameter ¶meter); void AddMana(Missile &missile, AddMissileParameter ¶meter); void AddMagi(Missile &missile, AddMissileParameter ¶meter); void AddRingOfFire(Missile &missile, AddMissileParameter ¶meter); void AddSearch(Missile &missile, AddMissileParameter ¶meter); -void AddChargedBoltBow(Missile &missile, AddMissileParameter ¶meter); +void AddItemChargedBolt(Missile &missile, AddMissileParameter ¶meter); void AddElementalArrow(Missile &missile, AddMissileParameter ¶meter); void AddArrow(Missile &missile, AddMissileParameter ¶meter); void AddPhasing(Missile &missile, AddMissileParameter ¶meter); @@ -418,11 +418,11 @@ void ProcessHorkSpawn(Missile &missile); void ProcessRune(Missile &missile); void ProcessLightningWall(Missile &missile); void ProcessBigExplosion(Missile &missile); -void ProcessLightningBow(Missile &missile); +void ProcessItemLightning(Missile &missile); void ProcessRingOfFire(Missile &missile); void ProcessSearch(Missile &missile); void ProcessImmolation(Missile &missile); -void ProcessSpectralArrow(Missile &missile); +void ProcessItemMissile(Missile &missile); void ProcessLightningControl(Missile &missile); void ProcessLightning(Missile &missile); void ProcessTownPortal(Missile &missile); diff --git a/Source/spelldat.h b/Source/spelldat.h index 74458bc8663..d3dcd090c72 100644 --- a/Source/spelldat.h +++ b/Source/spelldat.h @@ -174,10 +174,10 @@ enum class MissileID : int8_t { LightningWallControl, Immolation, SpectralArrow, - FireballBow, - LightningBow, - ChargedBoltBow, - HolyBoltBow, + ItemFireball, + ItemLightning, + ItemChargedBolt, + ItemHolyBolt, // unused Warp, Reflect, Berserk, diff --git a/assets/txtdata/missiles/misdat.tsv b/assets/txtdata/missiles/misdat.tsv index 986f99a9372..8c4b54027a9 100644 --- a/assets/txtdata/missiles/misdat.tsv +++ b/assets/txtdata/missiles/misdat.tsv @@ -72,11 +72,11 @@ Magi AddMagi Physical,Invisible LightningWall AddLightningWall ProcessLightningWall SpellLightningWall SpellLightningHit Lightning Lightning LightningWallControl AddWallControl ProcessWallControl Lightning Lightning,Invisible Immolation AddNova ProcessImmolation SpellFirebolt SpellFireHit Fireball Fire -SpectralArrow AddSpectralArrow ProcessSpectralArrow Arrow Physical,Arrow -FireballBow AddImmolation ProcessFireball ShootFireballBow SpellFireHit Fireball Fire Blockable -LightningBow AddLightningBow ProcessLightningBow ShootFireballBow Lightning Lightning,Invisible -ChargedBoltBow AddChargedBoltBow ProcessChargedBolt SpellChargedBolt ChargedBolt Lightning Blockable -HolyBoltBow AddHolyBolt ProcessHolyBolt SpellHolyBolt SpellLightningHit HolyBolt Physical Blockable +ItemMissile AddItemMissile ProcessItemMissile Arrow Physical,Arrow +ItemFireball AddImmolation ProcessFireball ShootFireballBow SpellFireHit Fireball Fire Blockable +ItemLightning AddItemLightning ProcessItemLightning ShootFireballBow Lightning Lightning,Invisible +ItemChargedBolt AddItemChargedBolt ProcessChargedBolt SpellChargedBolt ChargedBolt Lightning Blockable +ItemHolyBolt AddHolyBolt ProcessHolyBolt SpellHolyBolt SpellLightningHit HolyBolt Physical Blockable Warp AddWarp ProcessTeleport SpellEtherealize Physical,Invisible Reflect AddReflect SpellManaShield Reflect Physical,Invisible Berserk AddBerserk Physical,Invisible