Files
shenandClaude Sonnet 5 47baf6c0c6 Metin2 game client (P0–P11) + mobile asset pipeline
Networked client on the existing Godot 4.7 + libgr2 renderer:
- net: m2dev wire protocol (libsodium KX + XChaCha20), auth/select/game
  phases, EntityStore world model, ~all GC/CG headers. char create/delete,
  private shop / mall / cube, SHOP_GC_START_EX, guild, party (+ CG_PARTY_SET_STATE),
  quests, dragon soul, refine, safebox, exchange.
- UI: in-game windows migrated 1:1 from the reference uiscript/root .py —
  char status (/stat), inventory+equipment, select-item ([SELECT_ITEM] quest
  token), system-option + game-option + ESC system menu, private-shop 39-grid,
  party info board, shop tabs, atlas, minimap, quickbar, chat, …
- EterGrnLib polish: GR2 material blend/two-sided, LOD crossfade, motion-event
  dispatch, contact shadow, ray-AABB picking, weapon grip pre-transform.

Portable asset IO (A1) — all extension/libgr2/formats/mtproto reads routed
through godot::FileAccess (res:// PCK works on iOS/Android); standalone-lib
*_path() kept for the non-Godot CTests. AssetResolver + PropertyRegistry
switched to a baked index (bake_asset_index.gd) instead of std::filesystem.

Mobile builds: build-{android,ios}.sh, export-android.sh, pack-assets.sh,
gen-debug-keystore.sh. Assets ship as a zip mounted at runtime by
project/asset_pack.gd (adb push now; HTTP download is a drop-in later).

ctest 10/10, 34 GDScript suites, macOS/iOS/Android all build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EJxkHiNKS4kybHS3XKyAJ
2026-08-31 20:02:12 +09:00

107 lines
4.2 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "terrain_files.h"
#include "m2_tokvec.h" // read_file
#include <cstring>
namespace fmt {
namespace {
uint16_t rd_u16(const uint8_t* p) { return uint16_t(p[0]) | (uint16_t(p[1]) << 8); }
} // namespace
bool load_height_map(const std::string& path, HeightMap& out, std::string* err) {
std::string buf;
if (!read_file(path, buf)) { if (err) *err = "打不开 " + path; return false; }
const size_t need = size_t(HEIGHTMAP_RAW_XY) * HEIGHTMAP_RAW_XY * 2;
if (buf.size() != need) {
if (err) *err = "height.raw 大小 " + std::to_string(buf.size()) +
" != " + std::to_string(need) + "131*131*2";
return false;
}
out.raw.resize(size_t(HEIGHTMAP_RAW_XY) * HEIGHTMAP_RAW_XY);
const auto* p = reinterpret_cast<const uint8_t*>(buf.data());
for (size_t i = 0; i < out.raw.size(); ++i) out.raw[i] = rd_u16(p + i * 2);
return true;
}
bool load_tile_map(const std::string& path, TileMap& out, std::string* err) {
std::string buf;
if (!read_file(path, buf)) { if (err) *err = "打不开 " + path; return false; }
const size_t need = size_t(TILEMAP_RAW_XY) * TILEMAP_RAW_XY;
if (buf.size() != need) {
if (err) *err = "tile.raw 大小 " + std::to_string(buf.size()) +
" != " + std::to_string(need) + "258*258";
return false;
}
out.raw.assign(buf.begin(), buf.end());
return true;
}
bool load_attr_map(const std::string& path, AttrMap& out, std::string* err) {
std::string buf;
if (!read_file(path, buf)) { if (err) *err = "打不开 " + path; return false; }
const size_t payload = size_t(ATTRMAP_XY) * ATTRMAP_XY;
if (buf.size() != 6 + payload) {
if (err) *err = "attr.atr 大小 " + std::to_string(buf.size()) +
" != " + std::to_string(6 + payload) + "6B 头 + 256*256";
return false;
}
const auto* p = reinterpret_cast<const uint8_t*>(buf.data());
uint16_t magic = rd_u16(p), w = rd_u16(p + 2), h = rd_u16(p + 4);
if (magic != ATTR_MAGIC) {
if (err) *err = "attr.atr magic " + std::to_string(magic) + " != 2634";
return false;
}
if (w != ATTRMAP_XY || h != ATTRMAP_XY) {
if (err) *err = "attr.atr 尺寸头 " + std::to_string(w) + "x" + std::to_string(h) +
" != 256x256";
return false;
}
out.data.assign(buf.begin() + 6, buf.end());
return true;
}
bool load_water_map(const std::string& path, WaterMap& out, std::string* err) {
std::string buf;
if (!read_file(path, buf)) { if (err) *err = "打不开 " + path; return false; }
if (buf.size() < 7) { if (err) *err = "water.wtr 太小"; return false; }
const auto* p = reinterpret_cast<const uint8_t*>(buf.data());
uint16_t magic = rd_u16(p), w = rd_u16(p + 2), h = rd_u16(p + 4);
uint8_t layers = p[6];
if (magic != WATER_MAGIC) {
if (err) *err = "water.wtr magic " + std::to_string(magic) + " != 5426";
return false;
}
if (w != WATERMAP_XY || h != WATERMAP_XY) {
if (err) *err = "water.wtr 尺寸头 != 128x128";
return false;
}
const size_t ids = size_t(WATERMAP_XY) * WATERMAP_XY;
const size_t rest = buf.size() - 7;
// Terrain.cpp 接受两种:u16 层高 或 long(u32) 层高。
size_t hsize = 0;
if (rest == ids + size_t(layers) * 2) hsize = 2;
else if (rest == ids + size_t(layers) * 4) hsize = 4;
else if (rest == ids && layers == 0) hsize = 0;
else {
if (err) *err = "water.wtr 数据段大小 " + std::to_string(rest) +
" 与 layers=" + std::to_string(layers) + " 不符";
return false;
}
out.layer_count = layers;
out.ids.assign(buf.begin() + 7, buf.begin() + 7 + ids);
out.heights.clear();
const auto* hp = p + 7 + ids;
for (int i = 0; i < layers; ++i) {
if (hsize == 2) out.heights.push_back((int16_t)rd_u16(hp + i * 2));
else if (hsize == 4)
out.heights.push_back((int32_t)(uint32_t(hp[i * 4]) | (uint32_t(hp[i * 4 + 1]) << 8) |
(uint32_t(hp[i * 4 + 2]) << 16) |
(uint32_t(hp[i * 4 + 3]) << 24)));
}
return true;
}
} // namespace fmt