/** * @file missiles.h * * Interface of missile functionality. */ #pragma once #include #include #include "engine.h" #include "engine/point.hpp" #include "misdat.h" #include "monster.h" #include "player.h" #include "spelldat.h" #include "utils/stdcompat/optional.hpp" namespace devilution { constexpr WorldTilePosition GolemHoldingCell = Point { 1, 0 }; struct MissilePosition { Point tile; /** Sprite's pixel offset from tile. */ Displacement offset; /** Pixel velocity while moving */ Displacement velocity; /** Start position */ Point start; /** Start position */ Displacement traveled; /** * @brief Specifies the location (tile) while rendering */ Point tileForRendering; /** * @brief Specifies the location (offset) while rendering */ Displacement offsetForRendering; /** * @brief Stops the missile (set velocity to zero and set offset to last renderer location; shouldn't matter cause the missile don't move anymore) */ void StopMissile() { velocity = {}; if (tileForRendering == tile) offset = offsetForRendering; } }; /** * Represent a more fine-grained direction than the 8 value Direction enum. * * This is used when rendering projectiles like arrows which have additional sprites for "half-winds" on a 16-point compass. * The sprite sheets are typically 0-indexed and use the following layout (relative to the screen projection) * * W WSW SW SSW S * ^ * WNW | SSE * | * NW -------+------> SE * | * NNW | ESE * | * N NNE NE ENE E */ enum class Direction16 : uint8_t { South, South_SouthWest, SouthWest, West_SouthWest, West, West_NorthWest, NorthWest, North_NorthWest, North, North_NorthEast, NorthEast, East_NorthEast, East, East_SouthEast, SouthEast, South_SouthEast, }; enum class MissileSource : uint8_t { Player, Monster, Trap, }; struct Missile { /** Type of projectile */ MissileID _mitype; MissilePosition position; int _mimfnum; // The direction of the missile (direction enum) int _mispllvl; bool _miDelFlag; // Indicate whether the missile should be deleted MissileGraphicID _miAnimType; MissileGraphicsFlags _miAnimFlags; OptionalClxSpriteList _miAnimData; int _miAnimDelay; // Tick length of each frame in the current animation int _miAnimLen; // Number of frames in current animation // TODO: This field is no longer used and is always equal to // (*_miAnimData)[0].width() uint16_t _miAnimWidth; int16_t _miAnimWidth2; int _miAnimCnt; // Increases by one each game tick, counting how close we are to _pAnimDelay int _miAnimAdd; int _miAnimFrame; // Current frame of animation + 1. bool _miDrawFlag; bool _miLightFlag; bool _miPreFlag; uint32_t _miUniqTrans; int _mirange; // Time to live for the missile in game ticks, oncs 0 the missile will be marked for deletion via _miDelFlag int _misource; mienemy_type _micaster; int _midam; bool _miHitFlag; int _midist; // Used for arrows to measure distance travelled (increases by 1 each game tick). Higher value is a penalty for accuracy calculation when hitting enemy int _mlid; int _mirnd; int var1; int var2; int var3; int var4; int var5; int var6; int var7; bool limitReached; /** * @brief For moving missiles lastCollisionTargetHash contains the last entity (player or monster) that was checked in CheckMissileCol (needed to avoid multiple hits for a entity at the same tile). */ int16_t lastCollisionTargetHash; /** @brief Was the missile generated by a trap? */ [[nodiscard]] bool IsTrap() const { return _misource == -1; } [[nodiscard]] Player *sourcePlayer() { if (IsNoneOf(_micaster, TARGET_BOTH, TARGET_MONSTERS) || _misource == -1) return nullptr; return &Players[_misource]; } [[nodiscard]] Monster *sourceMonster() { if (_micaster != TARGET_PLAYERS || _misource == -1) return nullptr; return &Monsters[_misource]; } [[nodiscard]] bool isSameSource(Missile &missile) { return sourceType() == missile.sourceType() && _misource == missile._misource; } MissileSource sourceType() { if (_misource == -1) return MissileSource::Trap; if (_micaster == TARGET_PLAYERS) return MissileSource::Monster; return MissileSource::Player; } }; extern std::list Missiles; extern bool MissilePreFlag; void GetDamageAmt(SpellID i, int *mind, int *maxd); /** * @brief Returns the direction a vector from p1(x1, y1) to p2(x2, y2) is pointing to. * * @code{.unparsed} * W sW SW Sw S * ^ * nW | Se * | * NW ------+-----> SE * | * Nw | sE * | * N Ne NE nE E * @endcode * * @param p1 The point from which the vector starts. * @param p2 The point from which the vector ends. * @return the direction of the p1->p2 vector */ Direction16 GetDirection16(Point p1, Point p2); bool MonsterTrapHit(int monsterId, int mindam, int maxdam, int dist, MissileID t, DamageType damageType, bool shift); bool PlayerMHit(int pnum, Monster *monster, int dist, int mind, int maxd, MissileID mtype, DamageType damageType, bool shift, DeathReason deathReason, bool *blocked); /** * @brief Could the missile collide with solid objects? (like walls or closed doors) */ bool IsMissileBlockedByTile(Point position); /** * @brief Sets the missile sprite to the given sheet frame * @param missile this object * @param dir Sprite frame, typically representing a direction but there are some exceptions (arrows being 1 indexed, directionless spells) */ void SetMissDir(Missile &missile, int dir); /** * @brief Sets the sprite for this missile so it matches the given Direction * @param missile this object * @param dir Desired facing */ inline void SetMissDir(Missile &missile, Direction dir) { SetMissDir(missile, static_cast(dir)); } /** * @brief Sets the sprite for this missile so it matches the given Direction16 * @param missile this object * @param dir Desired facing at a 22.8125 degree resolution */ inline void SetMissDir(Missile &missile, Direction16 dir) { SetMissDir(missile, static_cast(dir)); } void InitMissiles(); struct AddMissileParameter { Point dst; Direction midir; Missile *pParent; bool spellFizzled; }; void AddOpenNest(Missile &missile, AddMissileParameter ¶meter); void AddRuneOfFire(Missile &missile, AddMissileParameter ¶meter); void AddRuneOfLight(Missile &missile, AddMissileParameter ¶meter); void AddRuneOfNova(Missile &missile, AddMissileParameter ¶meter); void AddRuneOfImmolation(Missile &missile, AddMissileParameter ¶meter); void AddRuneOfStone(Missile &missile, AddMissileParameter ¶meter); void AddReflect(Missile &missile, AddMissileParameter ¶meter); void AddBerserk(Missile &missile, AddMissileParameter ¶meter); /** * var1: Direction to place the spawn */ 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 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 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 AddElementalArrow(Missile &missile, AddMissileParameter ¶meter); void AddArrow(Missile &missile, AddMissileParameter ¶meter); void AddPhasing(Missile &missile, AddMissileParameter ¶meter); void AddFirebolt(Missile &missile, AddMissileParameter ¶meter); void AddMagmaBall(Missile &missile, AddMissileParameter ¶meter); void AddTeleport(Missile &missile, AddMissileParameter ¶meter); void AddNovaBall(Missile &missile, AddMissileParameter ¶meter); void AddFireWall(Missile &missile, AddMissileParameter ¶meter); /** * var1: X coordinate of the missile-light * var2: Y coordinate of the missile-light * var4: X coordinate of the missile-light * var5: Y coordinate of the missile-light */ void AddFireball(Missile &missile, AddMissileParameter ¶meter); /** * var1: X coordinate of the missile * var2: Y coordinate of the missile */ void AddLightningControl(Missile &missile, AddMissileParameter ¶meter); void AddLightning(Missile &missile, AddMissileParameter ¶meter); void AddMissileExplosion(Missile &missile, AddMissileParameter ¶meter); void AddWeaponExplosion(Missile &missile, AddMissileParameter ¶meter); /** * var1: Animation */ void AddTownPortal(Missile &missile, AddMissileParameter ¶meter); void AddFlashBottom(Missile &missile, AddMissileParameter ¶meter); void AddFlashTop(Missile &missile, AddMissileParameter ¶meter); void AddManaShield(Missile &missile, AddMissileParameter ¶meter); void AddFlameWave(Missile &missile, AddMissileParameter ¶meter); /** * var1: Animation * var3: Light strength */ void AddGuardian(Missile &missile, AddMissileParameter ¶meter); /** * var1: X coordinate of the destination * var2: Y coordinate of the destination */ void AddChainLightning(Missile &missile, AddMissileParameter ¶meter); void AddRhino(Missile &missile, AddMissileParameter ¶meter); /** * var1: X coordinate of the missile-light * var2: Y coordinate of the missile-light */ void AddGenericMagicMissile(Missile &missile, AddMissileParameter ¶meter); /** * var1: X coordinate of the missile-light * var2: Y coordinate of the missile-light */ void AddAcid(Missile &missile, AddMissileParameter ¶meter); void AddAcidPuddle(Missile &missile, AddMissileParameter ¶meter); /** * var1: mmode of the monster * var2: mnum of the monster */ void AddStoneCurse(Missile &missile, AddMissileParameter ¶meter); void AddGolem(Missile &missile, AddMissileParameter ¶meter); void AddApocalypseBoom(Missile &missile, AddMissileParameter ¶meter); void AddHealing(Missile &missile, AddMissileParameter ¶meter); void AddHealOther(Missile &missile, AddMissileParameter ¶meter); /** * var1: X coordinate of the missile-light * var2: Y coordinate of the missile-light * var4: X coordinate of the destination * var5: Y coordinate of the destination */ void AddElemental(Missile &missile, AddMissileParameter ¶meter); void AddIdentify(Missile &missile, AddMissileParameter ¶meter); /** * var1: X coordinate of the first wave * var2: Y coordinate of the first wave * var3: Direction of the first wave * var4: Direction of the second wave * var5: X coordinate of the second wave * var6: Y coordinate of the second wave */ void AddFireWallControl(Missile &missile, AddMissileParameter ¶meter); void AddInfravision(Missile &missile, AddMissileParameter ¶meter); /** * var1: X coordinate of the destination * var2: Y coordinate of the destination */ void AddFlameWaveControl(Missile &missile, AddMissileParameter ¶meter); void AddNova(Missile &missile, AddMissileParameter ¶meter); void AddRage(Missile &missile, AddMissileParameter ¶meter); void AddItemRepair(Missile &missile, AddMissileParameter ¶meter); void AddStaffRecharge(Missile &missile, AddMissileParameter ¶meter); void AddTrapDisarm(Missile &missile, AddMissileParameter ¶meter); void AddApocalypse(Missile &missile, AddMissileParameter ¶meter); void AddInferno(Missile &missile, AddMissileParameter ¶meter); void AddInfernoControl(Missile &missile, AddMissileParameter ¶meter); /** * var1: Light strength * var2: Base direction */ void AddChargedBolt(Missile &missile, AddMissileParameter ¶meter); void AddHolyBolt(Missile &missile, AddMissileParameter ¶meter); void AddResurrect(Missile &missile, AddMissileParameter ¶meter); void AddResurrectBeam(Missile &missile, AddMissileParameter ¶meter); void AddTelekinesis(Missile &missile, AddMissileParameter ¶meter); void AddBoneSpirit(Missile &missile, AddMissileParameter ¶meter); void AddRedPortal(Missile &missile, AddMissileParameter ¶meter); void AddDiabloApocalypse(Missile &missile, AddMissileParameter ¶meter); Missile *AddMissile(Point src, Point dst, Direction midir, MissileID mitype, mienemy_type micaster, int id, int midam, int spllvl, Missile *parent = nullptr, std::optional<_sfx_id> lSFX = std::nullopt); void ProcessElementalArrow(Missile &missile); void ProcessArrow(Missile &missile); void ProcessGenericProjectile(Missile &missile); void ProcessNovaBall(Missile &missilei); void ProcessAcidPuddle(Missile &missile); void ProcessFireWall(Missile &missile); void ProcessFireball(Missile &missile); void ProcessHorkSpawn(Missile &missile); void ProcessRune(Missile &missile); void ProcessLightningWall(Missile &missile); void ProcessBigExplosion(Missile &missile); void ProcessLightningBow(Missile &missile); void ProcessRingOfFire(Missile &missile); void ProcessSearch(Missile &missile); void ProcessLightningWallControl(Missile &missile); void ProcessImmolation(Missile &missile); void ProcessSpectralArrow(Missile &missile); void ProcessLightningControl(Missile &missile); void ProcessLightning(Missile &missile); void ProcessTownPortal(Missile &missile); void ProcessFlashBottom(Missile &missile); void ProcessFlashTop(Missile &missile); void ProcessFlameWave(Missile &missile); void ProcessGuardian(Missile &missile); void ProcessChainLightning(Missile &missile); void ProcessWeaponExplosion(Missile &missile); void ProcessMissileExplosion(Missile &missile); void ProcessAcidSplate(Missile &missile); void ProcessTeleport(Missile &missile); void ProcessStoneCurse(Missile &missile); void ProcessApocalypseBoom(Missile &missile); void ProcessRhino(Missile &missile); void ProcessFireWallControl(Missile &missile); void ProcessInfravision(Missile &missile); void ProcessApocalypse(Missile &missile); void ProcessFlameWaveControl(Missile &missile); void ProcessNova(Missile &missile); void ProcessRage(Missile &missile); void ProcessInferno(Missile &missile); void ProcessInfernoControl(Missile &missile); void ProcessChargedBolt(Missile &missile); void ProcessHolyBolt(Missile &missile); void ProcessElemental(Missile &missile); void ProcessBoneSpirit(Missile &missile); void ProcessResurrectBeam(Missile &missile); void ProcessRedPortal(Missile &missile); void ProcessMissiles(); void missiles_process_charge(); void RedoMissileFlags(); #ifdef BUILD_TESTING void TestRotateBlockedMissile(Missile &missile); #endif } // namespace devilution