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

203 lines
8.4 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.
// map_probe — SHINSOO-WORLD-RENDERING.md W0 的 load report。
//
// map_probe <assets_root> [map_rel] map_rel 默认 OutdoorA1/metin2_map_a1
//
// 扫一张地图:setting.txt / TextureSet / 每个区块的 6 类文件 / areadata
// 逐项打印「成功 / 失败 + 原因」,末尾给汇总。不加载 .msenv / PropertyW0 剩余项)。
#include "area_data.h"
#include "asset_resolver.h"
#include "environment.h"
#include "m2_coord.h"
#include "map_setting.h"
#include "property.h"
#include "splat.h"
#include "terrain_files.h"
#include "texture_set.h"
#include <cstdio>
#include <cstdlib>
#include <map>
#include <set>
#include <string>
using namespace fmt;
static int g_ok = 0, g_bad = 0;
static void line(const char* tag, bool ok, const std::string& detail) {
std::printf(" [%s] %-28s %s\n", ok ? "OK " : "ERR", tag, detail.c_str());
ok ? ++g_ok : ++g_bad;
}
int main(int argc, char** argv) {
if (argc < 2) {
std::fprintf(stderr, "usage: map_probe <assets_root> [map_rel]\n");
return 2;
}
std::string assets = argv[1];
std::string map_rel = argc > 2 ? argv[2] : "OutdoorA1/metin2_map_a1";
std::string map = assets + "/" + map_rel;
std::printf("== map_probe: %s ==\n", map.c_str());
std::string err;
MapSetting s;
if (!parse_map_setting_file(map + "/setting.txt", s, &err)) {
line("setting.txt", false, err);
return 1;
}
line("setting.txt", true,
"MapSize " + std::to_string(s.map_size_x) + "x" + std::to_string(s.map_size_y) +
" CellScale " + std::to_string(s.cell_scale) + " HeightScale " +
std::to_string(s.height_scale) + " env=" + s.environment);
// TextureSetsetting 里是 `textureset\metin2_A1.txt`,实盘在 assets/textureset/textureset/ 且小写
{
std::string rel = s.texture_set;
for (auto& c : rel)
if (c == '\\') c = '/';
for (auto& c : rel) c = (char)std::tolower((unsigned char)c);
std::string p = assets + "/textureset/" + rel;
TextureSet ts;
if (parse_texture_set_file(p, ts, &err))
line("TextureSet", true,
std::to_string(ts.layers.size()) + " layers (+1 empty = runtime " +
std::to_string(ts.runtime_count()) + ")");
else
line("TextureSet", false, p + " : " + err);
}
// 每个区块
int chunks_found = 0, hm_ok = 0, tile_ok = 0, attr_ok = 0, water_ok = 0, area_ok = 0;
long total_objs = 0;
std::map<uint32_t, int> crc_hist;
for (int tx = 0; tx < s.map_size_x; ++tx) {
for (int ty = 0; ty < s.map_size_y; ++ty) {
std::string dir = map + "/" + m2coord::tile_dir(tx, ty);
std::string e;
HeightMap hm;
TileMap tm;
AttrMap am;
WaterMap wm;
AreaData ad;
bool any = false;
if (load_height_map(dir + "/height.raw", hm, &e)) { ++hm_ok; any = true; }
if (load_tile_map(dir + "/tile.raw", tm, &e)) ++tile_ok;
if (load_attr_map(dir + "/attr.atr", am, &e)) ++attr_ok;
if (load_water_map(dir + "/water.wtr", wm, &e)) ++water_ok;
if (parse_area_data_file(dir + "/areadata.txt", ad, &e)) {
++area_ok;
total_objs += (long)ad.objects.size();
for (auto& o : ad.objects) crc_hist[o.crc]++;
}
if (any) ++chunks_found;
}
}
int n = s.chunk_count();
line("chunk dirs", chunks_found == n,
std::to_string(chunks_found) + "/" + std::to_string(n));
line("height.raw", hm_ok == n, std::to_string(hm_ok) + "/" + std::to_string(n) + " as 131x131");
line("tile.raw", tile_ok == n, std::to_string(tile_ok) + "/" + std::to_string(n) + " as 258x258");
line("attr.atr", attr_ok == n, std::to_string(attr_ok) + "/" + std::to_string(n) + " magic 2634");
line("water.wtr", water_ok == n, std::to_string(water_ok) + "/" + std::to_string(n) + " magic 5426");
line("areadata.txt", area_ok == n, std::to_string(area_ok) + "/" + std::to_string(n) + " parsed");
std::printf(" ---- AreaData: %ld objects, %zu distinct Property CRCs ----\n",
total_objs, crc_hist.size());
// splat(区块 000000
{
TileMap tm;
std::string e;
if (load_tile_map(map + "/000000/tile.raw", tm, &e)) {
SplatSet ss;
build_splat(tm, 18, ss);
std::string used;
for (auto& L : ss.layers)
used += std::to_string(L.layer) + "(" + std::to_string(L.coverage) + ") ";
line("splat 000000", !ss.layers.empty(),
std::to_string(ss.layers.size()) + " used layers: " + used);
}
}
// .msenv(虚拟路径 environment\<name> -> ETC/ymir work/environment/<name>
Environment env;
{
std::string p = assets + "/ETC/ymir work/environment/" + s.environment;
for (auto& c : p) c = (c == '\\') ? '/' : (char)std::tolower((unsigned char)c);
// 只小写文件名部分即可,这里整体小写对 macOS/实盘均成立
std::string p2 = assets + "/ETC/ymir work/environment/" + s.environment;
std::string lp = p2;
for (auto& c : lp) c = (char)std::tolower((unsigned char)c);
if (parse_environment_file(lp, env, &err))
line(".msenv", true,
s.environment + " : fog=" + (env.fog.enable ? "on" : "off") + " gradient=" +
std::to_string(env.sky.gradient.size()) + " rows, cloudtex=" +
env.sky.cloud_texture);
else
line(".msenv", false, lp + " : " + err);
}
// Property CRC 注册表
PropertyRegistry reg;
if (reg.scan(assets + "/Property", &err)) {
std::string bytype;
for (auto& [k, v] : reg.type_counts) bytype += k + "=" + std::to_string(v) + " ";
line("Property registry", reg.parse_failed == 0,
std::to_string(reg.by_crc.size()) + " CRCs (" + std::to_string(reg.files_scanned) +
" files, " + std::to_string(reg.parse_failed) + " failed, " +
std::to_string(reg.crc_collisions) + " collisions) [" + bytype + "]");
// areadata 的 CRC 覆盖率
long hit = 0, total = 0;
std::set<uint32_t> missing;
for (auto& [crc, cnt] : crc_hist) {
total++;
if (reg.find(crc)) hit++;
else missing.insert(crc);
}
line("AreaData CRC coverage", missing.empty(),
std::to_string(hit) + "/" + std::to_string(total) + " distinct CRCs resolve" +
(missing.empty() ? "" : " (missing e.g. " + std::to_string(*missing.begin()) + ")"));
} else {
line("Property registry", false, err);
}
// AssetResolver
AssetResolver res;
if (res.build(assets, AssetResolver::default_priority(), &err)) {
line("AssetResolver", true,
std::to_string(res.files_indexed) + " files, " +
std::to_string(res.by_ymir.size()) + " ymir keys, " +
std::to_string(res.ymir_conflicts) + " conflicts");
// TextureSet 图层贴图解析率
TextureSet ts2;
std::string rel = s.texture_set, e3;
for (auto& c : rel) c = (c == '\\') ? '/' : (char)std::tolower((unsigned char)c);
if (parse_texture_set_file(assets + "/textureset/" + rel, ts2, &e3)) {
int miss = 0;
for (auto& L : ts2.layers)
if (res.resolve(L.texture, nullptr).empty()) ++miss;
line("TextureSet -> assets", miss == 0,
std::to_string(ts2.layers.size() - miss) + "/" +
std::to_string(ts2.layers.size()) + " layer textures resolve");
}
// Property 引用的模型文件解析率(buildingfile / treefile / effectfile
int pref = 0, pmiss = 0;
for (auto& [crc, p] : reg.by_crc) {
for (const char* k : {"buildingfile", "treefile", "effectfile", "modelfile"}) {
std::string v = p.get(k);
if (v.empty()) continue;
++pref;
if (res.resolve(v, nullptr).empty()) ++pmiss;
}
}
line("Property models -> assets", true,
std::to_string(pref - pmiss) + "/" + std::to_string(pref) +
" resolve (misses incl. .spt trees / missing packs)");
} else {
line("AssetResolver", false, err);
}
std::printf("\n== summary: %d ok, %d err ==\n", g_ok, g_bad);
return g_bad ? 1 : 0;
}