40250 classic: increment 50 (Phase 1 W1) — §1.5 client version, §1.10 text codec, §2.2 two-packet merge
§1.5 CG_CLIENT_VERSION: send_client_version() now branches on locale. EUROPE (this project's locale) sends CGClientVersion2 (header 0xf1, 67B, timestamp "1215955205"); non-EUROPE sends CGClientVersion (0xfd) + CMake-injected MT_BUILD_TIMESTAMP in C __TIMESTAMP__ form. New ClassicSession::set_executable_name() (default "metin2.bin"; TODO(W0): M2Client passes OS::get_executable_path().get_file()). m_version_sent set only after send_fixed() succeeds. wire_classic.h gains CGClientVersion2 + size-table entry. §1.10 fixed-length text: new extension/src/net/text_codec.h — to_wire()/ from_wire_str() apply a hard byte cap without splitting a UTF-8 multibyte sequence; godot::String overloads are __has_include-guarded so mtnet stays godot-free. classic_session.cpp routes 8 fixed-length name/sign/comment fills through to_wire(); parser reads GC_CHAR_ADDITIONAL_INFO name via from_wire_str(). New net_text_codec_test (ctest net.text_codec). Locale codepage (CP949/CP1252) transcoding remains a follow-up; wire bytes are currently UTF-8 (ASCII round-trips). §2.2 two-packet PC/NPC merge: ClassicParser GC_CHARACTER_ADD drops invisible races (20025/20038/20039); PC/NPC only stash a bare Entity into m_pending_actor; everything else spawns immediately with a name from root/npclist.txt (new npc_names.h + set_npclist_path(); $MT_ASSETS/$M2_ASSETS fallback; TODO(W0): M2Client passes the asset root). GC_CHAR_ADDITIONAL_INFO with no pending entry records m_last_error + m_unhandled_headers and drops (never half-creates); on a hit it merges the pending Entity with the additional-info's 9 fields into one mut_spawn_full() then erases the pending entry. New pending_actor_count() test hook; net_classic_session_test updated (version assertions -> 0xf1; stash -> merge -> spawn, immediate monster spawn, invisible-race drop, no-pending additional-info is a no-op). Depends on W2 §2.2 steps 4-5 (entity_store mut_char_info touch / mut_shop_sign cleanup) — the merge works with current W2 code as-is. Build + ctest 17/17 (16 + net.text_codec); netbridge_test, p10_test green. Docs: CLIENT-GAP.md W1 row + increment-50 change-log entry; CLIENT-GAP-FIX.md §1.5/§1.10/§2.2 status + facts and C.5 batch record. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014yvAPqPivoY7vmBbzgqK4W
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f32064c74a
commit
b296457e3c
@@ -23,6 +23,11 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
// §1.10: every fixed-length `char name[...]` / sign / comment field below is
|
||||
// filled at the call site via mtnet::to_wire(src, cap) (byte cap, no split
|
||||
// multibyte char) and read back via mtnet::from_wire_str().
|
||||
#include "../text_codec.h"
|
||||
|
||||
namespace mtnet::classic {
|
||||
|
||||
// ------------------------------------------------------------------ constants
|
||||
@@ -52,6 +57,28 @@ inline constexpr int GUILD_MARK_WIDTH = 16;
|
||||
inline constexpr int GUILD_MARK_HEIGHT = 12;
|
||||
inline constexpr int MARK_BLOCK_TOTAL_COUNT = 80;
|
||||
|
||||
// CHRTYPE_* — the domain of Entity::ch_type and packet_add_char.bType
|
||||
// (client GameType.h / server char.h). Values line up with the Entity comment.
|
||||
enum : uint8_t {
|
||||
CHRTYPE_PC = 0,
|
||||
CHRTYPE_NPC = 1,
|
||||
CHRTYPE_MONSTER = 2,
|
||||
CHRTYPE_STONE = 3,
|
||||
CHRTYPE_WARP = 4,
|
||||
CHRTYPE_DOOR = 5,
|
||||
CHRTYPE_BUILDING = 6,
|
||||
CHRTYPE_PET = 7,
|
||||
CHRTYPE_HORSE = 8,
|
||||
CHRTYPE_GOTO = 9,
|
||||
};
|
||||
|
||||
// Races the reference client never spawns — REF/UserInterface/
|
||||
// PythonNetworkStreamPhaseGameActor.cpp:66 IsInvisibleRace(). GC_CHARACTER_ADD
|
||||
// for one of these is dropped before any pending/stash step.
|
||||
inline constexpr bool is_invisible_race(uint16_t race) {
|
||||
return race == 20025 || race == 20038 || race == 20039;
|
||||
}
|
||||
|
||||
// EPacketShopSubHeaders (client Packet.h L1864)
|
||||
enum : uint8_t {
|
||||
SHOP_SUB_START = 0,
|
||||
@@ -466,6 +493,17 @@ struct CGClientVersion {
|
||||
};
|
||||
static_assert(sizeof(CGClientVersion) == 67);
|
||||
|
||||
// CG_CLIENT_VERSION2 (header 0xf1) — TPacketCGClientVersion2. Same layout as
|
||||
// CGClientVersion; the EUROPE / non-YMIR locale sends THIS one with a fixed
|
||||
// timestamp "1215955205" (REF/.../PythonNetworkStreamPhaseGame.cpp:4155,
|
||||
// Locale.cpp:186). The 0xfd variant carries the C __TIMESTAMP__ instead.
|
||||
struct CGClientVersion2 {
|
||||
uint8_t header;
|
||||
char filename[33];
|
||||
char timestamp[33];
|
||||
};
|
||||
static_assert(sizeof(CGClientVersion2) == 67);
|
||||
|
||||
struct GCPlayerCreateSuccess { uint8_t header; uint8_t account_slot; SimplePlayer player; };
|
||||
static_assert(sizeof(GCPlayerCreateSuccess) == 2 + 63);
|
||||
struct GCPlayerCreateFailure { uint8_t header; uint8_t type; };
|
||||
@@ -1221,8 +1259,8 @@ constexpr int packet_size_cg(uint8_t h) {
|
||||
case HDR_CG_CHARACTER_DELETE: return sizeof(CGPlayerDelete);
|
||||
case HDR_CG_CHANGE_NAME: return sizeof(CGChangeName);
|
||||
case HDR_CG_EMPIRE: return sizeof(CGEmpire);
|
||||
case HDR_CG_CLIENT_VERSION:
|
||||
case HDR_CG_CLIENT_VERSION2: return sizeof(CGClientVersion);
|
||||
case HDR_CG_CLIENT_VERSION: return sizeof(CGClientVersion);
|
||||
case HDR_CG_CLIENT_VERSION2: return sizeof(CGClientVersion2);
|
||||
case HDR_CG_ENTERGAME: return sizeof(CGEnterGame);
|
||||
case HDR_CG_MOVE: return sizeof(CGMove);
|
||||
case HDR_CG_ATTACK: return sizeof(CGAttack);
|
||||
|
||||
Reference in New Issue
Block a user