Fix 40250 classic login and world loading

This commit is contained in:
shen
2026-09-02 07:03:25 +08:00
parent 8f61990a83
commit 44bdcb0781
17 changed files with 446 additions and 45 deletions
+86
View File
@@ -121,6 +121,11 @@ func setup(m2client: Node, assets_root: String,
map_path: String = "OutdoorA1/metin2_map_a1") -> void:
client = m2client
_assets = assets_root
# GC_MAIN_CHARACTER only carries the server-space coordinates, not the map
# folder. The old fixed A1 fallback puts characters from A2/A3 outside the
# rendered terrain, which looks like an empty map. Resolve the map from the
# character position before constructing Metin2World.
map_path = _resolve_map_path(map_path)
_build_lighting()
# 关键:setup 是协程。每个重活之间让出一帧,好让 M2Client._process 抽 socket
# (否则整段 ~5s 同步阻塞会漏 PONG → 服务器 10s 后 "peer closed")。
@@ -672,6 +677,87 @@ func _map_loaded() -> bool:
var rep: Dictionary = world.call("get_load_report")
return int(rep.get("map_size_x", 0)) > 0
func _resolve_map_path(requested: String) -> String:
if client == null or _assets == "" or not client.has_method("get_main_vid"):
return requested
var vid := int(client.get_main_vid())
if vid == 0 or not client.has_method("get_entity"):
return requested
var entity: Dictionary = client.get_entity(vid)
var pos: Variant = entity.get("pos_cm", null)
if not (pos is Vector3):
return requested
var settings: Array[String] = []
_collect_map_settings(_assets, settings)
var best := requested
var best_score := 1000000
for setting_path: String in settings:
var bounds := _map_bounds(setting_path)
if bounds.is_empty():
continue
var base: Vector2 = bounds["base"]
var size: Vector2i = bounds["size"]
var max_x := base.x + float(size.x) * 25600.0
var max_y := base.y + float(size.y) * 25600.0
if pos.x < base.x or pos.x >= max_x or pos.y < base.y or pos.y >= max_y:
continue
var root := _assets.trim_suffix("/")
var rel_setting := setting_path.substr(root.length() + 1)
var candidate := rel_setting.get_base_dir()
var lower := candidate.to_lower()
# Prefer the normal outdoor map over duplicate patch/season copies when
# several map packages advertise the same world-space rectangle.
var score := candidate.split("/").size()
if lower.begins_with("outdoor"):
score -= 20
if lower.begins_with("season") or lower.begins_with("metin2_patch"):
score += 20
if candidate == requested:
score -= 1000
if score < best_score:
best_score = score
best = candidate
if best != requested:
print("[GameScene] map resolved by server position ", pos, " -> ", best)
return best
func _collect_map_settings(dir_path: String, out: Array[String]) -> void:
var dir := DirAccess.open(dir_path)
if dir == null:
return
for file_name: String in dir.get_files():
if file_name.to_lower() == "setting.txt":
out.append(dir_path.path_join(file_name))
for child: String in dir.get_directories():
if child in [".godot", ".git", "build", "export"]:
continue
_collect_map_settings(dir_path.path_join(child), out)
func _map_bounds(setting_path: String) -> Dictionary:
var f := FileAccess.open(setting_path, FileAccess.READ)
if f == null:
return {}
var base := Vector2.ZERO
var size := Vector2i.ZERO
var have_base := false
var have_size := false
while not f.eof_reached():
var fields := f.get_line().replace("\t", " ").strip_edges().split(" ", false)
if fields.size() < 3:
continue
match String(fields[0]).to_lower():
"baseposition":
base = Vector2(float(fields[1]), float(fields[2]))
have_base = true
"mapsize":
size = Vector2i(int(fields[1]), int(fields[2]))
have_size = size.x > 0 and size.y > 0
if not have_base or not have_size:
return {}
return {"base": base, "size": size}
func _build_lighting() -> void:
var sun := DirectionalLight3D.new()
sun.name = "FallbackSun"