- 连击类型由 combo_changed 驱动(SetComboSkillFlag,技能 122 等级) - 命中判定改为 .msa 刀尖 Z 圆柱 vs .msm 防御球(hit_collision.gd 逐行移植) - _register_hit 改为 map::insert 语义,修正命中上限 - 攻速同时缩放 GetAttackingElapsedTime 与攻击动作速率;按武器动作模式目录绑定攻击段 - 去掉本地连击超时,motion_bound 清命中表 / 连击段号 - __ProcessDataAttackSuccess:InsertDelay 硬直、physics_push.gd 击退 + GetBlendingPosition 同步、 命中特效、__HitGood / __HitGreate / __HitStone 受击动作链与抖动(ui/hit_reaction.gd) - ClassicSession::send_use_skill 不再夹带 CG_FLY_TARGETING - mac 前进 / 后退方向与技能释放修复;武器按职业骨骼挂到手上 - 新增 hit_collision / physics_push / hit_view / net_world_push / weapon_attach 测试; gpu_pose_bounds / race_motion_assembly 适配 damage 随机变体 - 文档:CLIENT-GAP.md、CLIENT-GAP-FIX.md §3.5 / §3.7 与 C.5 增量 130 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ft3kXpNdLbKEfg5uDLfqVF
165 lines
5.8 KiB
GDScript
165 lines
5.8 KiB
GDScript
# input_key_test —— ClientVS22 game.py 键盘映射的离线回归。
|
|
# 覆盖 macOS Command / Windows Ctrl 表情键、快捷栏分页、按住攻击、
|
|
# 相机按住/释放、名称显示和几个窗口/坐骑入口。
|
|
extends SceneTree
|
|
|
|
const GameScene = preload("res://game_scene.gd")
|
|
const UiManager = preload("res://ui/ui_manager.gd")
|
|
|
|
class FakeClient extends Node:
|
|
var emotes := []
|
|
var commands := []
|
|
var used_items := []
|
|
var main := 1000
|
|
func send_emoticon(index: int) -> bool: emotes.append(index); return true
|
|
func say(_type: int, text: String) -> bool: commands.append(text); return true
|
|
func get_main_vid() -> int: return main
|
|
func get_entity(_vid: int) -> Dictionary: return {"mount_vnum": 123}
|
|
func get_inventory() -> Array: return [{"cell": 7, "vnum": 71114}]
|
|
func use_item(_window: int, cell: int) -> bool: used_items.append(cell); return true
|
|
|
|
class FakeQuickbar extends Node:
|
|
var activated := []
|
|
var pages := []
|
|
func activate(slot: int) -> void: activated.append(slot)
|
|
func set_page(page: int) -> void: pages.append(page)
|
|
|
|
class FakeNetPlay extends Node:
|
|
var attack_states := []
|
|
func set_attack_key(down: bool) -> void: attack_states.append(down)
|
|
|
|
class FakeCamera extends Camera3D:
|
|
var orbit := []
|
|
var zoom := []
|
|
var pitch := []
|
|
func set_key_orbit(direction: int) -> void: orbit.append(direction)
|
|
func set_key_zoom(direction: int) -> void: zoom.append(direction)
|
|
func set_key_pitch(direction: int) -> void: pitch.append(direction)
|
|
|
|
class FakeWorld extends Node:
|
|
var names := []
|
|
func set_names_visible(visible: bool) -> void: names.append(visible)
|
|
|
|
class FakeChat extends Node:
|
|
var toggles := 0
|
|
func is_typing() -> bool: return false
|
|
func focus_input() -> void: pass
|
|
func toggle_log() -> void: toggles += 1
|
|
|
|
class FakeStatus extends Node:
|
|
var states := []
|
|
func open(state: String) -> void: states.append(state)
|
|
|
|
class FakeToggle extends Node:
|
|
var toggles := 0
|
|
var button_toggles := 0
|
|
func toggle() -> void: toggles += 1
|
|
func toggle_help() -> void: toggles += 1
|
|
func toggle_buttons() -> void: button_toggles += 1
|
|
|
|
var _fail := 0
|
|
|
|
func _ck(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
_fail += 1
|
|
printerr("FAIL: " + message)
|
|
|
|
func _key(code: Key, pressed := true, ctrl := false, meta := false) -> InputEventKey:
|
|
var event := InputEventKey.new()
|
|
event.keycode = code
|
|
event.pressed = pressed
|
|
event.echo = false
|
|
event.ctrl_pressed = ctrl
|
|
event.meta_pressed = meta
|
|
return event
|
|
|
|
func _init() -> void:
|
|
await _run()
|
|
if _fail == 0:
|
|
print("PASS: input_key_test (ClientVS22 desktop key map)")
|
|
quit(0)
|
|
else:
|
|
printerr("%d check(s) failed" % _fail)
|
|
quit(1)
|
|
|
|
func _run() -> void:
|
|
var client := FakeClient.new()
|
|
var qb := FakeQuickbar.new()
|
|
var np := FakeNetPlay.new()
|
|
var camera := FakeCamera.new()
|
|
var world := FakeWorld.new()
|
|
var chat := FakeChat.new()
|
|
var status := FakeStatus.new()
|
|
var help := FakeToggle.new()
|
|
var quest := FakeToggle.new()
|
|
var gs: Node = GameScene.new()
|
|
gs.client = client
|
|
gs.quickbar = qb
|
|
gs.net_play = np
|
|
gs.cam = camera
|
|
gs.net_world = world
|
|
gs.chat = chat
|
|
gs.char_status_ui = status
|
|
gs.system_menu_ui = help
|
|
gs.quest_log = quest
|
|
get_root().add_child(gs)
|
|
|
|
gs._unhandled_input(_key(KEY_1, true, true, false))
|
|
gs._unhandled_input(_key(KEY_9, true, false, true))
|
|
_ck(client.emotes == [0, 8], "Ctrl/Command+number -> matching emoticon")
|
|
gs._unhandled_input(_key(KEY_1))
|
|
gs._unhandled_input(_key(KEY_F1))
|
|
gs._unhandled_input(_key(KEY_F4))
|
|
_ck(qb.activated == [0, 4, 7], "number/F1-F4 -> reference local quick slots")
|
|
gs._unhandled_input(_key(KEY_SHIFT))
|
|
gs._unhandled_input(_key(KEY_4))
|
|
gs._unhandled_input(_key(KEY_SHIFT, false))
|
|
_ck(qb.pages == [3], "held Shift + number -> quick page 4")
|
|
|
|
gs._unhandled_input(_key(KEY_SPACE))
|
|
gs._unhandled_input(_key(KEY_SPACE, false))
|
|
_ck(np.attack_states == [true, false], "Space press/release -> attack key state")
|
|
gs._unhandled_input(_key(KEY_Q))
|
|
gs._unhandled_input(_key(KEY_Q, false))
|
|
gs._unhandled_input(_key(KEY_R))
|
|
gs._unhandled_input(_key(KEY_R, false))
|
|
_ck(camera.orbit == [-1, 0] and camera.zoom == [-1, 0], "camera Q/R release stops continuous motion")
|
|
gs._unhandled_input(_key(KEY_ALT))
|
|
gs._unhandled_input(_key(KEY_ALT, false))
|
|
_ck(world.names == [true, false], "Alt press/release -> ShowName/HideName")
|
|
|
|
gs._unhandled_input(_key(KEY_B))
|
|
gs._unhandled_input(_key(KEY_V))
|
|
gs._unhandled_input(_key(KEY_C))
|
|
gs._unhandled_input(_key(KEY_H))
|
|
gs._unhandled_input(_key(KEY_N))
|
|
_ck(status.states == ["EMOTICON", "SKILL", "STATUS"], "character tabs match B/V/C")
|
|
_ck(help.toggles == 1 and quest.toggles == 1, "H help and N quest log")
|
|
gs._unhandled_input(_key(KEY_Q, true, true))
|
|
_ck(quest.button_toggles == 1, "Ctrl+Q toggles quest buttons")
|
|
gs._unhandled_input(_key(KEY_L))
|
|
_ck(chat.toggles == 1, "L toggles chat log")
|
|
gs._unhandled_input(_key(KEY_H, true, true))
|
|
gs._unhandled_input(_key(KEY_B, true, false, true))
|
|
gs._unhandled_input(_key(KEY_J, true, true))
|
|
_ck(client.commands == ["/user_horse_ride", "/user_horse_back", "/unmount"],
|
|
"Ctrl/Command horse commands follow reference")
|
|
|
|
# CWindowManager::RunKeyDown:只有 LockWindow(模态)或返回 True 的 ActiveWindow(输入框)
|
|
# 截键;普通打开的窗口(技能窗 / 背包)不吞键,1-4 / F1-F4 照样落到 game.py 快捷栏。
|
|
var ui_manager: CanvasLayer = UiManager.new()
|
|
get_root().add_child(ui_manager)
|
|
await process_frame
|
|
gs.ui = ui_manager
|
|
var skill_window := Panel.new()
|
|
ui_manager.open(skill_window)
|
|
qb.activated.clear()
|
|
gs._unhandled_input(_key(KEY_2))
|
|
gs._unhandled_input(_key(KEY_F2))
|
|
_ck(qb.activated == [1, 5], "non-modal window open keeps number/F-key quick slots: %s" % [qb.activated])
|
|
var confirm := Panel.new()
|
|
ui_manager.open(confirm, true)
|
|
gs._unhandled_input(_key(KEY_3))
|
|
_ck(qb.activated == [1, 5], "modal lock window still swallows quick slot keys")
|
|
gs.ui = null
|