/* * Copyright (C) 2002-2025 by the Widelands Development Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * */ #ifndef WL_LOGIC_MAP_OBJECTS_MAP_OBJECT_H #define WL_LOGIC_MAP_OBJECTS_MAP_OBJECT_H #include #include "base/macros.h" #include "commands/command.h" #include "graphic/animation/animation.h" #include "graphic/animation/diranimations.h" #include "graphic/color.h" #include "graphic/image.h" #include "logic/map_objects/info_to_draw.h" #include "logic/map_objects/map_object_type.h" #include "logic/map_objects/tribes/training_attribute.h" #include "logic/widelands.h" #include "notifications/signal.h" #include "scripting/lua_table.h" class RenderTarget; namespace Widelands { class MapObject; class Player; /** * Base class for descriptions of worker, files and so on. This must just * link them together */ class MapObjectDescr { public: using AttributeIndex = uint32_t; using Attributes = std::vector; enum class OwnerType { kWorld, kTribe }; MapObjectDescr(MapObjectType init_type, const std::string& init_name, const std::string& init_descname); MapObjectDescr(MapObjectType init_type, const std::string& init_name, const std::string& init_descname, const LuaTable& table); virtual ~MapObjectDescr(); [[nodiscard]] const std::string& name() const { return name_; } [[nodiscard]] const std::string& descname() const { return descname_; } // Type of the MapObjectDescr. [[nodiscard]] MapObjectType type() const { return type_; } virtual uint32_t get_animation(const std::string& animname, const MapObject* mo) const; [[nodiscard]] uint32_t main_animation() const; [[nodiscard]] std::string get_animation_name(uint32_t) const; ///< needed for save, debug [[nodiscard]] bool is_animation_known(const std::string& name) const; /// Preload animation graphics at default scale void load_graphics() const; /// Returns the image for the first frame of the idle animation if the MapObject has animations, /// nullptr otherwise const Image* representative_image(const RGBColor* player_color = nullptr) const; /// Returns the menu image if the MapObject has one, nullptr otherwise [[nodiscard]] const Image* icon() const; /// Returns the image fileneme for the menu image if the MapObject has one, is empty otherwise [[nodiscard]] const std::string& icon_filename() const; [[nodiscard]] bool has_attribute(AttributeIndex) const; [[nodiscard]] const MapObjectDescr::Attributes& attributes() const; static AttributeIndex get_attribute_id(const std::string& name, bool add_if_not_exists = false); /// Sets a tribe-specific ware or immovable helptext for this MapObject void set_helptexts(const std::string& tribename, std::map localized_helptext); /// Gets the tribe-specific ware or immovable helptext for the given tribe. Fails if it doesn't /// exist. [[nodiscard]] const std::map& get_helptexts(const std::string& tribename) const; /// Returns whether a tribe-specific helptext exists for the given tribe [[nodiscard]] bool has_helptext(const std::string& tribename) const; protected: // Add attributes to the attribute list void add_attributes(const std::vector& attribs); void add_attribute(AttributeIndex attr); /// Sets the directional animations in 'anims' with the animations /// '<basename>_(ne|e|se|sw|w|nw)'. void assign_directional_animation(DirAnimations* anims, const std::string& basename) const; private: void add_animations(const LuaTable& table, const std::string& animation_directory, Animation::Type anim_type); /// Throws an exception if the MapObjectDescr has no representative image void check_representative_image() const; using Anims = std::map; static std::map attribute_names_; Attributes attribute_ids_; const MapObjectType type_; /// Subclasses pick from the enum above std::string const name_; /// The name for internal reference std::string const descname_; /// A localized Descriptive name /// Tribe-specific helptexts. Format: > std::map> helptexts_; Anims anims_; std::string icon_filename_; // Filename for the menu icon DISALLOW_COPY_AND_ASSIGN(MapObjectDescr); }; /** * \par Notes on MapObject * * MapObject is the base class for everything that can be on the map: * buildings, animals, decorations, etc... most of the time, however, you'll * deal with one of the derived classes, BaseImmovable or Bob. * * Every MapObject has a unique serial number. This serial number is used as * key in the ObjectManager map, and in the safe ObjectPointer. * * Unless you're perfectly sure about when an object can be destroyed you * should use an ObjectPointer or, better yet, the type safe OPtr template. * This is not necessary when the relationship and lifetime between objects * is well-defined, such as in the relationship between Building and Flag. * * MapObjects can also have attributes. They are mainly useful for finding * objects of a given type (e.g. trees) within a certain radius. * * \warning DO NOT allocate/free MapObjects directly. Use the appropriate * type-dependent create() function for creation, and call die() for removal. * * \note Convenient creation functions are defined in class Game. * * When you do create a new object yourself (i.e. when you're implementing one * of the create() functions), you need to allocate the object using new, * potentially set it up by calling basic functions like set_position(), etc. * and then call init(). After that, the object is supposed to * be fully created. */ /// If you find a better way to do this that doesn't cost a virtual function /// or additional member variable, go ahead #define MO_DESCR(type) \ public: \ const type& descr() const { \ return dynamic_cast(*descr_); \ } class MapObject { friend struct ObjectManager; friend struct ObjectPointer; MO_DESCR(MapObjectDescr) public: virtual ~MapObject() = default; struct LogSink { virtual void log(const std::string& str) = 0; virtual ~LogSink() = default; }; virtual void load_finish(EditorGameBase&) { } virtual void postload(EditorGameBase&) { } virtual const Image* representative_image() const; protected: explicit MapObject(MapObjectDescr const* descr); public: Serial serial() const { return serial_; } /** * Is called right before the object will be removed from * the game. No connection is handled in this class. * * param serial : the object serial (cannot use param comment as this is a callback) */ Notifications::Signal removed; /** * Attributes are fixed boolean properties of an object. * An object either has a certain attribute or it doesn't. * See the \ref Attribute enume. * * \return whether this object has the given attribute */ bool has_attribute(uint32_t const attr) const { return descr().has_attribute(attr); } /** * \return the value of the given \ref TrainingAttribute. -1 if this object * doesn't have this kind of attribute. * The default behaviour returns \c -1 for all attributes. */ virtual int32_t get_training_attribute(TrainingAttribute attr) const; void remove(EditorGameBase&); virtual void destroy(EditorGameBase&); // The next functions are really only needed in games, not in the editor. void schedule_destroy(Game&); Time schedule_act(Game&, const Duration& tdelta, uint32_t data = 0); virtual void act(Game&, uint32_t data); LogSink* get_logsink() { return logsink_; } void set_logsink(LogSink*); /// Called when a new logsink is set. Used to give general information. virtual void log_general_info(const EditorGameBase&) const; Player* get_owner() const { return owner_; } const Player& owner() const; // Header bytes to distinguish between data packages for the different // MapObject classes. Be careful in changing those, since they are written // to files. enum { HeaderMapObject = 1, HeaderImmovable = 2, // 3 was battle object. // 4 was attack controller. HeaderBattle = 5, HeaderCritter = 6, HeaderWorker = 7, HeaderWareInstance = 8, HeaderShip = 9, HeaderPortDock = 10, HeaderShipFleet = 11, HeaderFerryFleet = 12, HeaderPinnedNote = 13, HeaderShipFleetInterface = 14, HeaderFerryFleetInterface = 15, HeaderNavalInvasionBase = 16, }; /** * Returns whether this immovable was reserved by a worker. */ bool is_reserved_by_worker() const; /** * Change whether this immovable is marked as reserved by a worker. */ void set_reserved_by_worker(bool reserve); /** * Static load functions of derived classes will return a pointer to * a Loader class. The caller needs to call the virtual functions * \ref load for all instances loaded that way, after that call * \ref load_pointers for all instances loaded that way and finally * call \ref load_finish for all instances loaded that way. * Those are the three phases of loading. After the last phase, * all Loader objects should be deleted. */ struct Loader { EditorGameBase* egbase_{nullptr}; MapObjectLoader* mol_{nullptr}; MapObject* object_{nullptr}; protected: Loader() = default; public: virtual ~Loader() = default; void init(EditorGameBase& e, MapObjectLoader& m, MapObject& object) { egbase_ = &e; mol_ = &m; object_ = &object; } [[nodiscard]] EditorGameBase& egbase() const { return *egbase_; } [[nodiscard]] MapObjectLoader& mol() const { return *mol_; } [[nodiscard]] MapObject* get_object() const { return object_; } template T& get() { return dynamic_cast(*object_); } protected: void load(FileRead&) const; public: virtual void load_pointers(); virtual void load_finish(); }; /// This is just a fail-safe guard for the time until we fully transition /// to the new MapObject saving system virtual bool has_new_save_support() { return false; } virtual void save(EditorGameBase&, MapObjectSaver&, FileWrite&); // Pure MapObjects cannot be loaded protected: /// Called only when the oject is logically created in the simulation. If /// called again, such as when the object is loaded from a savegame, it will /// cause bugs. virtual bool init(EditorGameBase&); virtual void cleanup(EditorGameBase&); /// Draws census and statistics on screen void do_draw_info(const InfoToDraw& info_to_draw, const std::string& census, const std::string& statictics, const Vector2f& field_on_dst, float scale, RenderTarget* dst) const; void molog(const Time& gametime, char const* fmt, ...) const PRINTF_FORMAT(3, 4); const MapObjectDescr* descr_; Serial serial_{0U}; LogSink* logsink_{nullptr}; std::atomic owner_{nullptr}; /** * MapObjects like trees are reserved by a worker that is walking * towards them, so that e.g. two lumberjacks don't attempt to * work on the same tree simultaneously or two hunters try to hunt * the same animal. */ bool reserved_by_worker_{false}; private: DISALLOW_COPY_AND_ASSIGN(MapObject); }; inline int32_t get_reverse_dir(int32_t const dir) { return 1 + ((dir - 1) + 3) % 6; } /** * * Keeps the list of all objects currently in the game. */ struct ObjectManager { using MapObjectMap = std::unordered_map; ObjectManager() = default; ~ObjectManager(); void cleanup(EditorGameBase&); MapObject* get_object(Serial const serial) const { const MapObjectMap::const_iterator it = objects_.find(serial); return it != objects_.end() ? it->second : nullptr; } void insert(MapObject*); void remove(MapObject&); /** * When saving the map object, ordere matters. Return a vector of all ids * that are currently available; */ std::vector all_object_serials_ordered() const; bool is_cleaning_up() const { return is_cleaning_up_; } private: Serial lastserial_{0U}; MapObjectMap objects_; bool is_cleaning_up_{false}; DISALLOW_COPY_AND_ASSIGN(ObjectManager); }; /** * Provides a safe pointer to a MapObject * Make sure the MapObject is initialized (has a serial) before using it in ObjectPointer! */ struct ObjectPointer { // Provide default constructor to shut up cppcheck. ObjectPointer() { serial_ = 0; } ObjectPointer(const MapObject* const obj) { // NOLINT allow implicit conversion assert(obj == nullptr || obj->serial_ != 0); serial_ = obj != nullptr ? obj->serial_ : 0; } // can use standard copy constructor and assignment operator ObjectPointer& operator=(const MapObject* const obj) { assert(obj == nullptr || obj->serial_ != 0); serial_ = obj != nullptr ? obj->serial_ : 0; return *this; } [[nodiscard]] bool is_set() const { return serial_ != 0u; } // TODO(unknown): dammit... without an EditorGameBase object, we can't implement a // MapObject* operator (would be really nice) MapObject* get(const EditorGameBase&); [[nodiscard]] MapObject* get(const EditorGameBase& egbase) const; bool operator<(const ObjectPointer& other) const { return serial_ < other.serial_; } bool operator==(const ObjectPointer& other) const { return serial_ == other.serial_; } bool operator!=(const ObjectPointer& other) const { return serial_ != other.serial_; } [[nodiscard]] uint32_t serial() const { return serial_; } private: uint32_t serial_; }; template struct OPtr { OPtr(T* const obj = nullptr) : m(obj) { // NOLINT allow implicit conversion } OPtr& operator=(T* const obj) { m = obj; return *this; } [[nodiscard]] bool is_set() const { return m.is_set(); } T* get(const EditorGameBase& egbase) { return static_cast(m.get(egbase)); } [[nodiscard]] T* get(const EditorGameBase& egbase) const { return static_cast(m.get(egbase)); } bool operator<(const OPtr& other) const { return m < other.m; } bool operator==(const OPtr& other) const { return m == other.m; } bool operator!=(const OPtr& other) const { return m != other.m; } [[nodiscard]] Serial serial() const { return m.serial(); } private: ObjectPointer m; }; } // namespace Widelands #endif // end of include guard: WL_LOGIC_MAP_OBJECTS_MAP_OBJECT_H