fix(locale): resolve Chinese monster and NPC names via Locale.mob_names and early-bind name resolver
This commit is contained in:
+11
-2
@@ -246,6 +246,13 @@ func setup(m2client: Node, assets_root: String,
|
|||||||
pc.ground_clicked.connect(_on_ground_clicked)
|
pc.ground_clicked.connect(_on_ground_clicked)
|
||||||
if _assets != "":
|
if _assets != "":
|
||||||
net_play.set_asset_root(_assets) # §3.5:加载 playersettingmodule.py 连击段表
|
net_play.set_asset_root(_assets) # §3.5:加载 playersettingmodule.py 连击段表
|
||||||
|
var _early_loc: RefCounted = load("res://locale.gd").new()
|
||||||
|
_early_loc.setup(_assets, cur_lang)
|
||||||
|
net_play.locale = _early_loc
|
||||||
|
if net_world and "name_resolver" in net_world:
|
||||||
|
net_world.name_resolver = net_play._entity_name # 早期绑定:确保最先刷出的实体即显示中文名
|
||||||
|
if net_world and "kind_resolver" in net_world:
|
||||||
|
net_world.kind_resolver = net_play._entity_kind
|
||||||
|
|
||||||
# 主角必须先于后续的 proto/UI/远端实体重活建立。实网一次可收到近 200 个
|
# 主角必须先于后续的 proto/UI/远端实体重活建立。实网一次可收到近 200 个
|
||||||
# GC_CHARACTER_ADD;若等整个 setup() 尾部才订阅,怪物模型同步加载会把本地角色
|
# GC_CHARACTER_ADD;若等整个 setup() 尾部才订阅,怪物模型同步加载会把本地角色
|
||||||
@@ -485,8 +492,10 @@ func setup(m2client: Node, assets_root: String,
|
|||||||
net_world.kind_resolver = net_play._entity_kind # §8.8 名字色分类:怪 / NPC 走 mob_proto bType
|
net_world.kind_resolver = net_play._entity_kind # §8.8 名字色分类:怪 / NPC 走 mob_proto bType
|
||||||
if net_world and "title_name_resolver" in net_world and assets_root != "":
|
if net_world and "title_name_resolver" in net_world and assets_root != "":
|
||||||
# §8.8 称号名:introloading.__RegisterTitleName -> localeInfo.PVP_LEVEL<grade>
|
# §8.8 称号名:introloading.__RegisterTitleName -> localeInfo.PVP_LEVEL<grade>
|
||||||
var _tt_loc: RefCounted = load("res://locale.gd").new()
|
var _tt_loc: RefCounted = net_play.locale if net_play.locale != null else load("res://locale.gd").new()
|
||||||
_tt_loc.setup(assets_root, cur_lang)
|
if net_play.locale == null:
|
||||||
|
_tt_loc.setup(assets_root, cur_lang)
|
||||||
|
net_play.locale = _tt_loc
|
||||||
net_world.title_name_resolver = func(g: int) -> String:
|
net_world.title_name_resolver = func(g: int) -> String:
|
||||||
var k := "PVP_LEVEL%d" % g
|
var k := "PVP_LEVEL%d" % g
|
||||||
return _tt_loc.t(k) if _tt_loc.has(k) else ""
|
return _tt_loc.t(k) if _tt_loc.has(k) else ""
|
||||||
|
|||||||
+53
-19
@@ -119,6 +119,7 @@ var net_world: Node # NetWorld
|
|||||||
var hud: Node # 可空
|
var hud: Node # 可空
|
||||||
var target_ui: Node # 40250 uitarget.py TargetBoard (可空)
|
var target_ui: Node # 40250 uitarget.py TargetBoard (可空)
|
||||||
var audio: Node # 可空
|
var audio: Node # 可空
|
||||||
|
var locale: RefCounted = null # 语言包 Locale 实例(查 mob_names.txt / 翻译)
|
||||||
var _last_level := -1
|
var _last_level := -1
|
||||||
var _last_exp := -1
|
var _last_exp := -1
|
||||||
var _last_dead_mob_pos := Vector3.INF
|
var _last_dead_mob_pos := Vector3.INF
|
||||||
@@ -663,29 +664,54 @@ func _entity_kind(e: Dictionary) -> int:
|
|||||||
return explicit_kind
|
return explicit_kind
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
var _locale_ref: RefCounted = null
|
func _get_locale() -> RefCounted:
|
||||||
|
if locale != null:
|
||||||
|
return locale
|
||||||
|
var ar_class = load("res://asset_root.gd")
|
||||||
|
var root_path: String = ar_class.path() if ar_class and ar_class.has_method("path") else ""
|
||||||
|
if root_path != "":
|
||||||
|
var loc_script = load("res://locale.gd")
|
||||||
|
if loc_script:
|
||||||
|
var loc: RefCounted = loc_script.new()
|
||||||
|
var cur_lang: String = loc_script.get_default_lang() if loc_script.has_method("get_default_lang") else "zh"
|
||||||
|
loc.setup(root_path, cur_lang)
|
||||||
|
locale = loc
|
||||||
|
return locale
|
||||||
|
return null
|
||||||
|
|
||||||
# 实体显示名:玩家 / NPC 走 GC_CHAR_ADD_INFO 的 name;怪没有名字包 -> 查 mob_proto / Locale。
|
# 实体显示名:
|
||||||
|
# 1. 玩家实体(PC):使用其自定义角色名(含变身中玩家、主玩家)
|
||||||
|
# 2. 怪物 / NPC / 陨石 / 建筑等(或 race >= 1 的非PC实体):优先查本地化 mob_names (locale),其次 mob_proto
|
||||||
|
# 3. 兜底回退:实体字典自带名称
|
||||||
func _entity_name(e: Dictionary) -> String:
|
func _entity_name(e: Dictionary) -> String:
|
||||||
var nm := String(e.get("name", ""))
|
|
||||||
if nm != "":
|
|
||||||
return nm
|
|
||||||
var race := int(e.get("race", 0))
|
var race := int(e.get("race", 0))
|
||||||
|
var kind := _entity_kind(e)
|
||||||
|
var is_main := bool(e.get("is_main", false)) or (client and client.has_method("get_main_vid") and int(e.get("vid", 0)) == int(client.get_main_vid()))
|
||||||
|
var is_poly := EntityRules._is_poly(e)
|
||||||
|
|
||||||
|
# 1. 玩家实体(PC):使用其自定义角色名(含变身中玩家、主玩家)
|
||||||
|
if is_main or is_poly or kind == EntityRules.KIND_PC or (race < 8 and not EntityRules._is_enemy(e)):
|
||||||
|
var nm := String(e.get("name", ""))
|
||||||
|
if nm != "":
|
||||||
|
return nm
|
||||||
|
|
||||||
|
# 2. 怪物 / NPC / 陨石 / 建筑等:优先查本地化词表 (mob_names.txt)
|
||||||
if race >= 1:
|
if race >= 1:
|
||||||
if _locale_ref == null and ResourceLoader.exists("res://locale.gd"):
|
var loc := _get_locale()
|
||||||
var LocaleScript = load("res://locale.gd")
|
if loc and loc.has_method("has_mob_name") and loc.has_mob_name(race):
|
||||||
if LocaleScript:
|
return loc.mob_name(race)
|
||||||
_locale_ref = LocaleScript.new()
|
if proto and proto.has_method("mob"):
|
||||||
_locale_ref.setup(AssetRoot.path() if ResourceLoader.exists("res://asset_root.gd") else "")
|
var m: Dictionary = proto.mob(race)
|
||||||
if _locale_ref and _locale_ref.has_method("mob_name"):
|
if not m.is_empty():
|
||||||
var zh_name: String = _locale_ref.mob_name(race)
|
var ln := String(m.get("locale_name", ""))
|
||||||
if not zh_name.begins_with("怪物 #") and not zh_name.is_empty():
|
if ln != "":
|
||||||
return zh_name
|
return ln
|
||||||
if race >= 1 and proto and proto.has_method("mob"):
|
var pn := String(m.get("name", ""))
|
||||||
var m: Dictionary = proto.mob(race)
|
if pn != "":
|
||||||
if not m.is_empty():
|
return pn
|
||||||
return String(m.get("locale_name", m.get("name", "")))
|
|
||||||
return ""
|
# 3. 兜底回退:实体字典自带名称
|
||||||
|
return String(e.get("name", ""))
|
||||||
|
|
||||||
# --- movement ----------------------------------------------------------------
|
# --- movement ----------------------------------------------------------------
|
||||||
|
|
||||||
@@ -1094,6 +1120,14 @@ func _on_target_info(vid: int, hp_pct: int) -> void:
|
|||||||
if target_ui and target_ui.has_method("open"):
|
if target_ui and target_ui.has_method("open"):
|
||||||
var lvl := int(e.get("level", -1))
|
var lvl := int(e.get("level", -1))
|
||||||
var grade := int(e.get("grade", -1))
|
var grade := int(e.get("grade", -1))
|
||||||
|
var mob_race := int(e.get("race", 0))
|
||||||
|
if not is_pc and mob_race >= 1 and proto and proto.has_method("mob"):
|
||||||
|
var m: Dictionary = proto.mob(mob_race)
|
||||||
|
if not m.is_empty():
|
||||||
|
if lvl <= 0:
|
||||||
|
lvl = int(m.get("level", -1))
|
||||||
|
if grade < 0:
|
||||||
|
grade = int(m.get("rank", -1))
|
||||||
target_ui.open(vid, nm if nm != "" else "目标", lvl, grade, is_pc)
|
target_ui.open(vid, nm if nm != "" else "目标", lvl, grade, is_pc)
|
||||||
target_ui.set_hp(hp_clamped)
|
target_ui.set_hp(hp_clamped)
|
||||||
TargetBoard.Action.NONE:
|
TargetBoard.Action.NONE:
|
||||||
|
|||||||
+19
-1
@@ -114,7 +114,10 @@ const MAX_UPGRADES_PER_FRAME := 4
|
|||||||
const MAX_UPGRADE_TIME_MSEC := 4
|
const MAX_UPGRADE_TIME_MSEC := 4
|
||||||
const VISIBILITY_CHECK_INTERVAL := 0.1
|
const VISIBILITY_CHECK_INTERVAL := 0.1
|
||||||
var _vis_accum := 0.0
|
var _vis_accum := 0.0
|
||||||
var name_resolver: Callable = Callable() # func(entity_dict) -> String(怪名走 mob_proto)
|
var name_resolver: Callable = Callable(): # func(entity_dict) -> String(怪名走 mob_proto)
|
||||||
|
set(v):
|
||||||
|
name_resolver = v
|
||||||
|
_refresh_all_names()
|
||||||
var kind_resolver: Callable = Callable() # §8.8 func(entity_dict) -> int(0 PC/1 NPC/2 怪/3 石/4 warp;net_play._entity_kind)
|
var kind_resolver: Callable = Callable() # §8.8 func(entity_dict) -> int(0 PC/1 NPC/2 怪/3 石/4 warp;net_play._entity_kind)
|
||||||
var title_name_resolver: Callable = Callable() # §8.8 func(grade:int) -> String(PVP_LEVEL<grade> 本地化;未注入回退裸 key,seam ⑧)
|
var title_name_resolver: Callable = Callable() # §8.8 func(grade:int) -> String(PVP_LEVEL<grade> 本地化;未注入回退裸 key,seam ⑧)
|
||||||
var _name_color_accum := 0.0 # §8.8 头顶名字色 / 称号标签的节流刷新累加器(~2Hz)
|
var _name_color_accum := 0.0 # §8.8 头顶名字色 / 称号标签的节流刷新累加器(~2Hz)
|
||||||
@@ -669,6 +672,21 @@ func _display_name(d: Dictionary) -> String:
|
|||||||
return r
|
return r
|
||||||
return str(d.get("name", ""))
|
return str(d.get("name", ""))
|
||||||
|
|
||||||
|
func _refresh_all_names() -> void:
|
||||||
|
if client == null or not client.has_method("get_entities"):
|
||||||
|
return
|
||||||
|
for e in client.get_entities():
|
||||||
|
var vid := int(e.get("vid", 0))
|
||||||
|
var n: Node3D = _by_vid.get(vid, null)
|
||||||
|
if n and is_instance_valid(n):
|
||||||
|
var nm := _display_name(e)
|
||||||
|
if nm != "":
|
||||||
|
var lbl := n.get_node_or_null("Label3D") as Label3D
|
||||||
|
if lbl:
|
||||||
|
lbl.text = nm
|
||||||
|
elif n.has_method("set_display_name"):
|
||||||
|
n.set_display_name(nm)
|
||||||
|
|
||||||
# --- §8.8 头顶名字色(CInstanceBase::GetNameColor)--------------------------------
|
# --- §8.8 头顶名字色(CInstanceBase::GetNameColor)--------------------------------
|
||||||
# net_world 直接持有 M2Client,能自建 name_color_index 需要的会话上下文
|
# net_world 直接持有 M2Client,能自建 name_color_index 需要的会话上下文
|
||||||
# (net_play._attack_ctx 同款)。kind_resolver 由 game_scene 注入 net_play._entity_kind
|
# (net_play._attack_ctx 同款)。kind_resolver 由 game_scene 注入 net_play._entity_kind
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ extends SceneTree
|
|||||||
|
|
||||||
const Locale = preload("res://locale.gd")
|
const Locale = preload("res://locale.gd")
|
||||||
const ItemTooltip = preload("res://ui/item_tooltip.gd")
|
const ItemTooltip = preload("res://ui/item_tooltip.gd")
|
||||||
|
const NetPlay = preload("res://net_play.gd")
|
||||||
|
|
||||||
func _init() -> void:
|
func _init() -> void:
|
||||||
print("=== Starting test_chinese_locale ===")
|
print("=== Starting test_chinese_locale ===")
|
||||||
@@ -97,6 +98,65 @@ func _init() -> void:
|
|||||||
printerr("FAIL: Expected title '武士刀+0', got '%s'" % model.get("title", ""))
|
printerr("FAIL: Expected title '武士刀+0', got '%s'" % model.get("title", ""))
|
||||||
failed += 1
|
failed += 1
|
||||||
|
|
||||||
|
# 7. NetPlay._entity_name resolution for mobs, stones, NPCs, and PCs
|
||||||
|
var np := NetPlay.new()
|
||||||
|
np.locale = loc
|
||||||
|
|
||||||
|
# 7a. Monster: stray_dog (race 101) -> 幼狼
|
||||||
|
var mob_dog := np._entity_name({"race": 101, "kind": 2, "name": "stray_dog"})
|
||||||
|
if mob_dog == "幼狼":
|
||||||
|
print("PASS: NetPlay._entity_name(race 101 stray_dog) resolved to '幼狼'")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
printerr("FAIL: Expected '幼狼', got '%s'" % mob_dog)
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
# 7b. Monster: wolf (race 102) -> 灰狼
|
||||||
|
var mob_wolf := np._entity_name({"race": 102, "kind": 2, "name": "wolf"})
|
||||||
|
if mob_wolf == "灰狼":
|
||||||
|
print("PASS: NetPlay._entity_name(race 102 wolf) resolved to '灰狼'")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
printerr("FAIL: Expected '灰狼', got '%s'" % mob_wolf)
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
# 7c. Metin Stone: race 8001 -> 鱼跃陨石
|
||||||
|
var stone := np._entity_name({"race": 8001, "kind": 3, "name": "metin_sorrow"})
|
||||||
|
if stone == "鱼跃陨石":
|
||||||
|
print("PASS: NetPlay._entity_name(race 8001) resolved to '鱼跃陨石'")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
printerr("FAIL: Expected '鱼跃陨石', got '%s'" % stone)
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
# 7d. NPC: Blacksmith (race 20016) -> 王铁匠
|
||||||
|
var npc_smith := np._entity_name({"race": 20016, "kind": 1, "name": "blacksmith"})
|
||||||
|
if npc_smith == "王铁匠":
|
||||||
|
print("PASS: NetPlay._entity_name(race 20016) resolved to '王铁匠'")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
printerr("FAIL: Expected '王铁匠', got '%s'" % npc_smith)
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
# 7e. PC: Player Warrior (race 0) keeps player name
|
||||||
|
var pc_warrior := np._entity_name({"race": 0, "kind": 0, "name": "无双战神"})
|
||||||
|
if pc_warrior == "无双战神":
|
||||||
|
print("PASS: NetPlay._entity_name(PC warrior) preserved player name '无双战神'")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
printerr("FAIL: Expected '无双战神', got '%s'" % pc_warrior)
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
# 7f. PC: Player Ninja (race 1) keeps player name
|
||||||
|
var pc_ninja := np._entity_name({"race": 1, "kind": 0, "name": "暗影刺客"})
|
||||||
|
if pc_ninja == "暗影刺客":
|
||||||
|
print("PASS: NetPlay._entity_name(PC ninja) preserved player name '暗影刺客'")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
printerr("FAIL: Expected '暗影刺客', got '%s'" % pc_ninja)
|
||||||
|
failed += 1
|
||||||
|
np.free()
|
||||||
|
|
||||||
print("\n=== %d / %d CHINESE LOCALE TESTS PASSED (Failed: %d) ===" % [passed, passed + failed, failed])
|
print("\n=== %d / %d CHINESE LOCALE TESTS PASSED (Failed: %d) ===" % [passed, passed + failed, failed])
|
||||||
if failed > 0:
|
if failed > 0:
|
||||||
quit(1)
|
quit(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user