/** * @file objects.h * * Interface of object functionality, interaction, spawning, loading, etc. */ #pragma once #include #include "engine/point.hpp" #include "engine/rectangle.hpp" #include "itemdat.h" #include "monster.h" #include "objdat.h" #include "textdat.h" #include "utils/attributes.h" namespace devilution { #define MAXOBJECTS 127 struct Object { _object_id _otype; Point position; bool _oLight; uint32_t _oAnimFlag; byte *_oAnimData; int _oAnimDelay; // Tick length of each frame in the current animation int _oAnimCnt; // Increases by one each game tick, counting how close we are to _pAnimDelay uint32_t _oAnimLen; // Number of frames in current animation uint32_t _oAnimFrame; // Current frame of animation. uint16_t _oAnimWidth; bool _oDelFlag; int8_t _oBreak; bool _oSolidFlag; /** True if the object allows missiles to pass through, false if it collides with missiles */ bool _oMissFlag; uint8_t _oSelFlag; bool _oPreFlag; bool _oTrapFlag; bool _oDoorFlag; int _olid; /** * 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; int _oVar1; int _oVar2; int _oVar3; int _oVar4; int _oVar5; uint32_t _oVar6; /** * @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; int _oVar8; /** * @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(Point topLeftPosition, Point 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(Rectangle mapRange) { SetMapRange(mapRange.position, mapRange.position + Displacement { 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(Rectangle 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(Rectangle mapRange, int leverID, _speech_id message) { InitializeBook(mapRange); _oVar8 = leverID; bookMessage = message; } /** * @brief Initializes this object as some form of door. Futher initialization of other game structures needs to be * performed separately, refer to the code in objects.cpp. */ constexpr void InitializeDoor() { _oDoorFlag = true; _oVar4 = 0; } /** * @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(Rectangle 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); } }; 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 *ObjectAtPosition(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 ObjectAtPosition(position) != nullptr; } /** * @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, Point start); /** * @brief Spawns an object of the given type at the map coordinates provided * @param objType Type specifier * @param objPos tile coordinates */ void AddObject(_object_id objType, Point objPos); void OperateTrap(Object &trap); void ProcessObjects(); void RedoPlayerVision(); void MonstCheckDoors(Monster &monster); void ObjChangeMap(int x1, int y1, int x2, int y2); void ObjChangeMapResync(int x1, int y1, int x2, int y2); void TryDisarm(int pnum, int i); int ItemMiscIdIdx(item_misc_id imiscid); void OperateObject(int pnum, int i, bool TeleFlag); void SyncOpObject(int pnum, int cmd, int i); void BreakObject(int pnum, Object &object); void SyncBreakObj(int pnum, 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 OperateNakrulLever(); void SyncNakrulRoom(); } // namespace devilution