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.
340 lines
12 KiB
340 lines
12 KiB
/** |
|
* @file objects.h |
|
* |
|
* Interface of object functionality, interaction, spawning, loading, etc. |
|
*/ |
|
#pragma once |
|
|
|
#include <cstdint> |
|
|
|
#include "engine/clx_sprite.hpp" |
|
#include "engine/point.hpp" |
|
#include "engine/rectangle.hpp" |
|
#include "engine/world_tile.hpp" |
|
#include "itemdat.h" |
|
#include "monster.h" |
|
#include "objdat.h" |
|
#include "textdat.h" |
|
#include "utils/attributes.h" |
|
#include "utils/string_or_view.hpp" |
|
|
|
namespace devilution { |
|
|
|
#define MAXOBJECTS 127 |
|
|
|
struct Object { |
|
_object_id _otype = OBJ_NULL; |
|
bool _oLight = false; |
|
bool _oTrapFlag = false; |
|
bool _oDoorFlag = false; |
|
|
|
Point position; |
|
uint32_t _oAnimFlag = 0; |
|
OptionalClxSpriteList _oAnimData; |
|
int _oAnimDelay = 0; // Tick length of each frame in the current animation |
|
int _oAnimCnt = 0; // Increases by one each game tick, counting how close we are to _pAnimDelay |
|
uint32_t _oAnimLen = 0; // Number of frames in current animation |
|
uint32_t _oAnimFrame = 0; // Current frame of animation. |
|
|
|
// TODO: Remove this field, it is unused and always equal to: |
|
// (*_oAnimData)[0].width() |
|
uint16_t _oAnimWidth = 0; |
|
|
|
bool _oDelFlag = false; |
|
int8_t _oBreak = 0; |
|
bool _oSolidFlag = false; |
|
/** True if the object allows missiles to pass through, false if it collides with missiles */ |
|
bool _oMissFlag = false; |
|
uint8_t _oSelFlag = 0; |
|
bool _oPreFlag = false; |
|
int _olid = 0; |
|
/** |
|
* Saves the absolute value of the engine state (typically from a call to AdvanceRndSeed()) to later use when spawning items from a container object |
|
* This is an unsigned value to avoid implementation defined behaviour when reading from this variable. |
|
*/ |
|
uint32_t _oRndSeed = 0; |
|
int _oVar1 = 0; |
|
int _oVar2 = 0; |
|
int _oVar3 = 0; |
|
int _oVar4 = 0; |
|
int _oVar5 = 0; |
|
uint32_t _oVar6 = 0; |
|
int _oVar8 = 0; |
|
|
|
/** |
|
* @brief ID of a quest message to play when this object is activated. |
|
* |
|
* Used by spell book objects which trigger quest progress for Halls of the Blind, Valor, or Warlord of Blood |
|
*/ |
|
_speech_id bookMessage = TEXT_NONE; |
|
|
|
/** |
|
* @brief Returns the network identifier for this object |
|
* |
|
* This is currently the index into the Objects array, but may change in the future. |
|
*/ |
|
[[nodiscard]] unsigned int GetId() const; |
|
|
|
/** |
|
* @brief Marks the map region to be refreshed when the player interacts with the object. |
|
* |
|
* Some objects will cause a map region to change when a player interacts with them (e.g. Skeleton King |
|
* antechamber levers). The coordinates used for this region are based on a 40*40 grid overlaying the central |
|
* 80*80 region of the dungeon. |
|
* |
|
* @param topLeftPosition corner of the map region closest to the origin. |
|
* @param bottomRightPosition corner of the map region furthest from the origin. |
|
*/ |
|
constexpr void SetMapRange(WorldTilePosition topLeftPosition, WorldTilePosition bottomRightPosition) |
|
{ |
|
_oVar1 = topLeftPosition.x; |
|
_oVar2 = topLeftPosition.y; |
|
_oVar3 = bottomRightPosition.x; |
|
_oVar4 = bottomRightPosition.y; |
|
} |
|
|
|
/** |
|
* @brief Convenience function for SetMapRange(Point, Point). |
|
* @param mapRange A rectangle defining the top left corner and size of the affected region. |
|
*/ |
|
constexpr void SetMapRange(WorldTileRectangle mapRange) |
|
{ |
|
SetMapRange(mapRange.position, mapRange.position + DisplacementOf<uint8_t>(mapRange.size)); |
|
} |
|
|
|
/** |
|
* @brief Sets up a generic quest book which will trigger a change in the map when activated. |
|
* |
|
* Books of this type use a generic message (see OperateSChambBook()) compared to the more specific quest books |
|
* initialized by IntializeQuestBook(). |
|
* |
|
* @param mapRange The region to be updated when this object is activated. |
|
*/ |
|
constexpr void InitializeBook(WorldTileRectangle mapRange) |
|
{ |
|
SetMapRange(mapRange); |
|
_oVar6 = _oAnimFrame + 1; // Save the frame number for the open book frame |
|
} |
|
|
|
/** |
|
* @brief Initializes this object as a quest book which will cause further changes and play a message when activated. |
|
* @param mapRange The region to be updated when this object is activated. |
|
* @param leverID An ID (distinct from the object index) to identify the new objects spawned after updating the map. |
|
* @param message The quest text to play when this object is activated. |
|
*/ |
|
constexpr void InitializeQuestBook(WorldTileRectangle mapRange, int leverID, _speech_id message) |
|
{ |
|
InitializeBook(mapRange); |
|
_oVar8 = leverID; |
|
bookMessage = message; |
|
} |
|
|
|
/** |
|
* @brief Marks an object which was spawned from a sublevel in response to a lever activation. |
|
* @param mapRange The region which was updated to spawn this object. |
|
* @param leverID The id (*not* an object ID/index) of the lever responsible for the map change. |
|
*/ |
|
constexpr void InitializeLoadedObject(WorldTileRectangle mapRange, int leverID) |
|
{ |
|
SetMapRange(mapRange); |
|
_oVar8 = leverID; |
|
} |
|
|
|
/** |
|
* @brief Check if the object can be broken (is an intact barrel or crux) |
|
* @return True if the object is intact and breakable, false if already broken or not a breakable object. |
|
*/ |
|
[[nodiscard]] constexpr bool IsBreakable() const |
|
{ |
|
return _oBreak == 1; |
|
} |
|
|
|
/** |
|
* @brief Check if the object has been broken |
|
* @return True if the object is breakable and has been broken, false if unbroken or not a breakable object. |
|
*/ |
|
[[nodiscard]] constexpr bool IsBroken() const |
|
{ |
|
return _oBreak == -1; |
|
} |
|
|
|
/** |
|
* Returns true if the object is a harmful shrine and the player has disabled permanent shrine effects. |
|
*/ |
|
[[nodiscard]] bool IsDisabled() const; |
|
|
|
/** |
|
* @brief Check if this object is barrel (or explosive barrel) |
|
* @return True if the object is one of the barrel types (see _object_id) |
|
*/ |
|
[[nodiscard]] constexpr bool IsBarrel() const |
|
{ |
|
return IsAnyOf(_otype, _object_id::OBJ_BARREL, _object_id::OBJ_BARRELEX, _object_id::OBJ_POD, _object_id::OBJ_PODEX, _object_id::OBJ_URN, _object_id::OBJ_URNEX); |
|
} |
|
|
|
/** |
|
* @brief Check if this object contains explosives or caustic material |
|
*/ |
|
[[nodiscard]] constexpr bool isExplosive() const |
|
{ |
|
return IsAnyOf(_otype, _object_id::OBJ_BARRELEX, _object_id::OBJ_PODEX, _object_id::OBJ_URNEX); |
|
} |
|
|
|
/** |
|
* @brief Check if this object is a chest (or trapped chest). |
|
* |
|
* Trapped chests get their base type change in addition to having the trap flag set, but if they get "refilled" by |
|
* a Thaumaturgic shrine the base type is not reverted. This means you need to consider both the base type and the |
|
* trap flag to differentiate between chests that are currently trapped and chests which have never been trapped. |
|
* |
|
* @return True if the object is any of the chest types (see _object_id) |
|
*/ |
|
[[nodiscard]] constexpr bool IsChest() const |
|
{ |
|
return IsAnyOf(_otype, _object_id::OBJ_CHEST1, _object_id::OBJ_CHEST2, _object_id::OBJ_CHEST3, _object_id::OBJ_TCHEST1, _object_id::OBJ_TCHEST2, _object_id::OBJ_TCHEST3); |
|
} |
|
|
|
/** |
|
* @brief Check if this object is a trapped chest (specifically a chest which is currently trapped). |
|
* @return True if the object is one of the trapped chest types (see _object_id) and has an active trap. |
|
*/ |
|
[[nodiscard]] constexpr bool IsTrappedChest() const |
|
{ |
|
return IsAnyOf(_otype, _object_id::OBJ_TCHEST1, _object_id::OBJ_TCHEST2, _object_id::OBJ_TCHEST3) && _oTrapFlag; |
|
} |
|
|
|
/** |
|
* @brief Check if this object is an untrapped chest (specifically a chest which has not been trapped). |
|
* @return True if the object is one of the untrapped chest types (see _object_id) and has no active trap. |
|
*/ |
|
[[nodiscard]] constexpr bool IsUntrappedChest() const |
|
{ |
|
return IsAnyOf(_otype, _object_id::OBJ_CHEST1, _object_id::OBJ_CHEST2, _object_id::OBJ_CHEST3) && !_oTrapFlag; |
|
} |
|
|
|
/** |
|
* @brief Check if this object is a crucifix |
|
* @return True if the object is one of the crux types (see _object_id) |
|
*/ |
|
[[nodiscard]] constexpr bool IsCrux() const |
|
{ |
|
return IsAnyOf(_otype, _object_id::OBJ_CRUX1, _object_id::OBJ_CRUX2, _object_id::OBJ_CRUX3); |
|
} |
|
|
|
/** |
|
* @brief Check if this object is a door |
|
* @return True if the object is one of the door types (see _object_id) |
|
*/ |
|
[[nodiscard]] constexpr bool isDoor() const |
|
{ |
|
return IsAnyOf(_otype, _object_id::OBJ_L1LDOOR, _object_id::OBJ_L1RDOOR, _object_id::OBJ_L2LDOOR, _object_id::OBJ_L2RDOOR, _object_id::OBJ_L3LDOOR, _object_id::OBJ_L3RDOOR, _object_id::OBJ_L5LDOOR, _object_id::OBJ_L5RDOOR); |
|
} |
|
|
|
/** |
|
* @brief Check if this object is a shrine |
|
* @return True if the object is one of the shrine types (see _object_id) |
|
*/ |
|
[[nodiscard]] constexpr bool IsShrine() const |
|
{ |
|
return IsAnyOf(_otype, _object_id::OBJ_SHRINEL, _object_id::OBJ_SHRINER); |
|
} |
|
|
|
/** |
|
* @brief Check if this object is a trap source |
|
* @return True if the object is one of the trap types (see _object_id) |
|
*/ |
|
[[nodiscard]] constexpr bool IsTrap() const |
|
{ |
|
return IsAnyOf(_otype, _object_id::OBJ_TRAPL, _object_id::OBJ_TRAPR); |
|
} |
|
|
|
/** |
|
* @brief Returns the name of the object as shown in the info box |
|
*/ |
|
[[nodiscard]] StringOrView name() const; |
|
}; |
|
|
|
extern DVL_API_FOR_TEST Object Objects[MAXOBJECTS]; |
|
extern int AvailableObjects[MAXOBJECTS]; |
|
extern int ActiveObjects[MAXOBJECTS]; |
|
extern int ActiveObjectCount; |
|
extern bool ApplyObjectLighting; |
|
extern bool LoadingMapObjects; |
|
|
|
/** |
|
* @brief Find an object given a point in map coordinates |
|
* |
|
* @param position The map coordinate to test |
|
* @param considerLargeObjects Default behaviour will return a pointer to a large object that covers this tile, set |
|
* this param to false if you only want the object whose base position matches this tile |
|
* @return A pointer to the object or nullptr if no object exists at this location |
|
*/ |
|
Object *FindObjectAtPosition(Point position, bool considerLargeObjects = true); |
|
|
|
/** |
|
* @brief Check whether an item occupies this tile position |
|
* @param position The map coordinate to test |
|
* @return true if the tile is occupied |
|
*/ |
|
inline bool IsObjectAtPosition(Point position) |
|
{ |
|
return FindObjectAtPosition(position) != nullptr; |
|
} |
|
|
|
/** |
|
* @brief Get a reference to the object located at this tile |
|
* |
|
* This function is unchecked. Trying to access an invalid position will result in out of bounds memory access |
|
* @param position The map coordinate of the object |
|
* @return a reference to the object |
|
*/ |
|
inline Object &ObjectAtPosition(Point position) |
|
{ |
|
return Objects[abs(dObject[position.x][position.y]) - 1]; |
|
} |
|
|
|
/** |
|
* @brief Check whether an item blocking object (solid object or open door) is located at this tile position |
|
* @param position The map coordinate to test |
|
* @return true if the tile is blocked |
|
*/ |
|
bool IsItemBlockingObjectAtPosition(Point position); |
|
|
|
void InitObjectGFX(); |
|
void FreeObjectGFX(); |
|
void AddL1Objs(int x1, int y1, int x2, int y2); |
|
void AddL2Objs(int x1, int y1, int x2, int y2); |
|
void AddL3Objs(int x1, int y1, int x2, int y2); |
|
void AddCryptObjects(int x1, int y1, int x2, int y2); |
|
void InitObjects(); |
|
void SetMapObjects(const uint16_t *dunData, int startx, int starty); |
|
/** |
|
* @brief Spawns an object of the given type at the map coordinates provided |
|
* @param objType Type specifier |
|
* @param objPos tile coordinates |
|
*/ |
|
Object *AddObject(_object_id objType, Point objPos); |
|
bool UpdateTrapState(Object &trap); |
|
void OperateTrap(Object &trap); |
|
void ProcessObjects(); |
|
void RedoPlayerVision(); |
|
void MonstCheckDoors(const Monster &monster); |
|
void ObjChangeMap(int x1, int y1, int x2, int y2); |
|
void ObjChangeMapResync(int x1, int y1, int x2, int y2); |
|
_item_indexes ItemMiscIdIdx(item_misc_id imiscid); |
|
void OperateObject(Player &player, Object &object); |
|
void SyncOpObject(Player &player, int cmd, Object &object); |
|
void BreakObjectMissile(const Player *player, Object &object); |
|
void BreakObject(const Player &player, Object &object); |
|
void DeltaSyncOpObject(Object &object); |
|
void DeltaSyncBreakObj(Object &object); |
|
void SyncBreakObj(const Player &player, Object &object); |
|
void SyncObjectAnim(Object &object); |
|
/** |
|
* @brief Updates the text drawn in the info box to describe the given object |
|
* @param object The currently highlighted object |
|
*/ |
|
void GetObjectStr(const Object &object); |
|
void SyncNakrulRoom(); |
|
|
|
} // namespace devilution
|
|
|