Files
mtgodot-poc/project/ui/motion_registry.gd
T
shenandshen c93894313a fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
2026-09-21 16:38:59 -07:00

207 lines
6.6 KiB
GDScript

# MotionRegistry —— 40250 playersettingmodule.py 的 RaceData motion 注册表适配。
#
# 40250 不按文件名等权挑动作:RegisterCacheMotionData/SetMotionRandomWeight
# 先把 (mode, index, sub-index, weight) 注册到 CRaceData,随后
# CActorInstance::GetRandomMotionKey 使用 random() % 100 和注册顺序选择。
# 这里读取随客户端资源发布的 playersettingmodule.py,保留相同的注册顺序、
# 重复项和权重;这样资源表才是唯一数据源,不能在 PlayerView 中复制一份常量。
extends RefCounted
const MODE_VALUES := {
"MOTION_MODE_GENERAL": 1,
"MOTION_MODE_ONEHAND_SWORD": 2,
"MOTION_MODE_TWOHAND_SWORD": 3,
"MOTION_MODE_DUALHAND_SWORD": 4,
"MOTION_MODE_BOW": 5,
"MOTION_MODE_FAN": 6,
"MOTION_MODE_BELL": 7,
"MOTION_MODE_FISHING": 8,
"MOTION_MODE_HORSE": 9,
"MOTION_MODE_HORSE_ONEHAND_SWORD": 10,
"MOTION_MODE_HORSE_TWOHAND_SWORD": 11,
"MOTION_MODE_HORSE_DUALHAND_SWORD": 12,
"MOTION_MODE_HORSE_BOW": 13,
"MOTION_MODE_HORSE_FAN": 14,
"MOTION_MODE_HORSE_BELL": 15,
"MOTION_MODE_WEDDING_DRESS": 16,
}
# CRaceMotionData::NAME_* in GameLib/RaceMotionData.h. Only the indices used by
# PlayerView are required here; unresolved skill/guild expressions are skipped,
# exactly as an unregistered motion key is rejected by GetMotionKey.
const MOTION_VALUES := {
"MOTION_WAIT": 1,
"MOTION_WALK": 2,
"MOTION_RUN": 3,
"MOTION_DAMAGE": 5,
"MOTION_DAMAGE_FLYING": 6,
"MOTION_STAND_UP": 7,
"MOTION_DAMAGE_BACK": 8,
"MOTION_DAMAGE_FLYING_BACK": 9,
"MOTION_STAND_UP_BACK": 10,
"MOTION_DEAD": 11,
"MOTION_DIG": 12,
"MOTION_NORMAL_ATTACK": 13,
"MOTION_COMBO_ATTACK_1": 14,
"MOTION_COMBO_ATTACK_2": 15,
"MOTION_COMBO_ATTACK_3": 16,
"MOTION_COMBO_ATTACK_4": 17,
"MOTION_COMBO_ATTACK_5": 18,
"MOTION_COMBO_ATTACK_6": 19,
"MOTION_COMBO_ATTACK_7": 20,
"MOTION_COMBO_ATTACK_8": 21,
}
const RACE_FUNCTIONS := [
"__LoadGameWarriorEx",
"__LoadGameAssassinEx",
"__LoadGameSuraEx",
"__LoadGameShamanEx",
]
var source_path := ""
var source_loaded := false
var _entries: Dictionary = {}
static func load_for(assets_root: String, race: int) -> RefCounted:
var result := new()
result.load_from(assets_root, race)
return result
func load_from(assets_root: String, race: int) -> void:
source_path = assets_root.path_join("root/playersettingmodule.py")
_entries.clear()
source_loaded = false
if not FileAccess.file_exists(source_path):
return
var file := FileAccess.open(source_path, FileAccess.READ)
if file == null:
return
var lines: Array[String] = []
while not file.eof_reached():
lines.append(file.get_line())
file.close()
# SetGeneralMotions is a Python helper invoked by each PC race function.
# Its `mode` parameter is the selected general mode in all four PC loaders.
_parse_lines(_function_block(lines, "SetGeneralMotions"), 1)
var function_name: String = RACE_FUNCTIONS[clampi(race & 3, 0, RACE_FUNCTIONS.size() - 1)]
_parse_lines(_function_block(lines, function_name), -1)
source_loaded = not _entries.is_empty()
func variants(mode: int, index: int) -> Array:
var value: Variant = _entries.get(_key(mode, index), [])
return value.duplicate(true) if value is Array else []
func has_variants(mode: int, index: int) -> bool:
var value: Variant = _entries.get(_key(mode, index), [])
return value is Array and not value.is_empty()
func has_mode(mode: int) -> bool:
var prefix := "%d:" % mode
for key in _entries.keys():
if String(key).begins_with(prefix):
return true
return false
func _key(mode: int, index: int) -> String:
return "%d:%d" % [mode, index]
func _function_block(lines: Array[String], function_name: String) -> Array[String]:
var result: Array[String] = []
var started := false
var prefix := "def " + function_name + "("
for line in lines:
if not started:
if line.begins_with(prefix):
started = true
continue
if line.begins_with("def "):
break
result.append(line)
return result
func _parse_lines(lines: Array[String], default_mode: int) -> void:
for raw in lines:
var line := raw
var comment := line.find("#")
if comment >= 0:
line = line.substr(0, comment)
line = line.strip_edges()
if line.is_empty():
continue
_parse_registration(line, default_mode)
_parse_weight_update(line, default_mode)
func _parse_registration(line: String, default_mode: int) -> void:
var marker := "RegisterCacheMotionData("
var start := line.find(marker)
if start < 0:
return
var close := line.rfind(")")
if close <= start + marker.length():
return
var args := line.substr(start + marker.length(), close - start - marker.length()).split(",")
if args.size() < 3:
return
var mode := _resolve(args[0], default_mode, MODE_VALUES)
var index := _resolve(args[1], -1, MOTION_VALUES)
var filename := _literal_filename(args[2])
if mode < 0 or index < 0 or filename == "":
return
var weight := 100
if args.size() >= 4:
weight = _integer_literal(args[3], 100)
var key := _key(mode, index)
var entries: Array = _entries.get(key, [])
entries.append({"name": filename, "weight": weight})
_entries[key] = entries
func _parse_weight_update(line: String, default_mode: int) -> void:
var marker := "SetMotionRandomWeight("
var start := line.find(marker)
if start < 0:
return
var close := line.rfind(")")
if close <= start + marker.length():
return
var args := line.substr(start + marker.length(), close - start - marker.length()).split(",")
if args.size() < 4:
return
var mode := _resolve(args[0], default_mode, MODE_VALUES)
var index := _resolve(args[1], -1, MOTION_VALUES)
var sub_index := _integer_literal(args[2], -1)
var weight := _integer_literal(args[3], -1)
if mode < 0 or index < 0 or sub_index < 0 or weight < 0:
return
var key := _key(mode, index)
var entries: Array = _entries.get(key, [])
if sub_index >= entries.size():
return
var entry: Dictionary = entries[sub_index]
entry["weight"] = weight
entries[sub_index] = entry
_entries[key] = entries
func _resolve(token: String, fallback: int, values: Dictionary) -> int:
var text := token.strip_edges()
if text == "mode":
return fallback if fallback >= 0 else 1
if text.is_valid_int():
return int(text)
var name := text.get_slice(".", text.get_slice_count(".") - 1)
return int(values.get(name, fallback))
func _integer_literal(token: String, fallback: int) -> int:
var text := token.strip_edges()
return int(text) if text.is_valid_int() else fallback
func _literal_filename(token: String) -> String:
var text := token.strip_edges()
if text.length() < 2:
return ""
if (text.begins_with("\"") and text.ends_with("\"")) or \
(text.begins_with("'") and text.ends_with("'")):
return text.substr(1, text.length() - 2)
return ""