#pragma once // EntityStore — headless model of the networked world: turns game-phase packets // (GC_MAIN_CHARACTER / GC_CHARACTER_ADD[2] / GC_CHARACTER_DEL / GC_MOVE / // GC_CHAT / GC_CHARACTER_UPDATE / GC_FLY_TARGETING) into entity state + // interpolated positions. // No Godot / engine dependency (unit-testable). A bridge layer polls // drain_changes() / drain_chat() and mirrors entities into Metin2World. // // Positions are Metin2 cm (server space). func mirrors CInstanceBase::FUNC_*. #include "wire.h" #include #include #include #include namespace mtnet { enum : uint8_t { FUNC_WAIT = 0, FUNC_MOVE = 1, FUNC_ATTACK = 2, FUNC_COMBO = 3, FUNC_MOB_SKILL = 4, FUNC_EMOTION = 5, FUNC_SKILL = 0x80, }; struct Entity { uint32_t vid = 0; uint16_t race = 0; uint8_t ch_type = 0; // CHRTYPE: 0 PC, 1 NPC, 2 MONSTER, 3 STONE, 4 WARP, ... std::string name; uint16_t parts[CHR_EQUIPPART_NUM] = {0, 0, 0, 0}; bool is_main = false; float x = 0, y = 0, z = 0; // current (interpolated) position, cm float angle = 0; uint8_t func = FUNC_WAIT; uint8_t position = 0; uint8_t walk_mode = 0; // 0 walk, 1 run (server's WALKMODE_*) uint32_t fly_target_vid = 0; int32_t fly_target_x = 0, fly_target_y = 0; bool fly_target_set = false; uint16_t moving_speed = 0; bool moving = false; float sx = 0, sy = 0, tx = 0, ty = 0; uint32_t move_start_ms = 0; uint32_t move_dur_ms = 0; // combat / status (0 = unknown until the server sends it) uint8_t attack_speed = 0; // GC_CHARACTER_ADD[2]/UPDATE bAttackSpeed (x100) int32_t hp = 0, max_hp = 0; int32_t sp = 0, max_sp = 0; int32_t level = 0; bool dead = false; bool stunned = false; uint32_t mount_vnum = 0; // 0 = on foot (GC_MOUNT / GC_CHAR_ADD_INFO) int32_t guild = 0; int16_t alignment = 0; uint8_t pk_mode = 0; }; // --- P9 world systems ---------------------------------------------------- // GC_WARP — teleport target. same_server() -> just move the player; otherwise // the caller must reconnect to addr:port (P10). struct WarpCue { int32_t x = 0, y = 0; int32_t addr = 0; uint16_t port = 0; bool same_server() const { return addr == 0; } }; // One atlas/minimap NPC entry (GC_NPC_POSITION). struct NPCMark { uint8_t type = 0; uint32_t vnum = 0; std::string name; int32_t x = 0, y = 0; }; // A quest/world marker (GC_TARGET_CREATE/UPDATE/DELETE). struct WorldMarker { int32_t id = 0; std::string name; uint32_t vid = 0; uint8_t type = 0; // CREATE_TARGET_TYPE_* int32_t x = 0, y = 0; }; // One floating damage number (GC_DAMAGE_INFO). struct DamageEvent { uint32_t vid = 0; uint8_t flag = 0; // DAMAGE_* bits int32_t amount = 0; }; // A one-shot combat/emote motion (GC_MOTION). struct MotionEvent { uint32_t vid = 0; uint32_t victim_vid = 0; uint16_t motion = 0; }; // A mining animation broadcast (GC_DIG_MOTION). struct DigMotionEvent { uint32_t vid = 0; uint32_t target_vid = 0; uint8_t count = 0; }; struct FishingEvent { uint8_t subheader = 0; uint32_t info = 0; uint8_t dir = 0; }; struct DungeonEvent { uint8_t subheader = 0; int32_t x = 0; int32_t y = 0; bool has_destination = false; }; struct LandArea { uint32_t id = 0; int32_t x = 0, y = 0; int32_t width = 0, height = 0; uint32_t guild_id = 0; }; struct Observer { uint32_t vid = 0; int32_t x = 0, y = 0; // server centimetres }; struct ObserverEvent { enum Kind { Add, Remove, Move } kind = Add; uint32_t vid = 0; int32_t x = 0, y = 0; }; // A buff/debuff on the local player (GC_AFFECT_ADD/REMOVE). struct Affect { uint32_t type = 0; uint8_t point_idx = 0; int32_t value = 0; uint32_t flag = 0; int32_t duration = 0; }; struct AffectChange { uint32_t type = 0; bool added = false; }; // A one-shot special/specific effect to play on an entity (GC_SPECIAL/SPECIFIC_EFFECT). struct EffectCue { uint32_t vid = 0; int32_t special = -1; // >=0 for GC_SPECIAL_EFFECT (built-in id) std::string file; // set for GC_SPECIFIC_EFFECT (.mse path) }; // A projectile (GC_CREATE_FLY): type, from vid, to vid. struct FlyCue { uint8_t type = 0; uint32_t start_vid = 0; uint32_t end_vid = 0; }; // A server broadcast that sets or appends a shooter's fly target. struct FlyTargetCue { uint32_t shooter_vid = 0; uint32_t target_vid = 0; int32_t x = 0; int32_t y = 0; bool append = false; }; // An NPC dialog to render (GC_SCRIPT): skin + raw EventManager script text. struct ScriptCue { uint8_t skin = 0; std::string text; }; // A yes/no prompt (GC_QUEST_CONFIRM). struct ConfirmCue { std::string msg; int32_t timeout = 0; uint32_t request_pid = 0; }; // One quest-log entry (GC_QUEST_INFO, flag-driven). struct QuestInfo { uint16_t index = 0; uint8_t flag = 0; bool begin = false; std::string title; std::string clock_name; int32_t clock_value = 0; std::string counter_name; int32_t counter_value = 0; std::string icon; }; // One item in a slot (inventory / equipment). vnum 0 = empty. struct Item { uint32_t vnum = 0; uint8_t count = 0; uint32_t flags = 0; uint32_t anti_flags = 0; int32_t sockets[ITEM_SOCKET_SLOT_MAX_NUM] = {0, 0, 0}; ItemAttr attrs[ITEM_ATTRIBUTE_SLOT_MAX_NUM] = {}; bool empty() const { return vnum == 0; } }; // Snapshot supplied by GC_VIEW_EQUIP when inspecting another character. struct ViewedEquipment { uint32_t vid = 0; Item items[VIEW_EQUIP_WEAR_MAX_NUM] = {}; }; // --- P8 party (GC_PARTY_*) -------------------------------------------------- struct PartyMember { uint32_t pid = 0; uint32_t vid = 0; // 0 until GC_PARTY_LINK std::string name; uint8_t state = 0; // bit0 = leader (this fork's server) uint8_t hp_pct = 0; // 0..100 int16_t affects[PARTY_AFFECT_SLOT_MAX_NUM] = {0}; bool leader() const { return (state & 1) != 0; } }; // --- P8 messenger friend list (GC_MESSENGER) ------------------------------ struct Friend { std::string name; bool online = false; }; // --- P8 NPC shop (GC_SHOP) ---------------------------------------------------- struct ShopEntry { uint32_t vnum = 0; uint32_t price = 0; uint8_t count = 0; uint8_t pos = 0; // slot index within its tab (0..SHOP_HOST_ITEM_MAX_NUM-1) }; // One shelf/tab of a SHOP_GC_START_EX shop. A plain SHOP_GC_START shop is // modelled as a single unnamed tab. struct ShopTab { std::string name; uint8_t coin_type = 0; std::vector items; }; // --- P8 exchange / trade (GC_EXCHANGE) ------------------------------------- struct ExchangeSlot { uint32_t vnum = 0; uint8_t count = 0; }; struct ExchangeState { bool active = false; uint32_t partner_vid = 0; ExchangeSlot self_items[12] = {}; ExchangeSlot peer_items[12] = {}; int64_t self_gold = 0; int64_t peer_gold = 0; bool self_accept = false; bool peer_accept = false; }; // --- guild (GC_GUILD) -------------------------------------------------------- struct GuildMember { uint32_t pid = 0; uint8_t grade = 0; bool is_general = false; uint8_t job = 0; uint8_t level = 0; uint32_t offer = 0; std::string name; }; struct GuildGrade { std::string name; uint8_t auth = 0; }; struct GuildState { bool in_guild = false; uint32_t id = 0; std::string name; uint8_t level = 0; uint32_t exp = 0; uint32_t gold = 0; uint16_t member_count = 0; uint16_t max_member_count = 0; uint32_t master_pid = 0; bool has_land = false; }; // GUILD_GC_SKILL_INFO — the guild-skill page. struct GuildSkillState { bool valid = false; uint8_t skill_point = 0; uint8_t levels[GUILD_SKILL_MAX_NUM] = {}; uint16_t guild_point = 0; uint16_t max_guild_point = 0; }; // GUILD_GC_WAR — our current guild-war status against one opponent. struct GuildWarStatus { uint32_t opp_guild_id = 0; uint8_t type = 0; uint8_t state = GUILD_WAR_NONE; }; // GUILD_GC_WAR_POINT — a scoreboard delta. struct GuildWarScore { uint32_t gain_guild_id = 0; uint32_t opp_guild_id = 0; int32_t point = 0; }; // --- dragon-soul refine (GC_DRAGON_SOUL_REFINE) -------------------------- struct DragonSoulCue { uint8_t sub_type = 0; // DS_SUB_* (OPEN / REFINE_SUCCEED / REFINE_FAIL_*) uint8_t window = 0; // affected item position (success/fail) uint16_t cell = 0; }; // --- refine (GC_REFINE_INFORMATION) ---------------------------------------- struct RefineCue { uint8_t type = 0; uint8_t pos = 0; // inventory cell of the item uint32_t src_vnum = 0; uint32_t result_vnum = 0; int32_t cost = 0; int32_t prob = 0; // success % struct Mat { uint32_t vnum = 0; int32_t count = 0; } materials[5] = {}; uint8_t material_count = 0; }; // An item lying in the world (GC_ITEM_GROUND_ADD). struct GroundItem { uint32_t vid = 0; uint32_t vnum = 0; float x = 0, y = 0, z = 0; // server cm std::string owner; }; // One inventory/equipment slot changed (window = WINDOW_INVENTORY / _EQUIPMENT). struct InvChange { uint8_t window = 0; uint16_t cell = 0; }; // A ground item appeared / vanished. struct GroundChange { uint32_t vid = 0; bool added = false; }; // "You picked up N x " (GC_ITEM_GET) or "someone used " (GC_ITEM_USE). struct ItemEvent { enum Kind { Get, Use }; Kind kind = Get; uint32_t vnum = 0; uint8_t count = 0; std::string from; // GC_ITEM_GET only }; struct PvpRelation { uint32_t src_vid = 0; uint32_t dst_vid = 0; uint8_t mode = 0; }; struct LoverInfo { std::string name; uint8_t love_point = 0; bool valid = false; }; // The local player's full stat array (GC_PLAYER_POINTS), indexed by EPointTypes. struct PlayerPoints { int32_t v[256] = {0}; int32_t hp() const { return v[POINT_HP]; } int32_t max_hp() const { return v[POINT_MAX_HP]; } int32_t sp() const { return v[POINT_SP]; } int32_t max_sp() const { return v[POINT_MAX_SP]; } int32_t level() const { return v[POINT_LEVEL]; } int32_t exp() const { return v[POINT_EXP]; } int32_t next_exp() const { return v[POINT_NEXT_EXP]; } int32_t gold() const { return v[POINT_GOLD]; } int32_t energy() const { return v[POINT_ENERGY]; } int32_t energy_end_time() const { return v[POINT_ENERGY_END_TIME]; } }; class EntityStore { public: enum class ChangeKind { Spawn, Despawn, Move, MainSet, Info }; struct Change { ChangeKind kind; uint32_t vid; }; struct ChatMsg { uint8_t type = 0; // EChatType; CHAT_TYPE_WHISPER for whispers uint32_t vid = 0; // speaker vid (0 for whisper / system) std::string text; std::string from; // whisper sender name (empty otherwise) uint8_t sub = 0; // whisper: WHISPER_TYPE_* }; void set_now(uint32_t now_ms) { m_now = now_ms; } // Feed one complete game-phase packet. `body` points at the packet start // (header/length included); `len` == that length. Unknown headers ignored. void apply(uint16_t header, const void *body, uint16_t len); // Advance interpolation of moving entities to m_now. void tick(); const Entity *get(uint32_t vid) const; uint32_t main_vid() const { return m_main_vid; } std::vector vids() const; size_t size() const { return m_ents.size(); } // local player's full stat block; valid once GC_PLAYER_POINTS has arrived. const PlayerPoints &points() const { return m_points; } // local player's skill levels (index = skill id, 0..SKILL_MAX_NUM-1). uint8_t skill_level(int id) const { return (id >= 0 && id < SKILL_MAX_NUM) ? m_skills[id] : 0; } // 0 normal / 1 master / 2 grand master / 3 perfect master (GC_SKILL_LEVEL_NEW). uint8_t skill_master(int id) const { return (id >= 0 && id < SKILL_MAX_NUM) ? m_skill_master[id] : 0; } uint8_t skill_group() const { return m_skill_group; } bool skill_group_dirty() { bool d = m_skill_group_dirty; m_skill_group_dirty = false; return d; } bool skills_dirty() { bool d = m_skills_dirty; m_skills_dirty = false; return d; } // local player's quickslots (GC_QUICKSLOT_*), 0..QUICKSLOT_MAX_NUM-1. QuickSlot quickslot(int pos) const { return (pos >= 0 && pos < QUICKSLOT_MAX_NUM) ? m_quickslots[pos] : QuickSlot{}; } bool quickslots_dirty() { bool d = m_quickslots_dirty; m_quickslots_dirty = false; return d; } std::vector drain_cooldown_ends() { auto v = std::move(m_cooldown_ends); m_cooldown_ends.clear(); return v; } std::vector drain_fly_cues() { auto v = std::move(m_fly_cues); m_fly_cues.clear(); return v; } std::vector drain_fly_target_cues() { auto v = std::move(m_fly_target_cues); m_fly_target_cues.clear(); return v; } std::vector drain_scripts() { auto v = std::move(m_scripts); m_scripts.clear(); return v; } std::vector drain_confirms() { auto v = std::move(m_confirms); m_confirms.clear(); return v; } // vids of quest-log entries changed since last drain. std::vector drain_quest_changes() { auto v = std::move(m_quest_changes); m_quest_changes.clear(); return v; } const QuestInfo *quest(uint16_t index) const { auto it = m_quests.find(index); return it == m_quests.end() ? nullptr : &it->second; } std::vector quest_indices() const { std::vector v; v.reserve(m_quests.size()); for (auto &kv : m_quests) { v.push_back(kv.first); } return v; } // currently-selected target's HP percent (GC_TARGET_INFO); 0 vid = none. uint32_t target_vid() const { return m_target_vid; } uint8_t target_hp_pct() const { return m_target_hp_pct; } // --- P8 party --- bool in_party() const { return !m_party.empty(); } std::vector party_pids() const { std::vector v; v.reserve(m_party.size()); for (auto &kv : m_party) { v.push_back(kv.first); } return v; } const PartyMember *party_member(uint32_t pid) const { auto it = m_party.find(pid); return it == m_party.end() ? nullptr : &it->second; } uint8_t party_distribute_mode() const { return m_party_mode; } bool party_dirty() { bool d = m_party_dirty; m_party_dirty = false; return d; } std::vector drain_party_invites() { auto v = std::move(m_party_invites); m_party_invites.clear(); return v; } // --- P8 messenger --- std::vector friends() const { std::vector v; v.reserve(m_friends.size()); for (auto &kv : m_friends) { v.push_back(kv.second); } return v; } bool friends_dirty() { bool d = m_friends_dirty; m_friends_dirty = false; return d; } // --- P8 NPC shop --- bool shop_open() const { return m_shop_open; } uint32_t shop_vid() const { return m_shop_vid; } const std::vector &shop_items() const { return m_shop_items; } // SHOP_GC_START_EX shelves; empty for a plain SHOP_GC_START shop (use shop_items()). const std::vector &shop_tabs() const { return m_shop_tabs; } bool shop_dirty() { bool d = m_shop_dirty; m_shop_dirty = false; return d; } std::vector drain_shop_errors() { auto v = std::move(m_shop_errors); m_shop_errors.clear(); return v; } // --- P8 exchange --- const ExchangeState &exchange() const { return m_exchange; } bool exchange_dirty() { bool d = m_exchange_dirty; m_exchange_dirty = false; return d; } // --- P8 safebox --- bool safebox_open() const { return m_safebox_open; } int safebox_size() const { return m_safebox_size; } int64_t safebox_gold() const { return m_safebox_gold; } const Item &safebox_slot(int cell) const; bool safebox_dirty() { bool d = m_safebox_dirty; m_safebox_dirty = false; return d; } // --- item-mall (창고몰) --- bool mall_open() const { return m_mall_open; } int mall_size() const { return m_mall_size; } const Item &mall_slot(int cell) const; bool mall_dirty() { bool d = m_mall_dirty; m_mall_dirty = false; return d; } // --- cube (제작) --- struct CubeResultEntry { uint32_t vnum = 0; int count = 0; }; struct CubeMaterialSlot { uint32_t vnum = 0; int count = 0; }; struct CubeRecipe { // one craftable output + its materials uint32_t result_vnum = 0; int result_count = 0; int64_t gold = 0; std::vector> material_groups; // any-of groups }; struct CubeState { bool open = false; uint32_t npc_vnum = 0; int64_t need_gold = 0; // gold the current pending craft needs uint32_t need_item_vnum = 0; // last "cube info" hint int need_item_count = 0; std::vector results; // r_list: what this NPC can make std::vector recipes; // m_info: materials per result }; enum class CubeEvent { Opened, Closed, InfoChanged, Success, Fail }; const CubeState &cube() const { return m_cube; } std::vector drain_cube_events() { auto v = std::move(m_cube_events); m_cube_events.clear(); return v; } // last Success payload (valid right after a CubeEvent::Success is drained) CubeResultEntry cube_last_success() const { return m_cube_last_success; } // --- guild --- const GuildState &guild() const { return m_guild; } std::vector guild_members() const { std::vector v; v.reserve(m_guild_members.size()); for (auto &kv : m_guild_members) { v.push_back(kv.second); } return v; } const GuildGrade &guild_grade(int i) const { static const GuildGrade kEmpty; return (i >= 0 && i < 16) ? m_guild_grades[i] : kEmpty; } bool guild_dirty() { bool d = m_guild_dirty; m_guild_dirty = false; return d; } // --- guild war / guild skill --- const GuildSkillState &guild_skill() const { return m_guild_skill; } bool guild_skill_dirty() { bool d = m_guild_skill_dirty; m_guild_skill_dirty = false; return d; } const GuildWarStatus &guild_war() const { return m_guild_war; } // active GvG pairs (src,dst); order not significant. const std::vector &guild_wars() const { return m_guild_wars; } bool guild_war_dirty() { bool d = m_guild_war_dirty; m_guild_war_dirty = false; return d; } std::string guild_name(uint32_t id) const { auto it = m_guild_names.find(id); return it == m_guild_names.end() ? std::string() : it->second; } // one-shot WAR state transitions (declare/accept/start/end) for toasts. std::vector drain_guild_war_events() { auto v = std::move(m_guild_war_events); m_guild_war_events.clear(); return v; } std::vector drain_guild_war_scores() { auto v = std::move(m_guild_war_scores); m_guild_war_scores.clear(); return v; } // --- refine --- std::vector drain_refine_cues() { auto v = std::move(m_refine_cues); m_refine_cues.clear(); return v; } std::vector drain_ds_cues() { auto v = std::move(m_ds_cues); m_ds_cues.clear(); return v; } const Item &dragon_soul_slot(int cell) const; // --- P9 world systems --- std::vector drain_warps() { auto v = std::move(m_warps); m_warps.clear(); return v; } // server wall-clock (unix seconds) as of the last GC_TIME; add the elapsed // real time yourself for a running clock. int64_t server_time() const { return m_server_time; } bool take_time_dirty() { bool d = m_time_dirty; m_time_dirty = false; return d; } int channel() const { return m_channel; } bool take_channel_dirty() { bool d = m_channel_dirty; m_channel_dirty = false; return d; } const std::vector &npc_marks() const { return m_npc_marks; } bool take_npc_marks_dirty() { bool d = m_npc_marks_dirty; m_npc_marks_dirty = false; return d; } std::vector markers() const { std::vector v; v.reserve(m_markers.size()); for (auto &kv : m_markers) { v.push_back(kv.second); } return v; } bool take_markers_dirty() { bool d = m_markers_dirty; m_markers_dirty = false; return d; } std::vector drain_mount_changes() { auto v = std::move(m_mount_changes); m_mount_changes.clear(); return v; } // items: normal inventory, 24 wear positions, and the independent 4x4 belt inventory. const Item &inv_slot(int cell) const; const Item &equip_slot(int wear) const; const Item &belt_slot(int cell) const; const Item &item_slot(uint8_t window, int cell) const; const ViewedEquipment *viewed_equipment(uint32_t vid) const; const GroundItem *ground(uint32_t vid) const; std::vector ground_vids() const; std::vector drain_inv() { auto v = std::move(m_inv_changes); m_inv_changes.clear(); return v; } std::vector drain_ground() { auto v = std::move(m_ground_changes); m_ground_changes.clear(); return v; } std::vector drain_view_equipment_changes() { auto v = std::move(m_view_equipment_changes); m_view_equipment_changes.clear(); return v; } std::vector drain_item_events() { auto v = std::move(m_item_events); m_item_events.clear(); return v; } std::vector drain_pvp_changes() { auto v = std::move(m_pvp_changes); m_pvp_changes.clear(); return v; } std::vector pvp_relations() const; bool take_duel_started() { bool started = m_duel_started; m_duel_started = false; return started; } const LoverInfo &lover() const { return m_lover; } bool take_lover_dirty() { bool dirty = m_lover_dirty; m_lover_dirty = false; return dirty; } std::vector drain_changes() { auto v = std::move(m_changes); m_changes.clear(); return v; } std::vector drain_chat() { auto v = std::move(m_chat); m_chat.clear(); return v; } // vids whose hp/sp/level/dead/stunned changed since last drain (deduped). std::vector drain_vitals() { auto v = std::move(m_vitals); m_vitals.clear(); return v; } std::vector drain_damage() { auto v = std::move(m_damage); m_damage.clear(); return v; } std::vector drain_motions() { auto v = std::move(m_motions); m_motions.clear(); return v; } std::vector drain_dig_motions() { auto v = std::move(m_dig_motions); m_dig_motions.clear(); return v; } std::vector drain_fishing_events() { auto v = std::move(m_fishing_events); m_fishing_events.clear(); return v; } std::vector drain_dungeon_events() { auto v = std::move(m_dungeon_events); m_dungeon_events.clear(); return v; } const std::vector &land_areas() const { return m_land_areas; } bool take_land_dirty() { bool d = m_land_dirty; m_land_dirty = false; return d; } std::vector observers() const { std::vector v; v.reserve(m_observers.size()); for (auto &kv : m_observers) v.push_back(kv.second); return v; } std::vector drain_observer_events() { auto v = std::move(m_observer_events); m_observer_events.clear(); return v; } std::vector drain_affects() { auto v = std::move(m_affect_changes); m_affect_changes.clear(); return v; } std::vector drain_effect_cues() { auto v = std::move(m_effect_cues); m_effect_cues.clear(); return v; } std::vector affects() const { std::vector v; v.reserve(m_affects.size()); for (auto &kv : m_affects) { v.push_back(kv.second); } return v; } // true once since GC_PLAYER_POINTS last arrived (consumes the flag). bool take_points_dirty() { bool d = m_points_dirty; m_points_dirty = false; return d; } // true once since GC_TARGET_INFO last arrived (consumes the flag). bool take_target_dirty() { bool d = m_target_dirty; m_target_dirty = false; return d; } private: Entity &touch(uint32_t vid, bool &created); void start_move(Entity &e, float tx, float ty, uint32_t start_ms, uint32_t dur_ms, uint8_t func); void mark_vitals(uint32_t vid); Item *mut_slot(uint8_t window, uint16_t cell); std::unordered_map m_ents; uint32_t m_main_vid = 0; uint32_t m_now = 0; std::vector m_changes; std::vector m_chat; std::vector m_vitals; std::vector m_damage; std::vector m_motions; std::vector m_dig_motions; std::vector m_fishing_events; std::vector m_dungeon_events; std::vector m_land_areas; bool m_land_dirty = false; std::unordered_map m_observers; std::vector m_observer_events; std::unordered_map m_affects; std::vector m_affect_changes; std::vector m_effect_cues; std::vector m_fly_cues; std::vector m_fly_target_cues; uint8_t m_skills[SKILL_MAX_NUM] = {0}; uint8_t m_skill_master[SKILL_MAX_NUM] = {0}; uint8_t m_skill_group = 0; bool m_skill_group_dirty = false; bool m_skills_dirty = false; QuickSlot m_quickslots[QUICKSLOT_MAX_NUM] = {}; bool m_quickslots_dirty = false; std::vector m_cooldown_ends; std::vector m_scripts; std::vector m_confirms; std::unordered_map m_quests; std::vector m_quest_changes; PlayerPoints m_points; bool m_points_dirty = false; uint32_t m_target_vid = 0; uint8_t m_target_hp_pct = 0; bool m_target_dirty = false; Item m_inventory[INVENTORY_MAX_NUM]; Item m_equipment[WEAR_MAX_NUM]; Item m_belt[BELT_INVENTORY_MAX_NUM]; std::unordered_map m_view_equipment; std::unordered_map m_ground; std::vector m_inv_changes; std::vector m_ground_changes; std::vector m_view_equipment_changes; std::vector m_item_events; std::unordered_map m_pvp; std::vector m_pvp_changes; bool m_duel_started = false; LoverInfo m_lover; bool m_lover_dirty = false; // P8 social / shop / storage std::unordered_map m_party; uint8_t m_party_mode = 0; bool m_party_dirty = false; std::vector m_party_invites; std::unordered_map m_friends; bool m_friends_dirty = false; bool m_shop_open = false; uint32_t m_shop_vid = 0; std::vector m_shop_items; // == m_shop_tabs[0].items when tabs present std::vector m_shop_tabs; bool m_shop_dirty = false; std::vector m_shop_errors; ExchangeState m_exchange; bool m_exchange_dirty = false; Item m_safebox[SAFEBOX_MAX_NUM]; bool m_safebox_open = false; int m_safebox_size = 0; int64_t m_safebox_gold = 0; bool m_safebox_dirty = false; Item m_mall[MALL_MAX_NUM]; bool m_mall_open = false; int m_mall_size = 0; bool m_mall_dirty = false; CubeState m_cube; std::vector m_cube_events; CubeResultEntry m_cube_last_success; void apply_server_command(const std::string &line); // P9 world systems std::vector m_warps; int64_t m_server_time = 0; bool m_time_dirty = false; int m_channel = 0; bool m_channel_dirty = false; std::vector m_npc_marks; bool m_npc_marks_dirty = false; std::unordered_map m_markers; bool m_markers_dirty = false; std::vector m_mount_changes; // guild / refine GuildState m_guild; std::unordered_map m_guild_members; GuildGrade m_guild_grades[16]; bool m_guild_dirty = false; GuildSkillState m_guild_skill; bool m_guild_skill_dirty = false; GuildWarStatus m_guild_war; std::vector m_guild_wars; bool m_guild_war_dirty = false; std::unordered_map m_guild_names; std::vector m_guild_war_events; std::vector m_guild_war_scores; std::vector m_refine_cues; // dragon-soul refine Item m_dragon_soul[DRAGON_SOUL_MAX_NUM]; std::vector m_ds_cues; }; } // namespace mtnet