完善客户端功能并同步差距文档

This commit is contained in:
shenlei
2026-09-03 18:40:01 +09:00
parent f93a172296
commit 13ccb8d02d
135 changed files with 21881 additions and 1259 deletions
+67 -4
View File
@@ -11,6 +11,7 @@
#include "wire.h"
#include <cstdint>
#include <deque>
#include <string>
#include <unordered_map>
#include <vector>
@@ -27,6 +28,29 @@ enum : uint8_t {
FUNC_SKILL = 0x80,
};
// CLIENT-GAP §3.2: one entry of a remote actor's TCP state queue, mirroring the
// reference CInstanceBase::PushTCPState / StateProcess model. `chk_time` is the
// server command time (TPacketGCMove::dwTime); the entry is released once the
// store's server-frame clock reaches it. `arg` carries the motion index for the
// FUNC_COMBO / FUNC_SKILL cases.
struct StateCmd {
uint32_t chk_time = 0;
float dst_x = 0, dst_y = 0;
float rot = 0;
uint8_t func = FUNC_WAIT;
uint8_t arg = 0;
uint32_t dur_ms = 0; // server-supplied walk duration; 0 = derive from distance/speed
};
// Staleness valve for the server-frame gate. The reference StateProcess has no
// such cap, but our server-frame clock is not yet validated against a live
// 40250 server (§3.2): if chk_time is stamped in a timebase we don't share, a
// naive "wait until frame >= chk_time" would freeze the remote actor forever.
// A chk_time more than this far ahead of the store's frame clock is treated as
// out-of-band and released immediately. Deliberate deviation, documented in
// CLIENT-GAP-FIX.md §3.2.
static constexpr uint32_t STATE_QUEUE_MAX_WAIT_MS = 1000;
struct Entity {
uint32_t vid = 0;
uint16_t race = 0;
@@ -51,13 +75,24 @@ struct Entity {
uint32_t move_start_ms = 0;
uint32_t move_dur_ms = 0;
// CLIENT-GAP §3.2: TCP state queue + the "do this once the walk reaches Dst"
// latch (reference m_kMovAfterFunc / m_dwMovAfterArg). `skip_collision` mirrors
// __EnableSkipCollision(): a walk kicked off by a network state packet ignores
// actor-vs-actor collision until it arrives.
std::deque<StateCmd> state_queue;
uint8_t mov_after_func = FUNC_WAIT;
uint8_t mov_after_arg = 0;
float mov_after_rot = 0;
bool skip_collision = false;
// 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;
bool stunned = false; // server AFFECT_STUN (InstanceBaseEffect.cpp:932)
bool knock_down = false; // knockback motion is playing (CActorInstance::IsKnockDown)
uint32_t mount_vnum = 0; // 0 = on foot (GC_MOUNT / GC_CHAR_ADD_INFO)
int32_t guild = 0;
int16_t alignment = 0;
@@ -262,6 +297,8 @@ struct ShopTab {
struct ExchangeSlot {
uint32_t vnum = 0;
uint8_t count = 0;
int32_t sockets[ITEM_SOCKET_SLOT_MAX_NUM] = {0, 0, 0};
ItemAttr attrs[ITEM_ATTRIBUTE_SLOT_MAX_NUM] = {};
};
struct ExchangeState {
bool active = false;
@@ -458,6 +495,16 @@ public:
};
void set_now(uint32_t now_ms) { m_now = now_ms; }
// CLIENT-GAP §3.2: the server-frame clock the TCP state queue releases against
// (reference ELTimer_GetServerFrameMSec). 0 = "not clock-aligned yet" — while
// it is 0 every queued command is released immediately (degrades to the old
// apply-on-arrival behaviour until the handshake lDelta clock is wired, §3.2/5).
void set_server_frame_ms(uint32_t ms) { m_server_frame_ms = ms; }
uint32_t server_frame_ms() const { return m_server_frame_ms; }
// Release every due entry of every entity's TCP state queue. Called from tick()
// and again right after mut_move() enqueues, so an already-due command applies
// synchronously.
void process_states();
// Feed one complete game-phase packet. `body` points at the packet start
// (header/length included); `len` == that length. Unknown headers ignored.
@@ -491,8 +538,12 @@ public:
void mut_despawn(uint32_t vid);
// angle_deg is the already-decoded compass heading (m2dev: rot*360/256;
// classic: rot*5 — the two wire encodings differ, so decode at the parser).
// CLIENT-GAP §3.2: enqueues a StateCmd on the entity's TCP state queue rather
// than mutating the position directly, then drains anything already due.
// `arg` is the motion index (FUNC_COMBO / FUNC_SKILL); `chk_time` is the
// server command time (0 = release immediately).
void mut_move(uint32_t vid, float angle_deg, uint8_t func, float tx, float ty,
uint32_t dur_ms);
uint32_t dur_ms, uint8_t arg = 0, uint32_t chk_time = 0);
// Full stat block (index = POINT_*). `n` <= 256.
void mut_set_points(const int32_t *pts, int n);
// One stat delta (GC_CHARACTER_POINT_CHANGE equivalent).
@@ -523,6 +574,9 @@ public:
void mut_change_speed(uint32_t vid, uint16_t moving_speed);
void mut_walk_mode(uint32_t vid, uint8_t mode);
void mut_stun(uint32_t vid);
// CLIENT-GAP §3.7 — knockback-motion latch (client-only, animation-bound).
// Set true when a knockdown .msa starts, false when it ends; never from a timer.
void mut_knockdown(uint32_t vid, bool on);
void mut_dead(uint32_t vid);
void mut_motion(uint32_t vid, uint32_t victim_vid, uint16_t motion);
void mut_target(uint32_t vid, uint8_t hp_pct);
@@ -613,7 +667,10 @@ public:
// or deny a pending friend request.
void mut_friend_invite(const std::string &name);
void mut_exchange_start(uint32_t partner_vid);
void mut_exchange_item(bool self, uint8_t slot, uint32_t vnum, uint8_t count);
// `sockets` and `attrs` mirror the 40250 GC_EXCHANGE item payload. They are
// optional for synthetic/server-command updates and default to zero.
void mut_exchange_item(bool self, uint8_t slot, uint32_t vnum, uint8_t count,
const int32_t *sockets = nullptr, const ItemAttr *attrs = nullptr);
void mut_exchange_item_del(bool self, uint8_t slot);
void mut_exchange_gold(bool self, int64_t gold);
void mut_exchange_accept(bool self, bool accepted);
@@ -1098,13 +1155,19 @@ public:
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);
// CLIENT-GAP §3.2 helpers for the TCP state queue.
void drain_state_queue(Entity &e); // release due entries for one entity
void apply_state_cmd(Entity &e, const StateCmd &c); // the FUNC_* switch (1.0 / 50.0 thresholds)
void begin_state_walk(Entity &e, const StateCmd &c, uint8_t after_func); // Src/Dst + skip-collision
static bool can_process_network_state(const Entity &e); // ~ __CanProcessNetworkStatePacket
static bool is_enable_tcp_process(const Entity &e, uint8_t func); // ~ __IsEnableTCPProcess
std::unordered_map<uint32_t, Entity> m_ents;
uint32_t m_main_vid = 0;
uint32_t m_now = 0;
uint32_t m_server_frame_ms = 0;
std::vector<Change> m_changes;
std::vector<uint32_t> m_dirty;
std::string m_bgm_name;