gmp(M02): add 3.3.5 input profile
Work-Package: M02-GMP-INPUT-PROFILE-335-001 Agent: sindo-main-codex Tests: player input/movement/capability/terrain/camera/presentation, coordinate/streaming, docs/coordination and renderer dry-run passed Fidelity: build-12340 binding hashes and selected defaults pinned; TrinityCore turn rate and WoWee mouse routing are compatibility references with explicit limits
This commit is contained in:
@@ -21,10 +21,16 @@ var is_sprint_requested: bool:
|
||||
get:
|
||||
return _is_sprint_requested
|
||||
|
||||
## Signed keyboard turn request: left is negative and right is positive.
|
||||
var turn_axis: float:
|
||||
get:
|
||||
return _turn_axis
|
||||
|
||||
var _forward_axis: float
|
||||
var _strafe_axis: float
|
||||
var _vertical_axis: float
|
||||
var _is_sprint_requested: bool
|
||||
var _turn_axis: float
|
||||
|
||||
|
||||
## Creates one frame's movement request without retaining mutable input state.
|
||||
@@ -32,12 +38,14 @@ func _init(
|
||||
forward_axis_value: float = 0.0,
|
||||
strafe_axis_value: float = 0.0,
|
||||
vertical_axis_value: float = 0.0,
|
||||
sprint_requested: bool = false
|
||||
sprint_requested: bool = false,
|
||||
turn_axis_value: float = 0.0
|
||||
) -> void:
|
||||
_forward_axis = clampf(forward_axis_value, -1.0, 1.0)
|
||||
_strafe_axis = clampf(strafe_axis_value, -1.0, 1.0)
|
||||
_vertical_axis = clampf(vertical_axis_value, -1.0, 1.0)
|
||||
_is_sprint_requested = sprint_requested
|
||||
_turn_axis = clampf(turn_axis_value, -1.0, 1.0)
|
||||
|
||||
|
||||
## Returns true when the intent requests planar or vertical translation.
|
||||
|
||||
@@ -2,13 +2,17 @@ class_name PlayerInputActions
|
||||
extends RefCounted
|
||||
|
||||
## Stable Input Map action names consumed by the runtime player input adapter.
|
||||
## Defaults preserve the current render-sandbox controls; users may remap every
|
||||
## action through Godot's Input Map without changing gameplay code.
|
||||
## Separate translation actions preserve the renderer sandbox while allowing
|
||||
## Blizzlike335 defaults to match build-12340 movement bindings.
|
||||
|
||||
const MOVE_FORWARD := &"openwc_player_move_forward"
|
||||
const MOVE_BACKWARD := &"openwc_player_move_backward"
|
||||
const STRAFE_LEFT := &"openwc_player_strafe_left"
|
||||
const STRAFE_RIGHT := &"openwc_player_strafe_right"
|
||||
const BLIZZLIKE_STRAFE_LEFT := &"openwc_player_blizzlike_strafe_left"
|
||||
const BLIZZLIKE_STRAFE_RIGHT := &"openwc_player_blizzlike_strafe_right"
|
||||
const TURN_LEFT := &"openwc_player_turn_left"
|
||||
const TURN_RIGHT := &"openwc_player_turn_right"
|
||||
const DEBUG_FLY_UP := &"openwc_player_debug_fly_up"
|
||||
const DEBUG_FLY_DOWN := &"openwc_player_debug_fly_down"
|
||||
const DEBUG_SPRINT := &"openwc_player_debug_sprint"
|
||||
@@ -23,6 +27,10 @@ const REQUIRED_ACTIONS: Array[StringName] = [
|
||||
MOVE_BACKWARD,
|
||||
STRAFE_LEFT,
|
||||
STRAFE_RIGHT,
|
||||
BLIZZLIKE_STRAFE_LEFT,
|
||||
BLIZZLIKE_STRAFE_RIGHT,
|
||||
TURN_LEFT,
|
||||
TURN_RIGHT,
|
||||
DEBUG_FLY_UP,
|
||||
DEBUG_FLY_DOWN,
|
||||
DEBUG_SPRINT,
|
||||
|
||||
@@ -5,21 +5,86 @@ extends RefCounted
|
||||
## This adapter owns no movement state and may be replaced independently of the
|
||||
## local movement controller.
|
||||
|
||||
const RENDER_SANDBOX_PROFILE_ID := &"RenderSandbox"
|
||||
const BLIZZLIKE_335_PROFILE_ID := &"Blizzlike335"
|
||||
|
||||
## Samples the current Input singleton state for one physics tick.
|
||||
func sample_move_intent() -> MoveIntent:
|
||||
return compose_move_intent(
|
||||
Input.get_action_strength(PlayerInputActions.MOVE_FORWARD),
|
||||
Input.get_action_strength(PlayerInputActions.MOVE_BACKWARD),
|
||||
Input.get_action_strength(PlayerInputActions.STRAFE_LEFT),
|
||||
Input.get_action_strength(PlayerInputActions.STRAFE_RIGHT),
|
||||
Input.get_action_strength(PlayerInputActions.DEBUG_FLY_UP),
|
||||
Input.get_action_strength(PlayerInputActions.DEBUG_FLY_DOWN),
|
||||
Input.is_action_pressed(PlayerInputActions.DEBUG_SPRINT)
|
||||
var input_profile_id: StringName:
|
||||
get:
|
||||
return _input_profile_id
|
||||
|
||||
var _input_profile_id: StringName
|
||||
|
||||
|
||||
## Selects one supported action layout. Unknown profiles fail closed to Blizzlike335.
|
||||
func _init(requested_profile_id: StringName = BLIZZLIKE_335_PROFILE_ID) -> void:
|
||||
_input_profile_id = (
|
||||
RENDER_SANDBOX_PROFILE_ID
|
||||
if requested_profile_id == RENDER_SANDBOX_PROFILE_ID
|
||||
else BLIZZLIKE_335_PROFILE_ID
|
||||
)
|
||||
|
||||
|
||||
## Builds a normalized intent from action strengths for deterministic tests and
|
||||
## Samples the current Input singleton state for one physics tick.
|
||||
func sample_move_intent() -> MoveIntent:
|
||||
if _input_profile_id == RENDER_SANDBOX_PROFILE_ID:
|
||||
return compose_move_intent(
|
||||
Input.get_action_strength(PlayerInputActions.MOVE_FORWARD),
|
||||
Input.get_action_strength(PlayerInputActions.MOVE_BACKWARD),
|
||||
Input.get_action_strength(PlayerInputActions.STRAFE_LEFT),
|
||||
Input.get_action_strength(PlayerInputActions.STRAFE_RIGHT),
|
||||
Input.get_action_strength(PlayerInputActions.DEBUG_FLY_UP),
|
||||
Input.get_action_strength(PlayerInputActions.DEBUG_FLY_DOWN),
|
||||
Input.is_action_pressed(PlayerInputActions.DEBUG_SPRINT)
|
||||
)
|
||||
return compose_blizzlike_335_move_intent(
|
||||
Input.get_action_strength(PlayerInputActions.MOVE_FORWARD),
|
||||
Input.get_action_strength(PlayerInputActions.MOVE_BACKWARD),
|
||||
Input.get_action_strength(PlayerInputActions.BLIZZLIKE_STRAFE_LEFT),
|
||||
Input.get_action_strength(PlayerInputActions.BLIZZLIKE_STRAFE_RIGHT),
|
||||
Input.get_action_strength(PlayerInputActions.TURN_LEFT),
|
||||
Input.get_action_strength(PlayerInputActions.TURN_RIGHT),
|
||||
Input.is_action_pressed(PlayerInputActions.CAMERA_ROTATE)
|
||||
)
|
||||
|
||||
|
||||
## Builds the compatibility layout: Q/E always strafe; A/D turn unless
|
||||
## right-mouse camera rotation is active, when A/D also strafe. Default bindings
|
||||
## come from build 12340; conditional routing is reference-implementation evidence.
|
||||
static func compose_blizzlike_335_move_intent(
|
||||
forward_strength: float,
|
||||
backward_strength: float,
|
||||
strafe_left_strength: float,
|
||||
strafe_right_strength: float,
|
||||
turn_left_strength: float,
|
||||
turn_right_strength: float,
|
||||
camera_rotate_active: bool
|
||||
) -> MoveIntent:
|
||||
var effective_strafe_left := strafe_left_strength
|
||||
var effective_strafe_right := strafe_right_strength
|
||||
var turn_axis := clampf(turn_right_strength, 0.0, 1.0) - clampf(turn_left_strength, 0.0, 1.0)
|
||||
if camera_rotate_active:
|
||||
effective_strafe_left += turn_left_strength
|
||||
effective_strafe_right += turn_right_strength
|
||||
turn_axis = 0.0
|
||||
var move_intent := compose_move_intent(
|
||||
forward_strength,
|
||||
backward_strength,
|
||||
effective_strafe_left,
|
||||
effective_strafe_right,
|
||||
0.0,
|
||||
0.0,
|
||||
false
|
||||
)
|
||||
return MoveIntent.new(
|
||||
move_intent.forward_axis,
|
||||
move_intent.strafe_axis,
|
||||
0.0,
|
||||
false,
|
||||
turn_axis
|
||||
)
|
||||
|
||||
|
||||
## Builds a normalized sandbox intent from action strengths for deterministic tests and
|
||||
## alternative input adapters. Strengths outside `[0, 1]` are clamped.
|
||||
static func compose_move_intent(
|
||||
forward_strength: float,
|
||||
|
||||
@@ -23,6 +23,7 @@ var _strafe_speed_units_per_second: float
|
||||
var _flight_vertical_speed_units_per_second: float
|
||||
var _acceleration_units_per_second_squared: float
|
||||
var _sandbox_sprint_multiplier: float
|
||||
var _keyboard_turn_speed_radians_per_second: float
|
||||
var _movement_capabilities: PlayerMovementCapabilities
|
||||
var _godot_world_velocity_units_per_second := Vector3.ZERO
|
||||
var _is_flight_enabled := false
|
||||
@@ -38,7 +39,8 @@ func _init(
|
||||
flight_vertical_speed_units_per_second: float,
|
||||
acceleration_units_per_second_squared: float,
|
||||
sandbox_sprint_multiplier: float,
|
||||
movement_capabilities: PlayerMovementCapabilities = null
|
||||
movement_capabilities: PlayerMovementCapabilities = null,
|
||||
keyboard_turn_speed_radians_per_second: float = PI
|
||||
) -> void:
|
||||
_run_speed_units_per_second = run_speed_units_per_second
|
||||
_backward_speed_units_per_second = backward_speed_units_per_second
|
||||
@@ -46,6 +48,10 @@ func _init(
|
||||
_flight_vertical_speed_units_per_second = flight_vertical_speed_units_per_second
|
||||
_acceleration_units_per_second_squared = acceleration_units_per_second_squared
|
||||
_sandbox_sprint_multiplier = sandbox_sprint_multiplier
|
||||
_keyboard_turn_speed_radians_per_second = maxf(
|
||||
keyboard_turn_speed_radians_per_second,
|
||||
0.0
|
||||
)
|
||||
_movement_capabilities = (
|
||||
movement_capabilities
|
||||
if movement_capabilities != null
|
||||
@@ -53,6 +59,16 @@ func _init(
|
||||
)
|
||||
|
||||
|
||||
## Returns the Godot yaw delta for one signed keyboard-turn request.
|
||||
## Positive intent turns right, which is negative rotation around Godot +Y.
|
||||
func calculate_yaw_delta_radians(move_intent: MoveIntent, delta_seconds: float) -> float:
|
||||
return (
|
||||
-move_intent.turn_axis
|
||||
* _keyboard_turn_speed_radians_per_second
|
||||
* maxf(delta_seconds, 0.0)
|
||||
)
|
||||
|
||||
|
||||
## Toggles sandbox free flight and clears velocity when the profile permits it.
|
||||
## Returns the resulting state; rejected compatibility-profile requests return false.
|
||||
func toggle_sandbox_flight() -> bool:
|
||||
|
||||
@@ -94,6 +94,12 @@ func initialize_for_character(character_body: Node3D) -> void:
|
||||
refresh_camera_transform()
|
||||
|
||||
|
||||
## Synchronizes orbit yaw after keyboard turning changes the character transform.
|
||||
func synchronize_yaw_from_character() -> void:
|
||||
if _character_body != null:
|
||||
_yaw_radians = _character_body.rotation.y
|
||||
|
||||
|
||||
## Replaces camera-distance collision behavior and immediately refreshes the camera.
|
||||
## Passing null is rejected and preserves the current policy.
|
||||
func set_collision_policy(collision_policy: CameraCollisionPolicy) -> void:
|
||||
|
||||
@@ -22,6 +22,8 @@ const ADT_TERRAIN_QUERY_SCRIPT := preload("res://src/gameplay/terrain/adt_terrai
|
||||
@export var sprint_multiplier: float = 6.0
|
||||
@export var flight_vertical_speed: float = 7.0
|
||||
@export var acceleration: float = 28.0
|
||||
## TrinityCore 3.3.5 player MOVE_TURN_RATE default, in radians per second.
|
||||
@export var keyboard_turn_speed_radians_per_second: float = 3.141594
|
||||
## Local composition profile. RenderSandbox preserves debug sprint/free flight;
|
||||
## Blizzlike335 rejects them but does not yet claim complete movement parity.
|
||||
@export_enum("RenderSandbox", "Blizzlike335") var movement_profile_id: String = "RenderSandbox"
|
||||
@@ -50,7 +52,7 @@ func set_terrain_query(terrain_query: TerrainQuery) -> void:
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_player_input_source = PLAYER_INPUT_SOURCE_SCRIPT.new()
|
||||
_player_input_source = PLAYER_INPUT_SOURCE_SCRIPT.new(StringName(movement_profile_id))
|
||||
if _terrain_query == null:
|
||||
_terrain_query = ADT_TERRAIN_QUERY_SCRIPT.new(extracted_dir, map_name)
|
||||
_local_movement_controller = LOCAL_PLAYER_MOVEMENT_CONTROLLER_SCRIPT.new(
|
||||
@@ -60,7 +62,8 @@ func _ready() -> void:
|
||||
flight_vertical_speed,
|
||||
acceleration,
|
||||
sprint_multiplier,
|
||||
PLAYER_MOVEMENT_CAPABILITIES_SCRIPT.for_profile_id(StringName(movement_profile_id))
|
||||
PLAYER_MOVEMENT_CAPABILITIES_SCRIPT.for_profile_id(StringName(movement_profile_id)),
|
||||
keyboard_turn_speed_radians_per_second
|
||||
)
|
||||
_camera_rig = get_node_or_null(camera_pivot_path) as ThirdPersonCameraRig
|
||||
_appearance_presenter = get_node_or_null(visual_path) as CharacterAppearancePresenter
|
||||
@@ -96,6 +99,14 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
var move_intent := _player_input_source.sample_move_intent()
|
||||
var yaw_delta_radians := _local_movement_controller.calculate_yaw_delta_radians(
|
||||
move_intent,
|
||||
delta
|
||||
)
|
||||
if not is_zero_approx(yaw_delta_radians):
|
||||
rotation.y += yaw_delta_radians
|
||||
if _camera_rig != null:
|
||||
_camera_rig.synchronize_yaw_from_character()
|
||||
var godot_world_movement_basis := global_basis
|
||||
if _local_movement_controller.is_flight_enabled and _camera_rig != null:
|
||||
godot_world_movement_basis = _camera_rig.godot_world_flight_movement_basis()
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"profile_id": "Blizzlike335",
|
||||
"sources": {
|
||||
"original_client_default_bindings": {
|
||||
"status": "verified_private_metadata",
|
||||
"build": 12340,
|
||||
"path": "WTF/DefaultBindings.wtf",
|
||||
"sha256": "35FFA0E4E2356A13A0633A848F9E57ABE7DF9D8A16C2245E15D33013AD2F56A5",
|
||||
"distribution": "Selected binding facts only; proprietary file remains outside Git."
|
||||
},
|
||||
"original_client_binding_commands": {
|
||||
"status": "verified_private_metadata",
|
||||
"build": 12340,
|
||||
"path": "Interface/FrameXML/Bindings.xml",
|
||||
"sha256": "2E01276AFB7462F1417710F13B4BF1A456341F7F28CC8AE0D6D9C7442D7F6793",
|
||||
"distribution": "Hash and selected command names only; proprietary file remains outside Git."
|
||||
},
|
||||
"trinitycore_player_turn_rate": {
|
||||
"status": "verified_public_source",
|
||||
"repository": "TrinityCore/TrinityCore",
|
||||
"revision": "2853a621d6af91a803787a2b8a509f4ce3e0300d",
|
||||
"path": "src/server/game/Entities/Unit/Unit.cpp",
|
||||
"url": "https://raw.githubusercontent.com/TrinityCore/TrinityCore/2853a621d6af91a803787a2b8a509f4ce3e0300d/src/server/game/Entities/Unit/Unit.cpp",
|
||||
"symbol": "playerBaseMoveSpeed[MOVE_TURN_RATE]",
|
||||
"radians_per_second": 3.141594
|
||||
},
|
||||
"wowee_mouse_strafe_reference": {
|
||||
"status": "verified_public_reference_only",
|
||||
"repository": "Kelsidavis/WoWee",
|
||||
"revision": "626243e937fb93965fa583a6507ed5a1aa7dda4b",
|
||||
"path": "src/rendering/camera_controller.cpp",
|
||||
"sha256": "54A41EFDA77629B2EAB432734A94D0F6F58223F3E4752338FF4C321E7835001B",
|
||||
"observation": "A/D turn while right mouse is released and strafe while it is held; Q/E always strafe.",
|
||||
"fidelity_limit": "Reference implementation behavior, not original-client proof."
|
||||
}
|
||||
},
|
||||
"selected_default_bindings": [
|
||||
{"command": "MOVEFORWARD", "keys": ["W", "UP"], "implemented_action": "openwc_player_move_forward"},
|
||||
{"command": "MOVEBACKWARD", "keys": ["S", "DOWN"], "implemented_action": "openwc_player_move_backward"},
|
||||
{"command": "TURNLEFT", "keys": ["A", "LEFT"], "implemented_action": "openwc_player_turn_left"},
|
||||
{"command": "TURNRIGHT", "keys": ["D", "RIGHT"], "implemented_action": "openwc_player_turn_right"},
|
||||
{"command": "STRAFELEFT", "keys": ["Q"], "implemented_action": "openwc_player_blizzlike_strafe_left"},
|
||||
{"command": "STRAFERIGHT", "keys": ["E"], "implemented_action": "openwc_player_blizzlike_strafe_right"},
|
||||
{"command": "JUMP", "keys": ["SPACE", "NUMPAD0"], "implemented_action": null},
|
||||
{"command": "TOGGLEAUTORUN", "keys": ["NUMLOCK", "BUTTON4"], "implemented_action": null},
|
||||
{"command": "CAMERAZOOMIN", "keys": ["MOUSEWHEELUP"], "implemented_action": "openwc_player_camera_zoom_in"},
|
||||
{"command": "CAMERAZOOMOUT", "keys": ["MOUSEWHEELDOWN"], "implemented_action": "openwc_player_camera_zoom_out"},
|
||||
{"command": "TURNORACTION", "keys": ["BUTTON2"], "implemented_action": "openwc_player_camera_rotate"},
|
||||
{"command": "CAMERAORSELECTORMOVE", "keys": ["BUTTON1"], "implemented_action": null}
|
||||
],
|
||||
"known_gaps": [
|
||||
"Jump, autorun and left-button camera/select behavior are recorded but not implemented by this package.",
|
||||
"The server-core turn-rate default is compatibility evidence, not direct original-client timing proof.",
|
||||
"Right-mouse A/D strafe routing is supported by a public reference implementation, not direct original-client capture."
|
||||
]
|
||||
}
|
||||
@@ -12,6 +12,7 @@ func _initialize() -> void:
|
||||
_verify_acceleration_and_deceleration(failures)
|
||||
_verify_sandbox_sprint(failures)
|
||||
_verify_flight_state_and_basis(failures)
|
||||
_verify_keyboard_turn(failures)
|
||||
_verify_invalid_delta(failures)
|
||||
_verify_scene_boundary(failures)
|
||||
|
||||
@@ -21,7 +22,7 @@ func _initialize() -> void:
|
||||
quit(1)
|
||||
return
|
||||
|
||||
print("LOCAL_PLAYER_MOVEMENT PASS cases=12 state_transitions=2 scene_boundary=1")
|
||||
print("LOCAL_PLAYER_MOVEMENT PASS cases=15 state_transitions=2 scene_boundary=1")
|
||||
quit(0)
|
||||
|
||||
|
||||
@@ -88,6 +89,13 @@ func _verify_invalid_delta(failures: Array[String]) -> void:
|
||||
_expect_vector_near(controller.godot_world_velocity_units_per_second, Vector3.ZERO, "negative delta state", failures)
|
||||
|
||||
|
||||
func _verify_keyboard_turn(failures: Array[String]) -> void:
|
||||
var controller := _new_controller()
|
||||
_expect_near(controller.calculate_yaw_delta_radians(_intent(0.0, 0.0, 0.0, false, 1.0), 0.5), -PI * 0.5, "right turn yaw", failures)
|
||||
_expect_near(controller.calculate_yaw_delta_radians(_intent(0.0, 0.0, 0.0, false, -1.0), 0.5), PI * 0.5, "left turn yaw", failures)
|
||||
_expect_near(controller.calculate_yaw_delta_radians(_intent(0.0, 0.0, 0.0, false, 1.0), -1.0), 0.0, "negative turn delta", failures)
|
||||
|
||||
|
||||
func _verify_scene_boundary(failures: Array[String]) -> void:
|
||||
var movement_source := _read_text(MOVEMENT_CONTROLLER_PATH, failures)
|
||||
for forbidden_text in ["extends Node", "extends Resource", "Input.", "Camera3D", "ADTLoader", "global_position"]:
|
||||
@@ -117,9 +125,10 @@ func _intent(
|
||||
forward_axis: float,
|
||||
strafe_axis: float,
|
||||
vertical_axis: float = 0.0,
|
||||
sprint_requested: bool = false
|
||||
sprint_requested: bool = false,
|
||||
turn_axis: float = 0.0
|
||||
) -> MoveIntent:
|
||||
return MoveIntent.new(forward_axis, strafe_axis, vertical_axis, sprint_requested)
|
||||
return MoveIntent.new(forward_axis, strafe_axis, vertical_axis, sprint_requested, turn_axis)
|
||||
|
||||
|
||||
func _read_text(path: String, failures: Array[String]) -> String:
|
||||
@@ -140,6 +149,11 @@ func _expect_vector_near(
|
||||
failures.append("%s expected %s, got %s" % [label, expected_value, actual_value])
|
||||
|
||||
|
||||
func _expect_near(actual_value: float, expected_value: float, label: String, failures: Array[String]) -> void:
|
||||
if absf(actual_value - expected_value) > 0.000001:
|
||||
failures.append("%s expected %.6f, got %.6f" % [label, expected_value, actual_value])
|
||||
|
||||
|
||||
func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
|
||||
if not actual_value:
|
||||
failures.append("%s expected true" % label)
|
||||
|
||||
@@ -6,6 +6,7 @@ const MOVE_INTENT_PATH := "res://src/domain/input/move_intent.gd"
|
||||
const PLAYER_INPUT_SOURCE_PATH := "res://src/gameplay/input/player_input_source.gd"
|
||||
const PLAYER_CONTROLLER_PATH := "res://src/scenes/player/third_person_wow_controller.gd"
|
||||
const REGRESSION_SCENE_PATH := "res://src/tests/scenes/player_input_regression.tscn"
|
||||
const PROFILE_FIXTURE_PATH := "res://src/tests/fixtures/player_input_profile_335.json"
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
@@ -15,6 +16,7 @@ func _initialize() -> void:
|
||||
func _run_verification() -> void:
|
||||
var failures: Array[String] = []
|
||||
_verify_default_input_actions(failures)
|
||||
_verify_profile_fixture(failures)
|
||||
_verify_move_intent_composition(failures)
|
||||
_verify_controller_boundary(failures)
|
||||
_verify_regression_scene(failures)
|
||||
@@ -25,7 +27,7 @@ func _run_verification() -> void:
|
||||
quit(1)
|
||||
return
|
||||
|
||||
print("PLAYER_INPUT PASS actions=%d intent_cases=6 controller=1 regression_scene=1" % PlayerInputActions.REQUIRED_ACTIONS.size())
|
||||
print("PLAYER_INPUT PASS actions=%d intent_cases=9 profiles=3 fixture=1 controller=1 regression_scene=3" % PlayerInputActions.REQUIRED_ACTIONS.size())
|
||||
quit(0)
|
||||
|
||||
|
||||
@@ -34,9 +36,17 @@ func _verify_default_input_actions(failures: Array[String]) -> void:
|
||||
_expect_true(InputMap.has_action(action_name), "Input Map contains %s" % action_name, failures)
|
||||
|
||||
_expect_key_binding(PlayerInputActions.MOVE_FORWARD, KEY_W, true, failures)
|
||||
_expect_key_binding(PlayerInputActions.MOVE_FORWARD, KEY_UP, false, failures)
|
||||
_expect_key_binding(PlayerInputActions.MOVE_BACKWARD, KEY_S, true, failures)
|
||||
_expect_key_binding(PlayerInputActions.MOVE_BACKWARD, KEY_DOWN, false, failures)
|
||||
_expect_key_binding(PlayerInputActions.STRAFE_LEFT, KEY_A, true, failures)
|
||||
_expect_key_binding(PlayerInputActions.STRAFE_RIGHT, KEY_D, true, failures)
|
||||
_expect_key_binding(PlayerInputActions.BLIZZLIKE_STRAFE_LEFT, KEY_Q, true, failures)
|
||||
_expect_key_binding(PlayerInputActions.BLIZZLIKE_STRAFE_RIGHT, KEY_E, true, failures)
|
||||
_expect_key_binding(PlayerInputActions.TURN_LEFT, KEY_A, true, failures)
|
||||
_expect_key_binding(PlayerInputActions.TURN_LEFT, KEY_LEFT, false, failures)
|
||||
_expect_key_binding(PlayerInputActions.TURN_RIGHT, KEY_D, true, failures)
|
||||
_expect_key_binding(PlayerInputActions.TURN_RIGHT, KEY_RIGHT, false, failures)
|
||||
_expect_key_binding(PlayerInputActions.DEBUG_FLY_UP, KEY_E, true, failures)
|
||||
_expect_key_binding(PlayerInputActions.DEBUG_FLY_DOWN, KEY_Q, true, failures)
|
||||
_expect_key_binding(PlayerInputActions.DEBUG_SPRINT, KEY_SHIFT, false, failures)
|
||||
@@ -47,6 +57,40 @@ func _verify_default_input_actions(failures: Array[String]) -> void:
|
||||
_expect_mouse_binding(PlayerInputActions.CAMERA_ZOOM_OUT, MOUSE_BUTTON_WHEEL_DOWN, failures)
|
||||
|
||||
|
||||
func _verify_profile_fixture(failures: Array[String]) -> void:
|
||||
var fixture_text := _read_text(PROFILE_FIXTURE_PATH, failures)
|
||||
var parsed_fixture: Variant = JSON.parse_string(fixture_text)
|
||||
_expect_true(parsed_fixture is Dictionary, "profile fixture parses", failures)
|
||||
if not parsed_fixture is Dictionary:
|
||||
return
|
||||
var fixture := parsed_fixture as Dictionary
|
||||
_expect_true(fixture.get("schema_version") == 1, "profile fixture schema", failures)
|
||||
_expect_true(fixture.get("profile_id") == "Blizzlike335", "profile fixture identity", failures)
|
||||
var sources := fixture.get("sources", {}) as Dictionary
|
||||
var bindings_source := sources.get("original_client_default_bindings", {}) as Dictionary
|
||||
_expect_true(bindings_source.get("build") == 12340, "fixture client build", failures)
|
||||
_expect_true(
|
||||
bindings_source.get("sha256") == "35FFA0E4E2356A13A0633A848F9E57ABE7DF9D8A16C2245E15D33013AD2F56A5",
|
||||
"fixture binding source hash",
|
||||
failures
|
||||
)
|
||||
var commands_source := sources.get("original_client_binding_commands", {}) as Dictionary
|
||||
_expect_true(
|
||||
commands_source.get("sha256") == "2E01276AFB7462F1417710F13B4BF1A456341F7F28CC8AE0D6D9C7442D7F6793",
|
||||
"fixture command source hash",
|
||||
failures
|
||||
)
|
||||
var turn_source := sources.get("trinitycore_player_turn_rate", {}) as Dictionary
|
||||
_expect_near(float(turn_source.get("radians_per_second", 0.0)), 3.141594, "fixture turn rate", failures)
|
||||
var mouse_strafe_source := sources.get("wowee_mouse_strafe_reference", {}) as Dictionary
|
||||
_expect_true(
|
||||
mouse_strafe_source.get("revision") == "626243e937fb93965fa583a6507ed5a1aa7dda4b",
|
||||
"fixture mouse-strafe reference revision",
|
||||
failures
|
||||
)
|
||||
_expect_true((fixture.get("selected_default_bindings", []) as Array).size() == 12, "fixture selected binding count", failures)
|
||||
|
||||
|
||||
func _verify_move_intent_composition(failures: Array[String]) -> void:
|
||||
var neutral := PlayerInputSource.compose_move_intent(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)
|
||||
_expect_near(neutral.forward_axis, 0.0, "neutral forward", failures)
|
||||
@@ -67,6 +111,18 @@ func _verify_move_intent_composition(failures: Array[String]) -> void:
|
||||
var debug_requests := PlayerInputSource.compose_move_intent(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, true)
|
||||
_expect_true(debug_requests.is_sprint_requested, "sprint request retained", failures)
|
||||
|
||||
var blizzlike_strafe := PlayerInputSource.compose_blizzlike_335_move_intent(0.0, 0.0, 1.0, 0.0, 0.0, 0.0, false)
|
||||
_expect_near(blizzlike_strafe.strafe_axis, -1.0, "blizzlike Q strafe", failures)
|
||||
var blizzlike_turn := PlayerInputSource.compose_blizzlike_335_move_intent(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, false)
|
||||
_expect_near(blizzlike_turn.turn_axis, 1.0, "blizzlike D turn", failures)
|
||||
var mouse_strafe := PlayerInputSource.compose_blizzlike_335_move_intent(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, true)
|
||||
_expect_near(mouse_strafe.strafe_axis, 1.0, "camera-held D strafe", failures)
|
||||
_expect_near(mouse_strafe.turn_axis, 0.0, "camera-held D suppresses turn", failures)
|
||||
|
||||
_expect_true(PlayerInputSource.new(&"RenderSandbox").input_profile_id == &"RenderSandbox", "sandbox profile selected", failures)
|
||||
_expect_true(PlayerInputSource.new(&"Blizzlike335").input_profile_id == &"Blizzlike335", "blizzlike profile selected", failures)
|
||||
_expect_true(PlayerInputSource.new(&"Unknown").input_profile_id == &"Blizzlike335", "unknown profile fails closed", failures)
|
||||
|
||||
|
||||
func _verify_controller_boundary(failures: Array[String]) -> void:
|
||||
var controller_source := _read_text(PLAYER_CONTROLLER_PATH, failures)
|
||||
@@ -118,6 +174,31 @@ func _verify_regression_scene(failures: Array[String]) -> void:
|
||||
_expect_true(movement_controller.is_flight_enabled, "flight action preserves immediate sandbox toggle", failures)
|
||||
player.free()
|
||||
|
||||
var blizzlike_player := packed_scene.instantiate() as CharacterBody3D
|
||||
blizzlike_player.set("movement_profile_id", "Blizzlike335")
|
||||
root.add_child(blizzlike_player)
|
||||
blizzlike_player.set_physics_process(false)
|
||||
Input.action_press(PlayerInputActions.TURN_RIGHT)
|
||||
blizzlike_player.call("_physics_process", 0.5)
|
||||
Input.action_release(PlayerInputActions.TURN_RIGHT)
|
||||
_expect_near(blizzlike_player.rotation.y, -3.141594 * 0.5, "blizzlike D turns right", failures)
|
||||
var blizzlike_camera := blizzlike_player.get_node("CameraPivot") as ThirdPersonCameraRig
|
||||
_expect_near(blizzlike_camera.yaw_radians, blizzlike_player.rotation.y, "keyboard turn synchronizes camera yaw", failures)
|
||||
blizzlike_player.free()
|
||||
|
||||
var mouse_strafe_player := packed_scene.instantiate() as CharacterBody3D
|
||||
mouse_strafe_player.set("movement_profile_id", "Blizzlike335")
|
||||
root.add_child(mouse_strafe_player)
|
||||
mouse_strafe_player.set_physics_process(false)
|
||||
Input.action_press(PlayerInputActions.CAMERA_ROTATE)
|
||||
Input.action_press(PlayerInputActions.TURN_RIGHT)
|
||||
mouse_strafe_player.call("_physics_process", 0.5)
|
||||
Input.action_release(PlayerInputActions.TURN_RIGHT)
|
||||
Input.action_release(PlayerInputActions.CAMERA_ROTATE)
|
||||
_expect_near(mouse_strafe_player.rotation.y, 0.0, "camera-held D does not turn", failures)
|
||||
_expect_near(mouse_strafe_player.position.x, 2.25, "camera-held D strafes right", failures)
|
||||
mouse_strafe_player.free()
|
||||
|
||||
|
||||
func _expect_key_binding(
|
||||
action_name: StringName,
|
||||
|
||||
Reference in New Issue
Block a user