From 5a15d3ddf9f720bdf32856bc81ee56b211f00978 Mon Sep 17 00:00:00 2001 From: Luis Michaelis Date: Sat, 18 Nov 2023 15:41:12 +0100 Subject: [PATCH] refactor(Archive): initial implementation of spec-compliance This patch adapts the implementation of `ReadArchive` to properly support object references in archives. To do this, I added a new `Object` class which represents any ZenGin object and a new `read_object` API to `ReadArchive` which facilitates loading of a set of pre-defined ZenGin object types. This new API takes care of creating new object instances and resolving archive-internal references. This required changing the `VirtualObject` hierarchy to use `shared_ptr` instead of `unique_ptr` so the system is able to share references to archive objects without sacrificing memory safety. --- CMakeLists.txt | 1 + include/zenkit/Archive.hh | 20 +- include/zenkit/Object.hh | 80 +++++++ include/zenkit/World.hh | 62 ++++- include/zenkit/vobs/Camera.hh | 12 +- include/zenkit/vobs/Light.hh | 2 +- include/zenkit/vobs/Misc.hh | 57 +++-- include/zenkit/vobs/MovableObject.hh | 10 +- include/zenkit/vobs/Sound.hh | 4 +- include/zenkit/vobs/Trigger.hh | 16 +- include/zenkit/vobs/VirtualObject.hh | 157 +++++++----- include/zenkit/vobs/Zone.hh | 6 +- include/zenkit/world/BspTree.hh | 1 - include/zenkit/world/VobTree.hh | 5 +- include/zenkit/world/WayNet.hh | 1 - src/Archive.cc | 344 ++++++++++++++++++++++++++- src/Object.cc | 5 + src/World.cc | 155 +++++------- src/archive/ArchiveBinsafe.cc | 19 +- src/vobs/Camera.cc | 19 +- src/vobs/Misc.cc | 74 +----- src/vobs/MovableObject.cc | 17 +- src/vobs/VirtualObject.cc | 132 ++++------ src/world/VobTree.cc | 198 +-------------- 24 files changed, 808 insertions(+), 589 deletions(-) create mode 100644 include/zenkit/Object.hh create mode 100644 src/Object.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index b0e8cfdc..f0d8bc77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ list(APPEND _ZK_SOURCES src/ModelScriptDsl.cc src/MorphMesh.cc src/MultiResolutionMesh.cc + src/Object.cc src/SaveGame.cc src/SoftSkinMesh.cc src/Stream.cc diff --git a/include/zenkit/Archive.hh b/include/zenkit/Archive.hh index 97525ba1..8f17947d 100644 --- a/include/zenkit/Archive.hh +++ b/include/zenkit/Archive.hh @@ -3,6 +3,7 @@ #pragma once #include "zenkit/Boxes.hh" #include "zenkit/Library.hh" +#include "zenkit/Object.hh" #include "zenkit/Stream.hh" #include "phoenix/buffer.hh" @@ -11,9 +12,7 @@ #include #include -#include #include -#include #include #include @@ -23,6 +22,7 @@ namespace phoenix { namespace zenkit { class Read; + class ReadArchive; enum class ArchiveFormat { BINARY = 0, @@ -145,6 +145,19 @@ namespace zenkit { /// \throws zenkit::ParserError static std::unique_ptr from(Read* r); + template + std::enable_if_t, std::shared_ptr> // + read_object(GameVersion version) { + auto obj = this->read_object(version); + if (obj != nullptr && obj->get_type() != T::TYPE) { + throw ParserError {"ReadArchive", "Read unexcected object!"}; + } + + return std::reinterpret_pointer_cast(std::move(obj)); + } + + std::shared_ptr read_object(GameVersion version); + /// \brief Tries to read the begin of a new object from the archive. /// /// If a beginning of an object could not be read, the internal buffer is reverted to the state @@ -240,7 +253,7 @@ namespace zenkit { } /// \return Whether or not this archive represents a save-game. - [[nodiscard]] inline bool is_save_game() const noexcept { + [[nodiscard]] bool is_save_game() const noexcept { return header.save; } @@ -259,6 +272,7 @@ namespace zenkit { Read* read; private: + std::unordered_map> _m_cache {}; std::unique_ptr _m_owned; }; } // namespace zenkit diff --git a/include/zenkit/Object.hh b/include/zenkit/Object.hh new file mode 100644 index 00000000..fe9514a8 --- /dev/null +++ b/include/zenkit/Object.hh @@ -0,0 +1,80 @@ +// Copyright © 2023 GothicKit Contributors. +// SPDX-License-Identifier: MIT +#pragma once +#include "Misc.hh" + +namespace zenkit { + class ReadArchive; + + enum class ObjectType { + zCVob = 0, ///< The base type for all VObs. + zCVobLevelCompo = 1, ///< A basic VOb used for grouping other VObs. + oCItem = 2, ///< A VOb representing an item + oCNpc = 3, ///< A VOb representing an NPC + zCMoverController = 4, + zCVobScreenFX = 5, + zCVobStair = 6, + zCPFXController = 7, + zCVobAnimate = 8, + zCVobLensFlare = 9, + zCVobLight = 10, + zCVobSpot = 11, + zCVobStartpoint = 12, + zCMessageFilter = 13, + zCCodeMaster = 14, + zCTriggerWorldStart = 15, + zCCSCamera = 16, + zCCamTrj_KeyFrame = 17, + oCTouchDamage = 18, + zCTriggerUntouch = 19, + zCEarthquake = 20, + oCMOB = 21, ///< The base VOb type used for dynamic world objects. + oCMobInter = 22, ///< The base VOb type used for interactive world objects. + oCMobBed = 23, ///< A bed the player can sleep in. + oCMobFire = 24, ///< A campfire the player can cook things on. + oCMobLadder = 25, ///< A ladder the player can climb. + oCMobSwitch = 26, ///< A switch or button the player can use. + oCMobWheel = 27, ///< A grindstone the player can sharpen their weapon with. + oCMobContainer = 28, ///< A container the player can open. + oCMobDoor = 29, ///< A door the player can open. + zCTrigger = 30, ///< The base VOb type used for all kinds of triggers. + zCTriggerList = 31, ///< A collection of multiple triggers. + oCTriggerScript = 32, ///< A trigger for calling a script function. + oCTriggerChangeLevel = 33, ///< A trigger for changing the game world. + oCCSTrigger = 34, ///< A cutscene trigger. + zCMover = 35, + zCVobSound = 36, ///< A VOb which emits a certain sound. + zCVobSoundDaytime = 37, ///< A VOb which emits a sound only during a specified time. + oCZoneMusic = 38, ///< A VOb which plays music from the soundtrack. + oCZoneMusicDefault = 39, + zCZoneZFog = 40, ///< A VOb which indicates a foggy area. + zCZoneZFogDefault = 41, + zCZoneVobFarPlane = 42, + zCZoneVobFarPlaneDefault = 43, + ignored = 44, + unknown = 45, + + oCNpcTalent, + zCEventManager, + zCDecal, + zCMesh, + zCProgMeshProto, + zCParticleFX, + zCAICamera, + zCModel, + zCMorphMesh, + oCAIHuman, + oCAIVobMove, + oCCSPlayer, + zCSkyControler_Outdoor + }; + + class Object ZKAPI { + public: + Object() = default; + virtual ~Object() noexcept = default; + [[nodiscard]] virtual ObjectType get_type() const = 0; + + virtual void load(ReadArchive& r, GameVersion version); + }; +} // namespace zenkit diff --git a/include/zenkit/World.hh b/include/zenkit/World.hh index e86a82fd..045fc13c 100644 --- a/include/zenkit/World.hh +++ b/include/zenkit/World.hh @@ -17,6 +17,54 @@ namespace phoenix { } namespace zenkit { + namespace vobs { + struct Npc; + } + + struct CutscenePlayer : Object { + static constexpr ObjectType TYPE = ObjectType::oCCSPlayer; + + int32_t last_process_day; + int32_t last_process_hour; + int32_t play_list_count; + + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + + void load(ReadArchive& r, GameVersion version) override; + }; + + struct SkyController : Object { + static constexpr ObjectType TYPE = ObjectType::zCSkyControler_Outdoor; + + float master_time; + float rain_weight; + float rain_start; + float rain_stop; + float rain_sct_timer; + float rain_snd_vol; + float day_ctr; + + // Only relevant for G1: + float fade_scale; + bool render_lightning; + bool is_raining; + int rain_ctr; + + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + + void load(ReadArchive& r, GameVersion version) override; + }; + + struct SpawnLocation { + std::shared_ptr npc; + glm::vec3 position; + float timer; + }; + /// \brief Represents a ZenGin world. class World { public: @@ -76,7 +124,7 @@ namespace zenkit { public: /// \brief The list of VObs defined in this world. - std::vector> world_vobs; + std::vector> world_vobs; /// \brief The mesh of the world. Mesh world_mesh; @@ -86,5 +134,17 @@ namespace zenkit { /// \brief The way-net of this world. WayNet world_way_net; + + // \note Only available in save-games, otherwise empty. + std::vector> npcs {}; + std::vector npc_spawns {}; + bool npc_spawn_enabled = false; + int npc_spawn_flags = 0; + + // \note Only available in save-games, otherwise null. + std::shared_ptr player; + + // \note Only available in save-games, otherwise null. + std::shared_ptr sky_controller; }; } // namespace zenkit diff --git a/include/zenkit/vobs/Camera.hh b/include/zenkit/vobs/Camera.hh index 811d48d9..d6c5975a 100644 --- a/include/zenkit/vobs/Camera.hh +++ b/include/zenkit/vobs/Camera.hh @@ -69,7 +69,9 @@ namespace zenkit { namespace vobs { /// \brief A VOb which describes the trajectory of a camera during a cutscene. - struct CameraTrajectoryFrame : public VirtualObject { + struct CameraTrajectoryFrame : VirtualObject { + static constexpr ObjectType TYPE = ObjectType::zCCamTrj_KeyFrame; + float time; float roll_angle; float fov_scale; @@ -93,11 +95,15 @@ namespace zenkit { ZKREM("use ::load()") ZKAPI static std::unique_ptr parse(ReadArchive& ctx, GameVersion version); + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + ZKAPI void load(ReadArchive& r, GameVersion version) override; }; /// \brief A VOb which defined the movement of the camera during a cutscene. - struct CutsceneCamera : public VirtualObject { + struct CutsceneCamera : VirtualObject { CameraTrajectory trajectory_for; CameraTrajectory target_trajectory_for; CameraLoop loop_mode; @@ -115,7 +121,7 @@ namespace zenkit { std::int32_t position_count; std::int32_t target_count; - std::vector> frames; + std::vector> frames; // Save-game only variables bool s_paused {false}; diff --git a/include/zenkit/vobs/Light.hh b/include/zenkit/vobs/Light.hh index 2d0dda4f..721c5ccc 100644 --- a/include/zenkit/vobs/Light.hh +++ b/include/zenkit/vobs/Light.hh @@ -80,7 +80,7 @@ namespace zenkit { }; /// \brief A VOb which acts as a light source. - struct Light : public VirtualObject, public LightPreset { + struct Light : VirtualObject, LightPreset { /// \brief Parses a light VOb the given *ZenGin* archive. /// \param[out] obj The object to read. /// \param[in,out] ctx The archive reader to read from. diff --git a/include/zenkit/vobs/Misc.hh b/include/zenkit/vobs/Misc.hh index 74e2c057..2daf1bb7 100644 --- a/include/zenkit/vobs/Misc.hh +++ b/include/zenkit/vobs/Misc.hh @@ -58,7 +58,7 @@ namespace zenkit { namespace vobs { /// \brief An animated VOb. - struct Animate : public VirtualObject { + struct Animate : VirtualObject { bool start_on {false}; // Save-game only variables @@ -76,7 +76,9 @@ namespace zenkit { }; /// \brief A VOb representing an in-game item. - struct Item : public VirtualObject { + struct Item : VirtualObject { + static constexpr ObjectType TYPE = ObjectType::oCItem; + std::string instance; // Save-game only variables @@ -95,7 +97,7 @@ namespace zenkit { }; /// \brief A VOb representing a [lens flare](https://en.wikipedia.org/wiki/Lens_flare). - struct LensFlare : public VirtualObject { + struct LensFlare : VirtualObject { std::string fx; /// \brief Parses a lens flare VOb the given *ZenGin* archive. @@ -110,7 +112,7 @@ namespace zenkit { }; /// \brief A VOb representing a particle system controller. - struct ParticleEffectController : public VirtualObject { + struct ParticleEffectController : VirtualObject { std::string pfx_name; bool kill_when_done; bool initially_running; @@ -127,7 +129,7 @@ namespace zenkit { ZKAPI void load(ReadArchive& r, GameVersion version) override; }; - struct MessageFilter : public VirtualObject { + struct MessageFilter : VirtualObject { std::string target; MessageFilterAction on_trigger; MessageFilterAction on_untrigger; @@ -143,7 +145,7 @@ namespace zenkit { ZKAPI void load(ReadArchive& r, GameVersion version) override; }; - struct CodeMaster : public VirtualObject { + struct CodeMaster : VirtualObject { std::string target; bool ordered; bool first_false_is_failure; @@ -165,7 +167,7 @@ namespace zenkit { ZKAPI void load(ReadArchive& r, GameVersion version) override; }; - struct MoverController : public VirtualObject { + struct MoverController : VirtualObject { std::string target; MoverMessageType message; std::int32_t key; @@ -182,7 +184,7 @@ namespace zenkit { }; /// \brief A VOb which represents a damage source. - struct TouchDamage : public VirtualObject { + struct TouchDamage : VirtualObject { float damage; bool barrier; @@ -210,7 +212,7 @@ namespace zenkit { }; /// \brief A VOb which represents an earthquake-like effect. - struct Earthquake : public VirtualObject { + struct Earthquake : VirtualObject { float radius; float duration; glm::vec3 amplitude; @@ -226,18 +228,27 @@ namespace zenkit { ZKAPI void load(ReadArchive& r, GameVersion version) override; }; - struct Npc : public VirtualObject { - static const std::uint32_t attribute_count = 8; - static const std::uint32_t hcs_count = 4; - static const std::uint32_t missions_count = 5; - static const std::uint32_t aivar_count = 100; - static const std::uint32_t packed_count = 9; - static const std::uint32_t protection_count = 8; + struct Npc : VirtualObject { + static constexpr ObjectType TYPE = ObjectType::oCNpc; + static std::uint32_t const attribute_count = 8; + static std::uint32_t const hcs_count = 4; + static std::uint32_t const missions_count = 5; + static std::uint32_t const aivar_count = 100; + static std::uint32_t const packed_count = 9; + static std::uint32_t const protection_count = 8; + + struct Talent : Object { + static constexpr ObjectType TYPE = ObjectType::oCNpcTalent; - struct Talent { int talent; int value; int skill; + + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + + void load(ReadArchive& r, GameVersion version) override; }; using talent ZKREM("renamed to Talent") = Talent; @@ -245,7 +256,7 @@ namespace zenkit { struct Slot { bool used; std::string name; - Item* item {}; + std::shared_ptr item {}; bool in_inventory; }; @@ -265,7 +276,7 @@ namespace zenkit { int xp_next_level; int lp; - std::vector talents; + std::vector> talents; int fight_tactic; int fight_mode; @@ -287,7 +298,7 @@ namespace zenkit { bool move_lock; std::string packed[packed_count]; - std::vector> items; + std::vector> items; std::vector slots; bool current_state_valid; @@ -328,10 +339,14 @@ namespace zenkit { /// \see vob::parse ZKREM("use ::load()") ZKAPI static void parse(Npc& obj, ReadArchive& ctx, GameVersion version); + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + ZKAPI void load(ReadArchive& r, GameVersion version) override; }; - struct ScreenEffect : public VirtualObject { + struct ScreenEffect : VirtualObject { ZKAPI void load(ReadArchive& r, GameVersion version) override; }; } // namespace vobs diff --git a/include/zenkit/vobs/MovableObject.hh b/include/zenkit/vobs/MovableObject.hh index 7e6a10ed..58059fbc 100644 --- a/include/zenkit/vobs/MovableObject.hh +++ b/include/zenkit/vobs/MovableObject.hh @@ -31,7 +31,7 @@ namespace zenkit { }; namespace vobs { - struct MovableObject : public VirtualObject { + struct MovableObject : VirtualObject { std::string name; std::int32_t hp; std::int32_t damage; @@ -55,7 +55,7 @@ namespace zenkit { ZKAPI void load(ReadArchive& r, GameVersion version) override; }; - struct InteractiveObject : public MovableObject { + struct InteractiveObject : MovableObject { std::int32_t state; std::string target; std::string item; @@ -76,7 +76,7 @@ namespace zenkit { }; /// \brief A VOb representing a campfire. - struct Fire : public InteractiveObject { + struct Fire : InteractiveObject { std::string slot; std::string vob_tree; @@ -92,14 +92,14 @@ namespace zenkit { }; /// \brief A VOb representing a container. - struct Container : public InteractiveObject { + struct Container : InteractiveObject { bool locked; std::string key; std::string pick_string; std::string contents; // Save-game only variables - std::vector> s_items; + std::vector> s_items; /// \brief Parses a container VOb the given *ZenGin* archive. /// \param[out] obj The object to read. diff --git a/include/zenkit/vobs/Sound.hh b/include/zenkit/vobs/Sound.hh index 2951f95c..9765b0e1 100644 --- a/include/zenkit/vobs/Sound.hh +++ b/include/zenkit/vobs/Sound.hh @@ -38,7 +38,7 @@ namespace zenkit { namespace vobs { /// \brief A VOb which emits a sound. - struct Sound : public VirtualObject { + struct Sound : VirtualObject { float volume {0}; SoundMode mode {SoundMode::ONCE}; float random_delay {0}; @@ -67,7 +67,7 @@ namespace zenkit { }; /// \brief A VOb which emits a sound only during certain times of the day. - struct SoundDaytime : public Sound { + struct SoundDaytime : Sound { float start_time {0}; float end_time {0}; std::string sound_name2 {}; diff --git a/include/zenkit/vobs/Trigger.hh b/include/zenkit/vobs/Trigger.hh index 5c442a58..6bd0124e 100644 --- a/include/zenkit/vobs/Trigger.hh +++ b/include/zenkit/vobs/Trigger.hh @@ -69,7 +69,7 @@ namespace zenkit { namespace vobs { /// \brief A basic trigger VOb which does something upon the player interacting with it. - struct Trigger : public VirtualObject { + struct Trigger : VirtualObject { std::string target; std::uint8_t flags; std::uint8_t filter_flags; @@ -97,7 +97,7 @@ namespace zenkit { }; /// \brief A VOb which can move upon player interaction. - struct Mover : public Trigger { + struct Mover : Trigger { MoverBehavior behavior {MoverBehavior::TOGGLE}; float touch_blocker_damage {0}; float stay_open_time_sec {0}; @@ -143,12 +143,12 @@ namespace zenkit { }; /// \brief A VOb which can call multiple script function upon being triggered. - struct TriggerList : public Trigger { + struct TriggerList : Trigger { struct Target { std::string name {}; float delay {}; - [[nodiscard]] inline bool operator==(Target const& tgt) const noexcept { + [[nodiscard]] bool operator==(Target const& tgt) const noexcept { return this->name == tgt.name && this->delay == tgt.delay; } }; @@ -174,7 +174,7 @@ namespace zenkit { }; /// \brief A VOb which calls a script function upon being triggered. - struct TriggerScript : public Trigger { + struct TriggerScript : Trigger { std::string function {}; /// \brief Parses a script trigger VOb the given *ZenGin* archive. @@ -189,7 +189,7 @@ namespace zenkit { }; /// \brief A VOb which triggers a level change if the player moves close to it. - struct TriggerChangeLevel : public Trigger { + struct TriggerChangeLevel : Trigger { std::string level_name {}; std::string start_vob {}; @@ -206,7 +206,7 @@ namespace zenkit { }; /// \brief A VOb which triggers a world start event. - struct TriggerWorldStart : public VirtualObject { + struct TriggerWorldStart : VirtualObject { std::string target; bool fire_once; @@ -224,7 +224,7 @@ namespace zenkit { ZKAPI void load(ReadArchive& r, GameVersion version) override; }; - struct TriggerUntouch : public VirtualObject { + struct TriggerUntouch : VirtualObject { std::string target; /// \brief Parses an untouch trigger VOb the given *ZenGin* archive. diff --git a/include/zenkit/vobs/VirtualObject.hh b/include/zenkit/vobs/VirtualObject.hh index 5b412cd0..0288a6df 100644 --- a/include/zenkit/vobs/VirtualObject.hh +++ b/include/zenkit/vobs/VirtualObject.hh @@ -5,6 +5,7 @@ #include "zenkit/Library.hh" #include "zenkit/Material.hh" #include "zenkit/Misc.hh" +#include "zenkit/Object.hh" #include #include @@ -19,57 +20,6 @@ namespace zenkit { class ReadArchive; - /// \brief All possible VOb types. - /// \summary Mostly copied from [ZenLib](https://github.com/Try/ZenLib). - enum class VobType : std::uint8_t { - zCVob = 0, ///< The base type for all VObs. - zCVobLevelCompo = 1, ///< A basic VOb used for grouping other VObs. - oCItem = 2, ///< A VOb representing an item - oCNpc = 3, ///< A VOb representing an NPC - zCMoverController = 4, - zCVobScreenFX = 5, - zCVobStair = 6, - zCPFXController = 7, - zCVobAnimate = 8, - zCVobLensFlare = 9, - zCVobLight = 10, - zCVobSpot = 11, - zCVobStartpoint = 12, - zCMessageFilter = 13, - zCCodeMaster = 14, - zCTriggerWorldStart = 15, - zCCSCamera = 16, - zCCamTrj_KeyFrame = 17, - oCTouchDamage = 18, - zCTriggerUntouch = 19, - zCEarthquake = 20, - oCMOB = 21, ///< The base VOb type used for dynamic world objects. - oCMobInter = 22, ///< The base VOb type used for interactive world objects. - oCMobBed = 23, ///< A bed the player can sleep in. - oCMobFire = 24, ///< A campfire the player can cook things on. - oCMobLadder = 25, ///< A ladder the player can climb. - oCMobSwitch = 26, ///< A switch or button the player can use. - oCMobWheel = 27, ///< A grindstone the player can sharpen their weapon with. - oCMobContainer = 28, ///< A container the player can open. - oCMobDoor = 29, ///< A door the player can open. - zCTrigger = 30, ///< The base VOb type used for all kinds of triggers. - zCTriggerList = 31, ///< A collection of multiple triggers. - oCTriggerScript = 32, ///< A trigger for calling a script function. - oCTriggerChangeLevel = 33, ///< A trigger for changing the game world. - oCCSTrigger = 34, ///< A cutscene trigger. - zCMover = 35, - zCVobSound = 36, ///< A VOb which emits a certain sound. - zCVobSoundDaytime = 37, ///< A VOb which emits a sound only during a specified time. - oCZoneMusic = 38, ///< A VOb which plays music from the soundtrack. - oCZoneMusicDefault = 39, - zCZoneZFog = 40, ///< A VOb which indicates a foggy area. - zCZoneZFogDefault = 41, - zCZoneVobFarPlane = 42, - zCZoneVobFarPlaneDefault = 43, - ignored = 44, - unknown = 45, - }; - /// \brief Ways a VOb can cast shadows. enum class ShadowType : std::uint8_t { NONE = 0, ///< The VOb does not cast any shadow. @@ -127,7 +77,9 @@ namespace zenkit { }; /// \brief Decal visual configuration for VObs. - struct Decal { + struct VisualDecal : Object { + static constexpr ObjectType TYPE = ObjectType::zCDecal; + std::string name {}; glm::vec2 dimension {}; glm::vec2 offset {}; @@ -142,11 +94,57 @@ namespace zenkit { /// \note After this function returns the position of \p ctx will be at the end of the parsed object. /// \return The parsed decal. /// \throws ParserError if parsing fails. - ZKREM("use ::load()") ZKAPI static Decal parse(ReadArchive& ctx, GameVersion version); + ZKREM("use ::load()") ZKAPI static VisualDecal parse(ReadArchive& ctx, GameVersion version); + + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } ZKAPI void load(ReadArchive& r, GameVersion version); }; + struct VisualMesh : Object { + static constexpr ObjectType TYPE = ObjectType::zCMesh; + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + }; + + struct VisualMultiResolutionMesh : Object { + static constexpr ObjectType TYPE = ObjectType::zCProgMeshProto; + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + }; + + struct VisualParticleEffect : Object { + static constexpr ObjectType TYPE = ObjectType::zCParticleFX; + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + }; + + struct VisualCamera : Object { + static constexpr ObjectType TYPE = ObjectType::zCAICamera; + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + }; + + struct VisualModel : Object { + static constexpr ObjectType TYPE = ObjectType::zCModel; + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + }; + + struct VisualMorphMesh : Object { + static constexpr ObjectType TYPE = ObjectType::zCMorphMesh; + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + }; + struct RigidBody { glm::vec3 vel; std::uint8_t mode; @@ -157,17 +155,41 @@ namespace zenkit { ZKAPI void load(ReadArchive& r, GameVersion version); }; - struct EventManager { + struct EventManager : Object { + static constexpr ObjectType TYPE = ObjectType::ignored; + bool cleared; bool active; + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + ZKAPI void load(ReadArchive& r, GameVersion version); }; + struct AiHuman : Object { + static constexpr ObjectType TYPE = ObjectType::zCModel; + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + + void load(ReadArchive& r, GameVersion version) override; + }; + + struct AiMove : Object { + static constexpr ObjectType TYPE = ObjectType::zCModel; + [[nodiscard]] ObjectType get_type() const override { + return TYPE; + } + + void load(ReadArchive& r, GameVersion version) override; + }; + /// \brief The base class for all VObs. /// ///

Contains parameters all VObs have, like their position, bounding box and model.

- struct VirtualObject { + struct VirtualObject : Object { VirtualObject() = default; VirtualObject(VirtualObject const&) = delete; VirtualObject(VirtualObject&&) = default; @@ -180,8 +202,8 @@ namespace zenkit { using save_state ZKREM("renamed to SaveState") = SaveState; - VobType type; ///< The type of this VOb. - uint32_t id; ///< The index of this VOb in the archive it was read from. + ObjectType type; ///< The type of this VOb. + uint32_t id; ///< The index of this VOb in the archive it was read from. AxisAlignedBoundingBox bbox {}; glm::vec3 position {}; @@ -203,20 +225,24 @@ namespace zenkit { std::string vob_name {}; std::string visual_name {}; - VisualType associated_visual_type {}; - std::optional visual_decal {}; + ZKREM("use ::visual.get_type() instead") VisualType associated_visual_type {}; + ZKREM("use ::visual instead") std::optional visual_decal {}; + std::shared_ptr visual = nullptr; + + // One of: AiHuman (oCAIHuman), AiMove (oCAIVobMove) + std::shared_ptr ai = nullptr; /// \brief Contains extra data about the item in the context of a saved game. std::optional saved {}; /// \brief The children of this VOb. - std::vector> children {}; + std::vector> children {}; /// \brief Default virtual destructor. - virtual ~VirtualObject() = default; + ~VirtualObject() override = default; /// \return `true` if this VOb is from a save-game and `false` if not. - [[nodiscard]] ZKAPI inline bool is_save_game() const noexcept { + [[nodiscard]] ZKAPI bool is_save_game() const noexcept { return saved.has_value(); } @@ -231,6 +257,11 @@ namespace zenkit { /// \throws ParserError if parsing fails. ZKREM("use ::load()") ZKAPI static void parse(VirtualObject& obj, ReadArchive& ctx, GameVersion version); - ZKAPI virtual void load(ReadArchive& r, GameVersion version); + // TODO(lmichaelis): Return a constant value from this! + [[nodiscard]] ObjectType get_type() const override { + return this->type; + } + + ZKAPI void load(ReadArchive& r, GameVersion version) override; }; } // namespace zenkit diff --git a/include/zenkit/vobs/Zone.hh b/include/zenkit/vobs/Zone.hh index c6b83bf5..b1b40d90 100644 --- a/include/zenkit/vobs/Zone.hh +++ b/include/zenkit/vobs/Zone.hh @@ -14,7 +14,7 @@ namespace zenkit { namespace vobs { /// \brief A VOb which defines the background music in a certain zone. - struct ZoneMusic : public VirtualObject { + struct ZoneMusic : VirtualObject { bool enabled {false}; std::int32_t priority {0}; bool ellipsoid {false}; @@ -38,7 +38,7 @@ namespace zenkit { }; /// \brief A VOb which defines the far plane settings in a certain zone. - struct ZoneFarPlane : public VirtualObject { + struct ZoneFarPlane : VirtualObject { float vob_far_plane_z; float inner_range_percentage; @@ -53,7 +53,7 @@ namespace zenkit { }; /// \brief A VOb which defines the fog in a certain zone. - struct ZoneFog : public VirtualObject { + struct ZoneFog : VirtualObject { float range_center {0}; float inner_range_percentage {0}; glm::u8vec4 color {}; diff --git a/include/zenkit/world/BspTree.hh b/include/zenkit/world/BspTree.hh index 8196776f..6a008f57 100644 --- a/include/zenkit/world/BspTree.hh +++ b/include/zenkit/world/BspTree.hh @@ -8,7 +8,6 @@ #include #include -#include #include namespace zenkit { diff --git a/include/zenkit/world/VobTree.hh b/include/zenkit/world/VobTree.hh index fa1b2ca4..62376794 100644 --- a/include/zenkit/world/VobTree.hh +++ b/include/zenkit/world/VobTree.hh @@ -5,15 +5,12 @@ #include "zenkit/Misc.hh" #include "zenkit/vobs/VirtualObject.hh" -#include - #include -#include namespace zenkit { /// \brief Parses a VOB tree from the given reader. /// \param in The reader to read from. /// \param version The version of Gothic being used. /// \return The tree parsed. - ZKAPI std::unique_ptr parse_vob_tree(ReadArchive& in, GameVersion version); + ZKAPI std::shared_ptr parse_vob_tree(ReadArchive& in, GameVersion version); } // namespace zenkit diff --git a/include/zenkit/world/WayNet.hh b/include/zenkit/world/WayNet.hh index a23a10e4..a31eaa06 100644 --- a/include/zenkit/world/WayNet.hh +++ b/include/zenkit/world/WayNet.hh @@ -7,7 +7,6 @@ #include #include -#include #include namespace zenkit { diff --git a/src/Archive.cc b/src/Archive.cc index 1d3a7781..ddf8acd0 100644 --- a/src/Archive.cc +++ b/src/Archive.cc @@ -3,6 +3,16 @@ #include "zenkit/Archive.hh" #include "zenkit/Stream.hh" +#include "zenkit/Material.hh" +#include "zenkit/vobs/Camera.hh" +#include "zenkit/vobs/Light.hh" +#include "zenkit/vobs/Misc.hh" +#include "zenkit/vobs/MovableObject.hh" +#include "zenkit/vobs/Sound.hh" +#include "zenkit/vobs/Trigger.hh" +#include "zenkit/vobs/VirtualObject.hh" +#include "zenkit/vobs/Zone.hh" + #include "Internal.hh" #include "archive/ArchiveAscii.hh" @@ -10,10 +20,71 @@ #include "archive/ArchiveBinsafe.hh" #include "phoenix/buffer.hh" +#include "zenkit/World.hh" #include namespace zenkit { + static std::unordered_map const OBJECTS = { + {"zCVob", ObjectType::zCVob}, + {"zCVobLevelCompo:zCVob", ObjectType::zCVobLevelCompo}, + {"oCItem:zCVob", ObjectType::oCItem}, + {"oCNpc:zCVob", ObjectType::oCNpc}, + {"oCMOB:zCVob", ObjectType::oCMOB}, + {"oCMobInter:oCMOB:zCVob", ObjectType::oCMobInter}, + {"oCMobBed:oCMobInter:oCMOB:zCVob", ObjectType::oCMobBed}, + {"oCMobFire:oCMobInter:oCMOB:zCVob", ObjectType::oCMobFire}, + {"oCMobLadder:oCMobInter:oCMOB:zCVob", ObjectType::oCMobLadder}, + {"oCMobSwitch:oCMobInter:oCMOB:zCVob", ObjectType::oCMobSwitch}, + {"oCMobWheel:oCMobInter:oCMOB:zCVob", ObjectType::oCMobWheel}, + {"oCMobContainer:oCMobInter:oCMOB:zCVob", ObjectType::oCMobContainer}, + {"oCMobDoor:oCMobInter:oCMOB:zCVob", ObjectType::oCMobDoor}, + {"zCPFXControler:zCVob", ObjectType::zCPFXController}, + {"zCVobAnimate:zCVob", ObjectType::zCVobAnimate}, + {"zCVobLensFlare:zCVob", ObjectType::zCVobLensFlare}, + {"zCVobLight:zCVob", ObjectType::zCVobLight}, + {"zCVobSpot:zCVob", ObjectType::zCVobSpot}, + {"zCVobStartpoint:zCVob", ObjectType::zCVobStartpoint}, + {"zCVobSound:zCVob", ObjectType::zCVobSound}, + {"zCVobSoundDaytime:zCVobSound:zCVob", ObjectType::zCVobSoundDaytime}, + {"oCZoneMusic:zCVob", ObjectType::oCZoneMusic}, + {"oCZoneMusicDefault:oCZoneMusic:zCVob", ObjectType::oCZoneMusicDefault}, + {"zCZoneZFog:zCVob", ObjectType::zCZoneZFog}, + {"zCZoneZFogDefault:zCZoneZFog:zCVob", ObjectType::zCZoneZFogDefault}, + {"zCZoneVobFarPlane:zCVob", ObjectType::zCZoneVobFarPlane}, + {"zCZoneVobFarPlaneDefault:zCZoneVobFarPlane:zCVob", ObjectType::zCZoneVobFarPlaneDefault}, + {"zCMessageFilter:zCVob", ObjectType::zCMessageFilter}, + {"zCCodeMaster:zCVob", ObjectType::zCCodeMaster}, + {"zCTrigger:zCVob", ObjectType::zCTrigger}, + {"zCTriggerList:zCTrigger:zCVob", ObjectType::zCTriggerList}, + {"oCTriggerScript:zCTrigger:zCVob", ObjectType::oCTriggerScript}, + {"zCMover:zCTrigger:zCVob", ObjectType::zCMover}, + {"oCTriggerChangeLevel:zCTrigger:zCVob", ObjectType::oCTriggerChangeLevel}, + {"zCTriggerWorldStart:zCVob", ObjectType::zCTriggerWorldStart}, + {"zCTriggerUntouch:zCVob", ObjectType::zCTriggerUntouch}, + {"zCCSCamera:zCVob", ObjectType::zCCSCamera}, + {"zCCamTrj_KeyFrame:zCVob", ObjectType::zCCamTrj_KeyFrame}, + {"oCTouchDamage:zCTouchDamage:zCVob", ObjectType::oCTouchDamage}, + {"zCEarthquake:zCVob", ObjectType::zCEarthquake}, + {"zCMoverControler:zCVob", ObjectType::zCMoverController}, + {"zCVobScreenFX:zCVob", ObjectType::zCVobScreenFX}, + {"zCVobStair:zCVob", ObjectType::zCVobStair}, + {"oCCSTrigger:zCTrigger:zCVob", ObjectType::oCCSTrigger}, + {"oCNpcTalent", ObjectType::oCNpcTalent}, + {"zCEventManager", ObjectType::zCEventManager}, + {"zCDecal", ObjectType::zCDecal}, + {"zCMesh", ObjectType::zCMesh}, + {"zCProgMeshProto", ObjectType::zCProgMeshProto}, + {"zCParticleFX", ObjectType::zCParticleFX}, + {"zCAICamera", ObjectType::zCAICamera}, + {"zCModel", ObjectType::zCModel}, + {"zCMorphMesh", ObjectType::zCMorphMesh}, + {"oCAIHuman:oCAniCtrl_Human:zCAIPlayer", ObjectType::oCAIHuman}, + {"oCAIVobMove", ObjectType::oCAIVobMove}, + {"oCCSPlayer:zCCSPlayer", ObjectType::oCCSPlayer}, + {"zCSkyControler_Outdoor", ObjectType::zCSkyControler_Outdoor}, + }; + ReadArchive::ReadArchive(ArchiveHeader head, Read* r) : header(std::move(head)), read(r) {} ReadArchive::ReadArchive(ArchiveHeader head, Read* r, std::unique_ptr owned) @@ -107,15 +178,282 @@ namespace zenkit { } else if (header.format == ArchiveFormat::BINSAFE) { reader = std::make_unique(std::move(header), r); } else { - throw zenkit::ParserError {"ReadArchive", - "format '" + std::to_string(static_cast(header.format)) + - "' is not supported"}; + throw ParserError {"ReadArchive", + "format '" + std::to_string(static_cast(header.format)) + + "' is not supported"}; } reader->read_header(); return reader; } + std::shared_ptr ReadArchive::read_object(GameVersion version) { + ArchiveObject obj; + if (!this->read_object_begin(obj)) { + ZKLOGE("ReadArchive", "Expected object, got entry."); + return nullptr; + } + + if (obj.class_name == "\xA7") { + if (!this->read_object_end()) { + ZKLOGE("ReadArchive", "Invalid reference object: has children"); + this->skip_object(true); + } + + auto cached = _m_cache.find(obj.index); + if (cached == _m_cache.end()) { + ZKLOGW("ReadArchive", "Unresolved reference: %d", obj.index); + return nullptr; + } + + return cached->second; + } + + if (obj.class_name == "%") { + // This object is marked as "Empty" + this->skip_object(true); + return nullptr; + } + + auto it = OBJECTS.find(obj.class_name); + ObjectType type = ObjectType::unknown; + if (it != OBJECTS.end()) { + type = it->second; + } + + // TODO(lmichaelis): The VOb type/id assignment is a hacky workaround! Create separate types! + std::shared_ptr syn; + switch (type) { + case ObjectType::oCNpcTalent: + syn = std::make_shared(); + break; + case ObjectType::zCEventManager: + syn = std::make_shared(); + break; + case ObjectType::zCDecal: + syn = std::make_shared(); + break; + case ObjectType::zCMesh: + syn = std::make_shared(); + break; + case ObjectType::zCProgMeshProto: + syn = std::make_shared(); + break; + case ObjectType::zCParticleFX: + syn = std::make_shared(); + break; + case ObjectType::zCAICamera: + syn = std::make_shared(); + break; + case ObjectType::zCModel: + syn = std::make_shared(); + break; + case ObjectType::zCMorphMesh: + syn = std::make_shared(); + break; + case ObjectType::oCAIHuman: + syn = std::make_shared(); + break; + case ObjectType::oCAIVobMove: + syn = std::make_shared(); + break; + case ObjectType::zCSkyControler_Outdoor: + syn = std::make_shared(); + break; + case ObjectType::oCCSPlayer: + syn = std::make_shared(); + break; + case ObjectType::zCVobLevelCompo: + case ObjectType::zCVobStartpoint: + case ObjectType::zCVobStair: + case ObjectType::zCVobSpot: + case ObjectType::zCVob: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCVobScreenFX: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCCSCamera: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCCamTrj_KeyFrame: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCVobAnimate: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCZoneVobFarPlane: + case ObjectType::zCZoneVobFarPlaneDefault: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCZoneZFogDefault: + case ObjectType::zCZoneZFog: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCVobLensFlare: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::oCItem: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCTrigger: + case ObjectType::oCCSTrigger: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::oCMOB: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::oCMobInter: + case ObjectType::oCMobLadder: + case ObjectType::oCMobSwitch: + case ObjectType::oCMobWheel: + case ObjectType::oCMobBed: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::oCMobFire: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::oCMobContainer: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::oCMobDoor: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCPFXController: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCVobLight: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCVobSound: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCVobSoundDaytime: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::oCZoneMusic: + case ObjectType::oCZoneMusicDefault: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCMessageFilter: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCCodeMaster: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCTriggerList: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::oCTriggerScript: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCMover: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::oCTriggerChangeLevel: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCTriggerWorldStart: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::oCTouchDamage: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCTriggerUntouch: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCEarthquake: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::zCMoverController: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + case ObjectType::oCNpc: + syn = std::make_shared(); + reinterpret_cast(syn.get())->type = type; + reinterpret_cast(syn.get())->id = obj.index; + break; + default: + ZKLOGE("ReadArchive", "Unknown object type: %s", obj.class_name.c_str()); + break; + } + + _m_cache.insert_or_assign(obj.index, syn); + + if (syn != nullptr) { + syn->load(*this, version); + } + + if (!this->read_object_end()) { + ZKLOGW("ReadArchive", "Object not fully loaded: %s", obj.class_name.data()); + this->skip_object(true); + } + + return syn; + } + void ReadArchive::skip_object(bool skip_current) { ArchiveObject tmp; int32_t level = skip_current ? 1 : 0; diff --git a/src/Object.cc b/src/Object.cc new file mode 100644 index 00000000..2243ecd3 --- /dev/null +++ b/src/Object.cc @@ -0,0 +1,5 @@ +#include "zenkit/Object.hh" + +namespace zenkit { + void Object::load(ReadArchive& r, GameVersion version) {} +} // namespace zenkit \ No newline at end of file diff --git a/src/World.cc b/src/World.cc index b2fe8242..43b3d93e 100644 --- a/src/World.cc +++ b/src/World.cc @@ -77,14 +77,14 @@ namespace zenkit { this->load(r, version); } - void World::load(zenkit::Read* r, zenkit::GameVersion version) { + void World::load(Read* r, GameVersion version) { auto archive = ReadArchive::from(r); ArchiveObject chnk {}; archive->read_object_begin(chnk); if (chnk.class_name != "oCWorld:zCWorld") { - throw zenkit::ParserError {"World", "'oCWorld:zCWorld' chunk expected, got '" + chnk.class_name + "'"}; + throw ParserError {"World", "'oCWorld:zCWorld' chunk expected, got '" + chnk.class_name + "'"}; } while (!archive->read_object_end()) { @@ -132,101 +132,11 @@ namespace zenkit { } else if (chnk.object_name == "WayNet") { this->world_way_net.load(*archive); } else if (chnk.object_name == "CutscenePlayer") { - // TODO: only present in save-games - - if (!archive->read_object_begin(chnk)) { - ZKLOGW("World", - "Object [%s %s %u %u] encountered but unable to parse", - chnk.object_name.c_str(), - chnk.class_name.c_str(), - chnk.version, - chnk.index); - archive->skip_object(true); - continue; - } - - (void) archive->read_int(); // lastProcessDay - (void) archive->read_int(); // lastProcessHour - (void) archive->read_int(); // playListCount - - archive->read_object_end(); + this->player = archive->read_object(version); } else if (chnk.object_name == "SkyCtrl") { - // TODO: only present in save-games - - if (!archive->read_object_begin(chnk)) { - ZKLOGW("World", - "Object [%s %s %u %u] encountered but unable to parse", - chnk.object_name.c_str(), - chnk.class_name.c_str(), - chnk.version, - chnk.index); - archive->skip_object(true); - continue; - } - - (void) archive->read_float(); // masterTime - (void) archive->read_float(); // rainWeight - (void) archive->read_float(); // rainStart - (void) archive->read_float(); // rainStop - (void) archive->read_float(); // rainSctTimer - (void) archive->read_float(); // rainSndVol - (void) archive->read_float(); // dayCtr - - if (version == GameVersion::GOTHIC_2) { - (void) archive->read_float(); // fadeScale - (void) archive->read_bool(); // renderLightning - (void) archive->read_bool(); // isRaining - (void) archive->read_int(); // rainCtr - } - - archive->read_object_end(); - } else if (chnk.object_name == "EndMarker" && archive->get_header().save) { - // TODO: save games contain a list of NPCs after the end marker - // First, Consume the end-maker fully + this->sky_controller = archive->read_object(version); + } else if (chnk.object_name == "EndMarker") { archive->read_object_end(); - - // Then, read all the NPCs - auto npc_count = archive->read_int(); // npcCount - for (auto i = 0; i < npc_count; ++i) { - archive->read_object_begin(chnk); - - if (chnk.class_name != "\xA7") { - vobs::Npc npc {}; - npc.load(*archive, version); - } else { - ZKLOGE("World", - "Cannot load NPC reference [%s %s %d %d]", - chnk.object_name.c_str(), - chnk.class_name.c_str(), - chnk.version, - chnk.index); - } - - if (!archive->read_object_end()) { - archive->skip_object(true); - } - } - - // After that, read all NPC spawn locations - auto npc_spawn_count = archive->read_int(); // NoOfEntries - for (auto i = 0; i < npc_spawn_count; ++i) { - archive->skip_object(false); // npc zReference - (void) archive->read_vec3(); // spawnPos - (void) archive->read_float(); // timer - } - - (void) archive->read_bool(); // spawningEnabled - - if (version == GameVersion::GOTHIC_2) { - (void) archive->read_int(); // spawnFlags - } - - if (!archive->read_object_end()) { - ZKLOGW("World", "Npc-list not fully parsed"); - archive->skip_object(true); - } - - // We have fully consumed the world block. From here we should just die. break; } @@ -240,5 +150,60 @@ namespace zenkit { archive->skip_object(true); } } + + // TODO: save games contain a list of NPCs after the end marker + if (archive->get_header().save) { + // Then, read all the NPCs + auto npc_count = archive->read_int(); // npcCount + this->npcs.resize(npc_count); + for (auto i = 0; i < npc_count; ++i) { + this->npcs[i] = archive->read_object(version); + } + + // After that, read all NPC spawn locations + auto npc_spawn_count = archive->read_int(); // NoOfEntries + this->npc_spawns.resize(npc_spawn_count); + + for (auto& spawn : this->npc_spawns) { + spawn.npc = archive->read_object(version); // npc + spawn.position = archive->read_vec3(); // spawnPos + spawn.timer = archive->read_float(); // timer + } + + this->npc_spawn_enabled = archive->read_bool(); // spawningEnabled + + if (version == GameVersion::GOTHIC_2) { + this->npc_spawn_flags = archive->read_int(); // spawnFlags + } + } + + if (!archive->read_object_end()) { + ZKLOGW("World", "Not fully parsed"); + archive->skip_object(true); + } + } + + void CutscenePlayer::load(ReadArchive& r, GameVersion) { + this->last_process_day = r.read_int(); // lastProcessDay + this->last_process_hour = r.read_int(); // lastProcessHour + this->play_list_count = r.read_int(); // playListCount + } + + void SkyController::load(ReadArchive& r, GameVersion version) { + // TODO + master_time = r.read_float(); // masterTime + rain_weight = r.read_float(); // rainWeight + rain_start = r.read_float(); // rainStart + rain_stop = r.read_float(); // rainStop + rain_sct_timer = r.read_float(); // rainSctTimer + rain_snd_vol = r.read_float(); // rainSndVol + day_ctr = r.read_float(); // dayCtr + + if (version == GameVersion::GOTHIC_2) { + fade_scale = r.read_float(); // fadeScale + render_lightning = r.read_bool(); // renderLightning + is_raining = r.read_bool(); // isRaining + rain_ctr = r.read_int(); // rainCtr + } } } // namespace zenkit diff --git a/src/archive/ArchiveBinsafe.cc b/src/archive/ArchiveBinsafe.cc index e20a6f61..93b4b8d4 100644 --- a/src/archive/ArchiveBinsafe.cc +++ b/src/archive/ArchiveBinsafe.cc @@ -159,8 +159,8 @@ namespace zenkit { auto unused = static_cast(ensure_entry_meta() - 2 * sizeof(float)); if (unused < 0) { - throw zenkit::ParserError {"ReadArchive.Binsafe" - "cannot read vec2 (2 * float): not enough space in rawFloat entry."}; + throw ParserError {"ReadArchive.Binsafe" + "cannot read vec2 (2 * float): not enough space in rawFloat entry."}; } auto c = read->read_vec2(); @@ -175,8 +175,8 @@ namespace zenkit { static_cast(ensure_entry_meta() - 3 * 2 * sizeof(float)); if (unused < 0) { - throw zenkit::ParserError {"ReadArchive.Binsafe", - "cannot read bbox (6 * float): not enough space in rawFloat entry."}; + throw ParserError {"ReadArchive.Binsafe", + "cannot read bbox (6 * float): not enough space in rawFloat entry."}; } AxisAlignedBoundingBox aabb {}; @@ -191,8 +191,7 @@ namespace zenkit { auto unused = static_cast(ensure_entry_meta() - 3 * 3 * sizeof(float)); if (unused < 0) { - throw zenkit::ParserError( - "ReadArchive.Binsafe: cannot read mat3x3 (9 * float): not enough space in raw entry."); + throw ParserError("ReadArchive.Binsafe: cannot read mat3x3 (9 * float): not enough space in raw entry."); } auto v = read->read_mat3(); @@ -206,12 +205,12 @@ namespace zenkit { auto length = ensure_entry_meta(); if (length < size) { - throw zenkit::ParserError {"ReadArchive.Binsafe", "not enough raw bytes to read!"}; + throw ParserError {"ReadArchive.Binsafe", "not enough raw bytes to read!"}; } else if (length > size) { ZKLOGW("ReadArchive.Binsafe", "Reading %d bytes although %d are actually available", size, length); } - std::vector bytes(length, std::byte {}); + std::vector bytes(length, std::byte {}); read->read(bytes.data(), length); return phoenix::buffer::of(std::move(bytes)); } @@ -220,12 +219,12 @@ namespace zenkit { auto length = ensure_entry_meta(); if (length < size) { - throw zenkit::ParserError {"ReadArchive.Binsafe", "not enough raw bytes to read!"}; + throw ParserError {"ReadArchive.Binsafe", "not enough raw bytes to read!"}; } else if (length > size) { ZKLOGW("ReadArchive.Binsafe", "Reading %zu bytes although %d are actually available", size, length); } - std::vector bytes(length, std::byte {}); + std::vector bytes(length, std::byte {}); read->read(bytes.data(), length); return Read::from(std::move(bytes)); } diff --git a/src/vobs/Camera.cc b/src/vobs/Camera.cc index 3e5dc2fc..11b73c5e 100644 --- a/src/vobs/Camera.cc +++ b/src/vobs/Camera.cc @@ -35,7 +35,7 @@ namespace zenkit::vobs { obj.load(r, version); } - void CutsceneCamera::load(zenkit::ReadArchive& r, zenkit::GameVersion version) { + void CutsceneCamera::load(ReadArchive& r, GameVersion version) { VirtualObject::load(r, version); this->trajectory_for = static_cast(r.read_enum()); // camTrjFOR this->target_trajectory_for = static_cast(r.read_enum()); // targetTrjFOR @@ -54,20 +54,9 @@ namespace zenkit::vobs { this->position_count = r.read_int(); // numPos this->target_count = r.read_int(); // numTargets - ArchiveObject frame_obj {}; - while (r.read_object_begin(frame_obj)) { - if (frame_obj.class_name != "zCCamTrj_KeyFrame:zCVob") { - ZKLOGW("VOb.CutsceneCamera", "Unexpected \"%s\" in \"zCCSCamera:zCVob\"", frame_obj.class_name.c_str()); - r.skip_object(true); - continue; - } - - this->frames.emplace_back(std::make_unique())->load(r, version); - - if (!r.read_object_end()) { - ZKLOGW("VOb.CutsceneCamera", "\"zCCamTrj_KeyFrame\" not fully parsed"); - r.skip_object(true); - } + for (auto i = 0; i < this->position_count + this->target_count; ++i) { + auto obj = r.read_object(version); + this->frames.push_back(obj); } if (r.is_save_game() && version == GameVersion::GOTHIC_2) { diff --git a/src/vobs/Misc.cc b/src/vobs/Misc.cc index 47cc5fbe..173b26d3 100644 --- a/src/vobs/Misc.cc +++ b/src/vobs/Misc.cc @@ -135,18 +135,24 @@ namespace zenkit::vobs { obj.load(r, version); } - void Earthquake::load(zenkit::ReadArchive& r, zenkit::GameVersion version) { + void Earthquake::load(ReadArchive& r, GameVersion version) { VirtualObject::load(r, version); this->radius = r.read_float(); // radius this->duration = r.read_float(); // timeSec this->amplitude = r.read_vec3(); // amplitudeCM } - void Npc::parse(vobs::Npc& obj, ReadArchive& r, GameVersion version) { + void Npc::Talent::load(ReadArchive& r, GameVersion version) { + this->talent = r.read_int(); // talent + this->value = r.read_int(); // value + this->skill = r.read_int(); // skill + } + + void Npc::parse(Npc& obj, ReadArchive& r, GameVersion version) { obj.load(r, version); } - void Npc::load(zenkit::ReadArchive& r, zenkit::GameVersion version) { + void Npc::load(ReadArchive& r, GameVersion version) { VirtualObject::load(r, version); this->npc_instance = r.read_string(); // npcInstance @@ -171,23 +177,7 @@ namespace zenkit::vobs { ArchiveObject hdr; for (auto i = 0u; i < talent_count; ++i) { - if (!r.read_object_begin(hdr)) // [% oCNpcTalent 0 0] - throw zenkit::ParserError {"vobs::Npc"}; - - // empty object - if (hdr.class_name == "%") { - r.skip_object(true); - continue; - } - - this->talents[i].talent = r.read_int(); // talent - this->talents[i].value = r.read_int(); // value - this->talents[i].skill = r.read_int(); // skill - - if (!r.read_object_end()) { - ZKLOGW("VOb.Npc", "oCNpcTalent object not fully parsed"); - r.skip_object(true); - } + this->talents[i] = r.read_object(version); } this->fight_tactic = r.read_int(); // fightTactic @@ -234,7 +224,7 @@ namespace zenkit::vobs { ZKLOGE("VOb.Npc", "!!! IMPORTANT !!! This save-game contains news entries and cannot be loaded currently. Please " "open an issue at https://github.com/GothicKit/phoenix providing your save-game as a ZIP file."); - throw zenkit::ParserError {"vobs::Npc"}; + throw ParserError {"vobs::Npc"}; } r.skip_object(false); // [carryVob % 0 0] @@ -262,16 +252,7 @@ namespace zenkit::vobs { this->items.resize(item_count); for (auto i = 0u; i < item_count; ++i) { - if (!r.read_object_begin(hdr)) throw zenkit::ParserError {"vobs::Npc"}; - - this->items[i] = std::make_unique(); - this->items[i]->load(r, version); - this->items[i]->id = hdr.index; - - if (!r.read_object_end()) { - ZKLOGW("VOb.Npc", "oCItem:zCVob object not fully parsed"); - r.skip_object(true); - } + this->items[i] = r.read_object(version); if ((this->items[i]->s_flags & 0x200) != 0) { (void) r.read_int(); // shortKey // TODO @@ -285,36 +266,7 @@ namespace zenkit::vobs { this->slots[i].name = r.read_string(); // name if (this->slots[i].used) { - // [vob § 0 0] - if (!r.read_object_begin(hdr)) { - throw zenkit::ParserError {"VOb.Npc"}; - } - - if (hdr.class_name == "\xA7") { - // This item is a reference. - // TODO: Warn if not found. - for (auto const& item : this->items) { - if (item->id == hdr.index) { - this->slots[i].item = item.get(); - break; - } - } - } else { - // This item is embedded. - this->items.push_back(std::make_unique()); - - auto& item = this->items.back(); - item->load(r, version); - item->id = hdr.index; - - this->slots[i].item = item.get(); - } - - if (!r.read_object_end()) { - ZKLOGW("VOb.Npc", "Inventory slot not fully parsed."); - r.skip_object(true); - } - + this->slots[i].item = r.read_object(version); this->slots[i].in_inventory = r.read_bool(); // inInv } } diff --git a/src/vobs/MovableObject.cc b/src/vobs/MovableObject.cc index 00baa1a8..f766d99a 100644 --- a/src/vobs/MovableObject.cc +++ b/src/vobs/MovableObject.cc @@ -55,19 +55,8 @@ namespace zenkit::vobs { auto item_count = static_cast(r.read_int()); // NumOfEntries this->s_items.resize(item_count); - ArchiveObject itm; for (auto i = 0u; i < item_count; ++i) { - if (!r.read_object_begin(itm) || itm.class_name != "oCItem:zCVob") { - throw zenkit::ParserError {"VOb.Container"}; - } - - this->s_items[i] = std::make_unique(); - this->s_items[i]->load(r, version); - - if (!r.read_object_end()) { - ZKLOGW("VOb.Container", "oCItem:zCVob object not fully parsed"); - r.skip_object(true); - } + this->s_items[i] = r.read_object(version); } } } @@ -76,7 +65,7 @@ namespace zenkit::vobs { obj.load(r, version); } - void Door::load(zenkit::ReadArchive& r, zenkit::GameVersion version) { + void Door::load(ReadArchive& r, GameVersion version) { InteractiveObject::load(r, version); this->locked = r.read_bool(); // locked this->key = r.read_string(); // keyInstance @@ -87,7 +76,7 @@ namespace zenkit::vobs { obj.load(r, version); } - void Fire::load(zenkit::ReadArchive& r, zenkit::GameVersion version) { + void Fire::load(ReadArchive& r, GameVersion version) { InteractiveObject::load(r, version); this->slot = r.read_string(); // fireSlot this->vob_tree = r.read_string(); // fireVobtreeName diff --git a/src/vobs/VirtualObject.cc b/src/vobs/VirtualObject.cc index 4372924e..bb622c5f 100644 --- a/src/vobs/VirtualObject.cc +++ b/src/vobs/VirtualObject.cc @@ -9,25 +9,23 @@ namespace zenkit { /// \brief A mapping of archive class names to visual_type values. - static std::unordered_map visual_type_map = { - {"zCDecal", VisualType::DECAL}, - {"zCMesh", VisualType::MESH}, - {"zCProgMeshProto", VisualType::MULTI_RESOLUTION_MESH}, - {"zCParticleFX", VisualType::PARTICLE_EFFECT}, - {"zCModel", VisualType::MODEL}, - {"zCAICamera", VisualType::AI_CAMERA}, - {"zCMorphMesh", VisualType::MORPH_MESH}, - {"\xA7", VisualType::UNKNOWN}, - {"%", VisualType::UNKNOWN}, + static std::unordered_map visual_type_map = { + {ObjectType::zCDecal, VisualType::DECAL}, + {ObjectType::zCMesh, VisualType::MESH}, + {ObjectType::zCProgMeshProto, VisualType::MULTI_RESOLUTION_MESH}, + {ObjectType::zCParticleFX, VisualType::PARTICLE_EFFECT}, + {ObjectType::zCModel, VisualType::MODEL}, + {ObjectType::zCAICamera, VisualType::AI_CAMERA}, + {ObjectType::zCMorphMesh, VisualType::MORPH_MESH}, }; - Decal Decal::parse(ReadArchive& in, GameVersion version) { - Decal dc {}; + VisualDecal VisualDecal::parse(ReadArchive& in, GameVersion version) { + VisualDecal dc {}; dc.load(in, version); return dc; } - void Decal::load(zenkit::ReadArchive& r, zenkit::GameVersion version) { + void VisualDecal::load(ReadArchive& r, GameVersion version) { this->name = r.read_string(); // name this->dimension = r.read_vec2(); // decalDim this->offset = r.read_vec2(); // decalOffset @@ -45,7 +43,7 @@ namespace zenkit { obj.load(in, version); } - void VirtualObject::load(zenkit::ReadArchive& r, zenkit::GameVersion version) { + void VirtualObject::load(ReadArchive& r, GameVersion version) { auto packed = r.read_int() != 0; // pack bool has_visual_object = true; bool has_ai_object = true; @@ -136,75 +134,29 @@ namespace zenkit { } } - ArchiveObject hdr {}; if (has_visual_object) { - ArchiveObject visual {}; - r.read_object_begin(visual); - this->associated_visual_type = visual_type_map[visual.class_name]; + this->visual = r.read_object(version); - if (this->associated_visual_type == VisualType::DECAL) { - this->visual_decal = Decal {}; - this->visual_decal->load(r, version); - } - - if (!r.read_object_end()) { - ZKLOGW("VirtualObject", "visual \"%s\" not fully parsed", visual.class_name.c_str()); - r.skip_object(true); - } - } - - // TODO - if (has_ai_object && r.read_object_begin(hdr)) { - if (hdr.class_name == "oCAIHuman:oCAniCtrl_Human:zCAIPlayer") { - (void) r.read_int(); // waterLevel - (void) r.read_float(); // floorY - (void) r.read_float(); // waterY - (void) r.read_float(); // ceilY - (void) r.read_float(); // feetY - (void) r.read_float(); // headY - (void) r.read_float(); // fallDistY - (void) r.read_float(); // fallStartY - - // TODO: aiNpc - if (r.read_object_begin(hdr)) { - r.skip_object(true); + if (visual != nullptr) { + auto it = visual_type_map.find(visual->get_type()); + if (it == visual_type_map.end()) { + this->associated_visual_type = VisualType::UNKNOWN; } else { - ZKLOGW("VirtualObject.Ai", "aiNpc not found"); - } - - (void) r.read_int(); // walkMode - (void) r.read_int(); // weaponMode - (void) r.read_int(); // wmodeLast - (void) r.read_int(); // wmodeSelect - (void) r.read_bool(); // changeWeapon - (void) r.read_int(); // actionMode - } else if (hdr.class_name == "oCAIVobMove") { - // TODO: vob - if (r.read_object_begin(hdr)) { - r.skip_object(true); + this->associated_visual_type = it->second; } - // TODO: owner - if (r.read_object_begin(hdr)) { - r.skip_object(true); + if (this->visual->get_type() == ObjectType::zCDecal) { + this->visual_decal.emplace(*std::reinterpret_pointer_cast(this->visual)); } } - - if (!r.read_object_end()) { - ZKLOGW("VirtualObject.Ai", "\"%s\" not fully parsed.", hdr.class_name.c_str()); - r.skip_object(true); - } } - if (has_event_manager_object && r.read_object_begin(hdr)) { - if (hdr.class_name != "zCEventManager") { - throw ParserError {"VirtualObject"}; - } - - EventManager em {}; - em.load(r, version); + if (has_ai_object) { + this->ai = r.read_object(version); + } - r.read_object_end(); + if (has_event_manager_object) { + (void) /* em = */ r.read_object(version); } if (r.get_header().save) { @@ -228,16 +180,36 @@ namespace zenkit { this->slide_direction = r.read_vec3(); } - void EventManager::load(ReadArchive& r, GameVersion) { + void EventManager::load(ReadArchive& r, GameVersion version) { this->cleared = r.read_bool(); this->active = r.read_bool(); - ArchiveObject obj; - if (r.read_object_begin(obj) && !r.read_object_end()) { - ZKLOGW("VirtualObject.EventManager", "emCutscene not fully parsed!"); - r.skip_object(true); - } + (void) /* TODO: emCutscene = */ r.read_object(version); + } + + void AiHuman::load(ReadArchive& r, GameVersion version) { + Object::load(r, version); + (void) r.read_int(); // waterLevel + (void) r.read_float(); // floorY + (void) r.read_float(); // waterY + (void) r.read_float(); // ceilY + (void) r.read_float(); // feetY + (void) r.read_float(); // headY + (void) r.read_float(); // fallDistY + (void) r.read_float(); // fallStartY + + (void) /* TODO: aiNpc = */ r.read_object(version); + + (void) r.read_int(); // walkMode + (void) r.read_int(); // weaponMode + (void) r.read_int(); // wmodeLast + (void) r.read_int(); // wmodeSelect + (void) r.read_bool(); // changeWeapon + (void) r.read_int(); // actionMode + } - // TODO: this->cutscene.load(r, version); + void AiMove::load(ReadArchive& r, GameVersion version) { + (void) /* TODO: vob = */ r.read_object(version); + (void) /* TODO: owner = */ r.read_object(version); } } // namespace zenkit diff --git a/src/world/VobTree.cc b/src/world/VobTree.cc index 6dcdea63..f7a4254f 100644 --- a/src/world/VobTree.cc +++ b/src/world/VobTree.cc @@ -16,199 +16,9 @@ #include namespace zenkit { - static std::unordered_map TYPES = { - {"zCVob", VobType::zCVob}, - {"zCVobLevelCompo:zCVob", VobType::zCVobLevelCompo}, - {"oCItem:zCVob", VobType::oCItem}, - {"oCNpc:zCVob", VobType::oCNpc}, - {"oCMOB:zCVob", VobType::oCMOB}, - {"oCMobInter:oCMOB:zCVob", VobType::oCMobInter}, - {"oCMobBed:oCMobInter:oCMOB:zCVob", VobType::oCMobBed}, - {"oCMobFire:oCMobInter:oCMOB:zCVob", VobType::oCMobFire}, - {"oCMobLadder:oCMobInter:oCMOB:zCVob", VobType::oCMobLadder}, - {"oCMobSwitch:oCMobInter:oCMOB:zCVob", VobType::oCMobSwitch}, - {"oCMobWheel:oCMobInter:oCMOB:zCVob", VobType::oCMobWheel}, - {"oCMobContainer:oCMobInter:oCMOB:zCVob", VobType::oCMobContainer}, - {"oCMobDoor:oCMobInter:oCMOB:zCVob", VobType::oCMobDoor}, - {"zCPFXControler:zCVob", VobType::zCPFXController}, - {"zCVobAnimate:zCVob", VobType::zCVobAnimate}, - {"zCVobLensFlare:zCVob", VobType::zCVobLensFlare}, - {"zCVobLight:zCVob", VobType::zCVobLight}, - {"zCVobSpot:zCVob", VobType::zCVobSpot}, - {"zCVobStartpoint:zCVob", VobType::zCVobStartpoint}, - {"zCVobSound:zCVob", VobType::zCVobSound}, - {"zCVobSoundDaytime:zCVobSound:zCVob", VobType::zCVobSoundDaytime}, - {"oCZoneMusic:zCVob", VobType::oCZoneMusic}, - {"oCZoneMusicDefault:oCZoneMusic:zCVob", VobType::oCZoneMusicDefault}, - {"zCZoneZFog:zCVob", VobType::zCZoneZFog}, - {"zCZoneZFogDefault:zCZoneZFog:zCVob", VobType::zCZoneZFogDefault}, - {"zCZoneVobFarPlane:zCVob", VobType::zCZoneVobFarPlane}, - {"zCZoneVobFarPlaneDefault:zCZoneVobFarPlane:zCVob", VobType::zCZoneVobFarPlaneDefault}, - {"zCMessageFilter:zCVob", VobType::zCMessageFilter}, - {"zCCodeMaster:zCVob", VobType::zCCodeMaster}, - {"zCTrigger:zCVob", VobType::zCTrigger}, - {"zCTriggerList:zCTrigger:zCVob", VobType::zCTriggerList}, - {"oCTriggerScript:zCTrigger:zCVob", VobType::oCTriggerScript}, - {"zCMover:zCTrigger:zCVob", VobType::zCMover}, - {"oCTriggerChangeLevel:zCTrigger:zCVob", VobType::oCTriggerChangeLevel}, - {"zCTriggerWorldStart:zCVob", VobType::zCTriggerWorldStart}, - {"zCTriggerUntouch:zCVob", VobType::zCTriggerUntouch}, - {"zCCSCamera:zCVob", VobType::zCCSCamera}, - {"zCCamTrj_KeyFrame:zCVob", VobType::zCCamTrj_KeyFrame}, - {"oCTouchDamage:zCTouchDamage:zCVob", VobType::oCTouchDamage}, - {"zCEarthquake:zCVob", VobType::zCEarthquake}, - {"zCMoverControler:zCVob", VobType::zCMoverController}, - {"zCVobScreenFX:zCVob", VobType::zCVobScreenFX}, - {"zCVobStair:zCVob", VobType::zCVobStair}, - {"oCCSTrigger:zCTrigger:zCVob", VobType::oCCSTrigger}, - {"\xA7", VobType::ignored}, // some sort of padding object, probably. seems to be always empty - }; - - std::unique_ptr parse_vob_tree(ReadArchive& in, GameVersion version) { - std::vector> vobs {}; - - ArchiveObject obj; - if (!in.read_object_begin(obj)) { - throw zenkit::ParserError("vob_tree: expected object where there was none"); - } - - VobType type; - - if (auto const& it = TYPES.find(obj.class_name); it != TYPES.end()) { - type = it->second; - } else { - type = VobType::unknown; - } - - std::unique_ptr object; - - switch (type) { - case VobType::zCCamTrj_KeyFrame: - case VobType::zCVobLevelCompo: - case VobType::zCVobStartpoint: - case VobType::zCVobStair: - case VobType::zCVobSpot: - case VobType::zCVob: - object = std::make_unique(); - break; - case VobType::zCVobScreenFX: - object = std::make_unique(); - break; - case VobType::zCCSCamera: - object = std::make_unique(); - break; - case VobType::zCVobAnimate: - object = std::make_unique(); - break; - case VobType::zCZoneVobFarPlane: - case VobType::zCZoneVobFarPlaneDefault: - object = std::make_unique(); - break; - case VobType::zCZoneZFogDefault: - case VobType::zCZoneZFog: - object = std::make_unique(); - break; - case VobType::zCVobLensFlare: - object = std::make_unique(); - break; - case VobType::oCItem: - object = std::make_unique(); - break; - case VobType::zCTrigger: - case VobType::oCCSTrigger: - object = std::make_unique(); - break; - case VobType::oCMOB: - object = std::make_unique(); - break; - case VobType::oCMobInter: - case VobType::oCMobLadder: - case VobType::oCMobSwitch: - case VobType::oCMobWheel: - case VobType::oCMobBed: - object = std::make_unique(); - break; - case VobType::oCMobFire: - object = std::make_unique(); - break; - case VobType::oCMobContainer: - object = std::make_unique(); - break; - case VobType::oCMobDoor: - object = std::make_unique(); - break; - case VobType::zCPFXController: - object = std::make_unique(); - break; - case VobType::zCVobLight: - object = std::make_unique(); - break; - case VobType::zCVobSound: - object = std::make_unique(); - break; - case VobType::zCVobSoundDaytime: - object = std::make_unique(); - break; - case VobType::oCZoneMusic: - case VobType::oCZoneMusicDefault: - object = std::make_unique(); - break; - case VobType::zCMessageFilter: - object = std::make_unique(); - break; - case VobType::zCCodeMaster: - object = std::make_unique(); - break; - case VobType::zCTriggerList: - object = std::make_unique(); - break; - case VobType::oCTriggerScript: - object = std::make_unique(); - break; - case VobType::zCMover: - object = std::make_unique(); - break; - case VobType::oCTriggerChangeLevel: - object = std::make_unique(); - break; - case VobType::zCTriggerWorldStart: - object = std::make_unique(); - break; - case VobType::oCTouchDamage: - object = std::make_unique(); - break; - case VobType::zCTriggerUntouch: - object = std::make_unique(); - break; - case VobType::zCEarthquake: - object = std::make_unique(); - break; - case VobType::zCMoverController: - object = std::make_unique(); - break; - case VobType::oCNpc: - object = std::make_unique(); - break; - case VobType::ignored: - break; - case VobType::unknown: - ZKLOGW("VobTree", - "Encountered unknown VOb [%s %s %u %u]", - obj.object_name.c_str(), - obj.class_name.c_str(), - obj.version, - obj.index); - break; - } - - if (object) { - object->load(in, version); - } - - if (!in.read_object_end()) { - ZKLOGW("VobTree", "\"%s\" not fully parsed", obj.class_name.c_str()); - in.skip_object(true); - } + std::shared_ptr parse_vob_tree(ReadArchive& in, GameVersion version) { + // TODO(lmichaelis): Make this dynamic cast faster! + auto object = std::dynamic_pointer_cast(in.read_object(version)); auto child_count = static_cast(in.read_int()); if (object == nullptr) { @@ -227,8 +37,6 @@ namespace zenkit { } object->children.reserve(child_count); - object->id = obj.index; - object->type = type; for (auto i = 0u; i < child_count; ++i) { auto child = parse_vob_tree(in, version);