You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
496 lines
16 KiB
496 lines
16 KiB
/** |
|
* @file missiles.h |
|
* |
|
* Interface of missile functionality. |
|
*/ |
|
#pragma once |
|
|
|
#include <cstdint> |
|
#include <list> |
|
#include <optional> |
|
|
|
#include "engine/displacement.hpp" |
|
#include "engine/point.hpp" |
|
#include "engine/world_tile.hpp" |
|
#include "misdat.h" |
|
#include "monster.h" |
|
#include "player.h" |
|
#include "spelldat.h" |
|
#include "utils/is_of.hpp" |
|
|
|
namespace devilution { |
|
|
|
constexpr WorldTilePosition GolemHoldingCell = Point { 1, 0 }; |
|
|
|
struct MissilePosition { |
|
/** Sprite's pixel offset from tile. */ |
|
Displacement offset; |
|
/** Pixel velocity while moving */ |
|
Displacement velocity; |
|
/** Pixels traveled as a numerator of 65,536. */ |
|
Displacement traveled; |
|
|
|
WorldTilePosition tile; |
|
/** Start position */ |
|
WorldTilePosition start; |
|
|
|
/** |
|
* @brief Specifies the location (tile) while rendering |
|
*/ |
|
WorldTilePosition 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 because the missile doesn't move anymore) |
|
*/ |
|
void StopMissile() |
|
{ |
|
velocity = {}; |
|
if (tileForRendering == tile) |
|
offset = offsetForRendering; |
|
} |
|
}; |
|
|
|
enum class MissileSource : uint8_t { |
|
Player, |
|
Monster, |
|
Trap, |
|
}; |
|
|
|
enum class GuardianFrame : uint8_t { |
|
Start = 0, |
|
Idle = 1, |
|
Attack = 2, |
|
}; |
|
|
|
enum class AcidPuddleFrame : uint8_t { |
|
Idle = 0, |
|
End = 1, |
|
}; |
|
|
|
enum class FireWallFrame : uint8_t { |
|
Start = 0, |
|
Idle = 1, |
|
}; |
|
|
|
enum class PortalFrame : uint8_t { |
|
Start = 0, |
|
Idle = 1, |
|
}; |
|
|
|
enum class RedPortalFrame : uint8_t { |
|
Start = 0, |
|
Idle = 1, |
|
}; |
|
|
|
struct Missile { |
|
/** Type of projectile */ |
|
MissileID _mitype; |
|
MissilePosition position; |
|
|
|
private: |
|
int _mimfnum; // The direction of the missile (direction enum) |
|
|
|
public: |
|
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; |
|
/** @brief Time to live for the missile in game ticks; once 0, the missile will be marked for deletion via _miDelFlag */ |
|
int duration; |
|
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; |
|
} |
|
|
|
void setAnimation(MissileGraphicID animtype); |
|
|
|
/** |
|
* @brief Sets the missile sprite to the given sheet frame |
|
* @param dir Sprite frame |
|
*/ |
|
void setFrameGroupRaw(int frameGroup) |
|
{ |
|
_mimfnum = frameGroup; |
|
setAnimation(_miAnimType); |
|
} |
|
|
|
void setDefaultFrameGroup() |
|
{ |
|
setFrameGroupRaw(0); |
|
} |
|
|
|
template <typename FrameEnum> |
|
void setFrameGroup(FrameEnum frameGroup) |
|
{ |
|
setFrameGroupRaw(static_cast<int>(frameGroup)); |
|
} |
|
|
|
/** |
|
* @brief Sets the sprite for this missile so it matches the given Direction |
|
* @param dir Desired facing |
|
*/ |
|
void setDirection(Direction dir) |
|
{ |
|
setFrameGroupRaw(static_cast<int>(dir)); |
|
} |
|
|
|
/** |
|
* @brief Sets the sprite for this missile so it matches the given Direction16 |
|
* @param dir Desired facing at a 22.8125 degree resolution |
|
*/ |
|
void setDirection(Direction16 dir) |
|
{ |
|
setFrameGroupRaw(static_cast<int>(dir)); |
|
} |
|
|
|
int getFrameGroupRaw() const |
|
{ |
|
return _mimfnum; |
|
} |
|
|
|
template <typename FrameEnum> |
|
FrameEnum getFrameGroup() const |
|
{ |
|
static_assert(std::is_enum_v<FrameEnum>, "Frame group must be an enum"); |
|
return static_cast<FrameEnum>(_mimfnum); |
|
} |
|
|
|
[[nodiscard]] Direction getDirection() const |
|
{ |
|
return static_cast<Direction>(_mimfnum); |
|
} |
|
|
|
[[nodiscard]] Direction16 getDirection16() const |
|
{ |
|
return static_cast<Direction16>(_mimfnum); |
|
} |
|
}; |
|
|
|
extern std::list<Missile> Missiles; |
|
extern bool MissilePreFlag; |
|
|
|
struct DamageRange { |
|
int min; |
|
int max; |
|
}; |
|
DamageRange GetDamageAmt(SpellID spell, int spellLevel); |
|
|
|
/** |
|
* @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(Monster &monster, int mindam, int maxdam, int dist, MissileID t, DamageType damageType, bool shift); |
|
bool PlayerMHit(Player &player, 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 tile); |
|
|
|
void InitMissiles(); |
|
|
|
struct AddMissileParameter { |
|
WorldTilePosition 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 AddWallControl(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(WorldTilePosition src, WorldTilePosition dst, Direction midir, MissileID mitype, |
|
mienemy_type micaster, int id, int midam, int spllvl, |
|
Missile *parent = nullptr, std::optional<SfxID> lSFX = std::nullopt); |
|
inline Missile *AddMissile(WorldTilePosition src, WorldTilePosition dst, Direction midir, MissileID mitype, |
|
mienemy_type micaster, const Player &player, int midam, int spllvl, |
|
Missile *parent = nullptr, std::optional<SfxID> lSFX = std::nullopt) |
|
{ |
|
return AddMissile(src, dst, midir, mitype, micaster, player.getId(), midam, spllvl, parent, lSFX); |
|
} |
|
inline Missile *AddMissile(WorldTilePosition src, WorldTilePosition dst, Direction midir, MissileID mitype, |
|
mienemy_type micaster, const Monster &monster, int midam, int spllvl, |
|
Missile *parent = nullptr, std::optional<SfxID> lSFX = std::nullopt) |
|
{ |
|
return AddMissile(src, dst, midir, mitype, micaster, static_cast<int>(monster.getId()), midam, spllvl, parent, lSFX); |
|
} |
|
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 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 ProcessWallControl(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 SetUpMissileAnimationData(); |
|
void RedoMissileFlags(); |
|
|
|
#ifdef BUILD_TESTING |
|
void TestRotateBlockedMissile(Missile &missile); |
|
#endif |
|
|
|
} // namespace devilution
|
|
|