#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "static_object.h" namespace godot { class MeshInstance3D; } namespace mtgodot { // W1 —— 加载一张 Metin2 户外地图并渲染灰色高度地形。 // SHINSOO-WORLD-RENDERING.md §8(Metin2World API 契约)/ §9-W1。 // // Metin2World (Node3D) // └─ Terrain_000000 (MeshInstance3D) …每区块一个 // // 后续阶段往这里加 splat 材质(W2)、静态对象(W3)、树(W4)、环境(W5)… class Metin2World : public godot::Node3D { GDCLASS(Metin2World, godot::Node3D) public: Metin2World(); ~Metin2World() override; void _ready() override; void _process(double delta) override; void set_assets_root(const godot::String &p); godot::String get_assets_root() const { return assets_root; } void set_map_path(const godot::String &p); godot::String get_map_path() const { return map_path; } void set_load_radius_tiles(int r) { load_radius = r; } int get_load_radius_tiles() const { return load_radius; } void set_focus_tile(godot::Vector2i t); godot::Vector2i get_focus_tile() const { return godot::Vector2i(focus_tx, focus_ty); } void set_auto_load(bool v) { auto_load = v; } bool get_auto_load() const { return auto_load; } void set_splat_enabled(bool v) { splat_enabled = v; } bool get_splat_enabled() const { return splat_enabled; } void set_terrain_patches(int n) { terrain_patches = n; } int get_terrain_patches() const { return terrain_patches; } void set_objects_enabled(bool v) { objects_enabled = v; } bool get_objects_enabled() const { return objects_enabled; } void set_env_enabled(bool v) { env_enabled = v; } bool get_env_enabled() const { return env_enabled; } void set_water_enabled(bool v) { water_enabled = v; } bool get_water_enabled() const { return water_enabled; } // §3.3: static building/tree shadows are already baked into shadowmap.dds, so // they don't cast realtime sun shadows by default (avoids double-darkening). // Only shadowflag=1 buildings and dynamic actors cast realtime. Flip these on // for a modernized look. void set_tree_shadows(bool v) { tree_shadows = v; } bool get_tree_shadows() const { return tree_shadows; } void set_static_shadows(bool v) { static_shadows = v; } bool get_static_shadows() const { return static_shadows; } void set_stream_budget(int v) { stream_budget = v < 1 ? 1 : v; } int get_stream_budget() const { return stream_budget; } bool load_map(); void unload_map(); // 流式:把关注点设到某全局米坐标;load_radius_tiles >= 0 时按 3×3(radius) 装/卸区块。 void set_focus_position(double gx_m, double gz_m); godot::Dictionary get_perf() const; // fps / frame ms / draw calls / prims / vram / 节点数 // 全局米坐标(Godot 空间)下的地表高度;地图外返回 0。 godot::Vector2 get_map_base_cm() const; // setting.txt BasePosition, in cm godot::Vector2i get_map_size_tiles() const; double sample_height(double gx_m, double gz_m) const; // attr.atr 属性字节(bit0=BLOCK, bit1=WATER…);地图外返回 0。 int sample_attribute(double gx_m, double gz_m) const; bool is_blocked(double gx_m, double gz_m) const { return (sample_attribute(gx_m, gz_m) & 1) != 0; } // ClientVS22 InstanceBaseMotion::GetFishingRot 等价:在角色前方 600cm // 按当前 heading ±(0..180°,步长 10°) 扫描 ATTRIBUTE_WATER。 // 返回选中的服务端 heading;没有水面或目标区块未加载时返回 -1。 double get_fishing_rotation(double gx_m, double gz_m, double heading_deg) const; bool can_fishing_position(double gx_m, double gz_m, double heading_deg) const; godot::Dictionary get_load_report() const; // Area ambience sources from AreaAmbienceData + .pra properties. Audio owns // playback; the world only exposes the reference source records. godot::Array get_ambience_sources() const; // 便捷:解一张 DDS 为 Image(HUD 小地图等用;Godot 原生不支持 .dds)。 godot::Ref load_dds(const godot::String &path) const; // map_path 下某区块目录的绝对路径(HUD 找 minimap.dds 用)。 godot::String chunk_dir(int tile_x, int tile_y) const; // 构建期用:扫 assets_root 建 AssetResolver 索引,写到 out_path(asset_index.txt)。 // 打进 PCK 后移动端 build_or_load() 直接装载,不再 std::filesystem 扫盘。 bool bake_asset_index(const godot::String &out_path); protected: static void _bind_methods(); private: godot::String assets_root; godot::String map_path = "OutdoorA1/metin2_map_a1"; int load_radius = -1; // <0 = 全图 int focus_tx = 0, focus_ty = 0; bool auto_load = true; bool splat_enabled = true; // §3.5: 每区块地形拆成 N×N patch(各自 MeshInstance),启用逐 patch 视锥剔除 + // visibility_range 远距整片剔除。1 = 不拆(旧行为)。必须整除 128。 int terrain_patches = 4; // patch 超过这个距离整片不画(米)。默认 3500 覆盖全图+俯视调试,实际增益来自 // 逐 patch 视锥剔除;游戏内可调低省远景地形。 float terrain_patch_view = 3500.0f; bool objects_enabled = true; bool env_enabled = true; bool water_enabled = true; bool collision_enabled = true; bool tree_shadows = false; // trees: baked in shadowmap.dds -> no realtime cast bool static_shadows = false; // shadowflag=0/empty buildings: same int water_pieces = 0; fmt::MapSetting setting; fmt::TextureSet texture_set; fmt::Environment env; bool env_ok = false; std::shared_ptr resolver; fmt::PropertyRegistry registry; StaticMeshCache static_cache; bool setting_ok = false; bool splat_ready = false; bool registry_ok = false; godot::String last_error; int chunks_built = 0, chunks_failed = 0, chunks_splatted = 0; int objects_placed = 0, objects_skipped = 0, objects_missing_model = 0; int trees_placed = 0, tree_species = 0; int native_trees_placed = 0, proxy_trees_placed = 0; int objects_mdatr_built = 0; godot::Node3D *objects_root = nullptr; double build_ms = 0; struct Chunk { int tx = 0, ty = 0; std::shared_ptr hm; std::shared_ptr am; godot::Node3D *root = nullptr; // 该区块的全部场景节点(terrain + water + 对象 + 树) int objects = 0, trees = 0; }; struct AmbienceSource { int tile_x = 0, tile_y = 0; int object_index = 0; godot::Vector3 position; int range_cm = 0; float max_volume_area_percentage = 0.0f; float play_interval = 0.0f; float play_interval_variation = 0.0f; godot::String play_type; std::vector sounds; }; std::vector chunks; std::vector> stream_queue; // 待建区块 std::vector ambience_sources; int stream_budget = 1; // 每帧最多建几个区块(streaming 时) godot::String map_dir() const; bool build_chunk(int tx, int ty); void place_chunk_objects(int tx, int ty, godot::Node3D *root, int &n_obj, int &n_tree); void place_chunk_ambience(int tx, int ty); void unload_chunk(int idx); void stream_update(); const Chunk *chunk_at(int tx, int ty) const; }; } // namespace mtgodot