97bb53f6cd
Work-Package: M02-RND-CHARACTER-PRESENTATION-001 Agent: sindo-main-codex Tests: presentation/input/movement/terrain/camera regressions; coordinate/streaming/docs/coordination gates; renderer dry-run 7/7 Fidelity: preserves current sandbox model/outfit and Stand/Idle/Run/Walk behavior; build-12340 parity remains unverified
127 lines
5.9 KiB
GDScript
127 lines
5.9 KiB
GDScript
extends CharacterBody3D
|
|
|
|
const COORDINATE_MAPPER_SCRIPT := preload("res://src/domain/coordinates/coordinate_mapper.gd")
|
|
const GODOT_WORLD_POSITION_SCRIPT := preload("res://src/domain/coordinates/godot_world_position.gd")
|
|
const ADT_TILE_COORDINATE_SCRIPT := preload("res://src/domain/coordinates/adt_tile_coordinate.gd")
|
|
const ADT_TILE_LOCAL_POSITION_SCRIPT := preload("res://src/domain/coordinates/adt_tile_local_position.gd")
|
|
const PLAYER_INPUT_SOURCE_SCRIPT := preload("res://src/gameplay/input/player_input_source.gd")
|
|
const PLAYER_INPUT_ACTIONS_SCRIPT := preload("res://src/gameplay/input/player_input_actions.gd")
|
|
const LOCAL_PLAYER_MOVEMENT_CONTROLLER_SCRIPT := preload("res://src/gameplay/movement/local_player_movement_controller.gd")
|
|
const ADT_TERRAIN_QUERY_SCRIPT := preload("res://src/gameplay/terrain/adt_terrain_query.gd")
|
|
|
|
@export var extracted_dir: String = "res://data/extracted"
|
|
@export var map_name: String = "Azeroth"
|
|
@export var spawn_tile_x: int = 42
|
|
@export var spawn_tile_y: int = 28
|
|
@export var spawn_at_tile_center: bool = true
|
|
|
|
@export var run_speed: float = 7.0
|
|
@export var backward_speed: float = 4.5
|
|
@export var strafe_speed: float = 4.5
|
|
@export var sprint_multiplier: float = 6.0
|
|
@export var flight_vertical_speed: float = 7.0
|
|
@export var acceleration: float = 28.0
|
|
@export var ground_offset: float = 0.05
|
|
@export var ground_snap_speed: float = 24.0
|
|
|
|
@export var camera_pivot_path: NodePath = NodePath("CameraPivot")
|
|
@export var visual_path: NodePath = NodePath("Visual")
|
|
@export var animation_presenter_path: NodePath = NodePath("Visual/AnimationPresenter")
|
|
|
|
var _camera_rig: ThirdPersonCameraRig
|
|
var _appearance_presenter: CharacterAppearancePresenter
|
|
var _animation_presenter: CharacterAnimationPresenter
|
|
var _player_input_source: PlayerInputSource
|
|
var _local_movement_controller: LocalPlayerMovementController
|
|
var _terrain_query: TerrainQuery
|
|
|
|
|
|
## Replaces the ground-height backend without changing player movement or presentation.
|
|
## Passing null is a composition error and leaves the current query unchanged.
|
|
func set_terrain_query(terrain_query: TerrainQuery) -> void:
|
|
if terrain_query == null:
|
|
push_error("ThirdPersonWowController: terrain query cannot be null")
|
|
return
|
|
_terrain_query = terrain_query
|
|
|
|
|
|
func _ready() -> void:
|
|
_player_input_source = PLAYER_INPUT_SOURCE_SCRIPT.new()
|
|
if _terrain_query == null:
|
|
_terrain_query = ADT_TERRAIN_QUERY_SCRIPT.new(extracted_dir, map_name)
|
|
_local_movement_controller = LOCAL_PLAYER_MOVEMENT_CONTROLLER_SCRIPT.new(
|
|
run_speed,
|
|
backward_speed,
|
|
strafe_speed,
|
|
flight_vertical_speed,
|
|
acceleration,
|
|
sprint_multiplier
|
|
)
|
|
_camera_rig = get_node_or_null(camera_pivot_path) as ThirdPersonCameraRig
|
|
_appearance_presenter = get_node_or_null(visual_path) as CharacterAppearancePresenter
|
|
_animation_presenter = get_node_or_null(animation_presenter_path) as CharacterAnimationPresenter
|
|
if _camera_rig == null:
|
|
push_error("ThirdPersonWowController: ThirdPersonCameraRig not found at %s" % camera_pivot_path)
|
|
else:
|
|
_camera_rig.initialize_for_character(self)
|
|
if _appearance_presenter == null:
|
|
push_error("ThirdPersonWowController: CharacterAppearancePresenter not found at %s" % visual_path)
|
|
else:
|
|
_appearance_presenter.character_appearance_ready.connect(_on_character_appearance_ready)
|
|
_appearance_presenter.load_character_appearance(extracted_dir)
|
|
if _animation_presenter == null:
|
|
push_error("ThirdPersonWowController: CharacterAnimationPresenter not found at %s" % animation_presenter_path)
|
|
if spawn_at_tile_center:
|
|
var spawn_tile = ADT_TILE_COORDINATE_SCRIPT.new(spawn_tile_x, spawn_tile_y)
|
|
var half_tile_size := COORDINATE_MAPPER_SCRIPT.ADT_TILE_SIZE_YARDS * 0.5
|
|
var spawn_local = ADT_TILE_LOCAL_POSITION_SCRIPT.new(half_tile_size, global_position.y, half_tile_size)
|
|
var spawn_position = COORDINATE_MAPPER_SCRIPT.adt_tile_local_to_godot(spawn_tile, spawn_local)
|
|
global_position = Vector3(spawn_position.x_units, spawn_position.y_units, spawn_position.z_units)
|
|
var spawn_ground_sample := _sample_ground_height(global_position)
|
|
if spawn_ground_sample.is_available:
|
|
global_position.y = spawn_ground_sample.height_units + ground_offset
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if _camera_rig != null:
|
|
_camera_rig.handle_camera_input(event)
|
|
if event.is_action_pressed(PLAYER_INPUT_ACTIONS_SCRIPT.DEBUG_TOGGLE_FLIGHT) and not event.is_echo():
|
|
_local_movement_controller.toggle_sandbox_flight()
|
|
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
var move_intent := _player_input_source.sample_move_intent()
|
|
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()
|
|
global_position += _local_movement_controller.advance(move_intent, godot_world_movement_basis, delta)
|
|
|
|
if not _local_movement_controller.is_flight_enabled:
|
|
var ground_sample := _sample_ground_height(global_position)
|
|
if ground_sample.is_available:
|
|
var target_y := ground_sample.height_units + ground_offset
|
|
global_position.y = lerpf(global_position.y, target_y, clampf(ground_snap_speed * delta, 0.0, 1.0))
|
|
|
|
var movement_velocity := _local_movement_controller.godot_world_velocity_units_per_second
|
|
var horizontal_motion := Vector2(movement_velocity.x, movement_velocity.z)
|
|
if _appearance_presenter != null and horizontal_motion.length_squared() > 0.01:
|
|
_appearance_presenter.global_rotation.y = atan2(-movement_velocity.x, -movement_velocity.z)
|
|
|
|
if _animation_presenter != null:
|
|
_animation_presenter.present_locomotion(horizontal_motion.length_squared() > 0.04)
|
|
|
|
|
|
func _on_character_appearance_ready(character_root: Node3D) -> void:
|
|
if _animation_presenter != null:
|
|
_animation_presenter.bind_character_root(character_root)
|
|
|
|
|
|
func _sample_ground_height(godot_world_position: Vector3) -> TerrainGroundSample:
|
|
return _terrain_query.sample_ground_height(
|
|
GODOT_WORLD_POSITION_SCRIPT.new(
|
|
godot_world_position.x,
|
|
godot_world_position.y,
|
|
godot_world_position.z
|
|
)
|
|
)
|