237 lines
6.7 KiB
C#
237 lines
6.7 KiB
C#
using EinSoftworks.Events;
|
|
using EinSoftworks.Input;
|
|
using EinSoftworks.Movement;
|
|
using Godot;
|
|
|
|
namespace Voider.Player;
|
|
|
|
public partial class Player : FirstPersonController
|
|
{
|
|
[Export]
|
|
public Label SpeedVal { get; set; }
|
|
|
|
[Export]
|
|
public Label StateVal { get; set; }
|
|
|
|
[Export]
|
|
public Label PositionVal { get; set; }
|
|
|
|
[Export]
|
|
public Label VelocityVal { get; set; }
|
|
|
|
private float _lastSpeed = 0f;
|
|
private float _lastDisplayedSpeed = 0f;
|
|
private Vector3 _lastDisplayedPosition;
|
|
private Vector3 _lastDisplayedVelocity;
|
|
private string _lastDisplayedState = "";
|
|
private const float UI_UPDATE_THRESHOLD = 0.1f;
|
|
|
|
public override void _Ready()
|
|
{
|
|
// InputManager is initialized by the library
|
|
InputManager = InputManager.Instance;
|
|
|
|
// Assign CameraNode from scene before calling base._Ready()
|
|
if (CameraNode == null)
|
|
{
|
|
CameraNode = GetNode<Node3D>("CameraMount");
|
|
}
|
|
|
|
// Set FOV and camera settings before base._Ready()
|
|
BaseFov = 75f;
|
|
MouseSensitivity = 0.35f;
|
|
ControllerSensitivity = 3.0f;
|
|
MinPitch = -89f;
|
|
MaxPitch = 89f;
|
|
EnableDebugOutput = true;
|
|
|
|
base._Ready();
|
|
|
|
// Try to find UI labels if they're not already assigned
|
|
// This allows the scene to work standalone or with external UI
|
|
if (SpeedVal == null || StateVal == null || PositionVal == null || VelocityVal == null)
|
|
{
|
|
// Try to find DebugHUD in the scene tree
|
|
var ui = GetNodeOrNull<Control>("/root/LibraryTest/UI/DebugHUD");
|
|
if (ui == null)
|
|
{
|
|
ui = GetNodeOrNull<Control>("/root/Main/UI/DebugHUD");
|
|
}
|
|
|
|
if (ui != null)
|
|
{
|
|
SpeedVal = ui.GetNodeOrNull<Label>("StatsPanel/VBoxContainerValues/SpeedVal");
|
|
StateVal = ui.GetNodeOrNull<Label>("StatsPanel/VBoxContainerValues/StateVal");
|
|
PositionVal = ui.GetNodeOrNull<Label>("StatsPanel/VBoxContainerValues/PositionVal");
|
|
VelocityVal = ui.GetNodeOrNull<Label>("StatsPanel/VBoxContainerValues/VelocityVal");
|
|
}
|
|
}
|
|
|
|
Config = new MovementConfig
|
|
{
|
|
// Base speeds (HL2-style)
|
|
MaxSpeed = 5f,
|
|
MaxSprintSpeed = 8f,
|
|
MaxWalkSpeed = 3f,
|
|
MaxCrouchSpeed = 2f,
|
|
Acceleration = 8f,
|
|
AirAcceleration = 1.5f,
|
|
AirSpeedCap = 8f,
|
|
AirControl = 0.3f,
|
|
Friction = 6f,
|
|
StopSpeed = 1f,
|
|
JumpVelocity = 5f,
|
|
Gravity = 15f,
|
|
|
|
// Bunny hopping with momentum preservation
|
|
EnableBunnyHopping = true,
|
|
EnableAutoHop = false,
|
|
BunnyHopSpeedMultiplier = 1.05f,
|
|
|
|
// Crouching
|
|
EnableCrouching = true,
|
|
CrouchHeight = 0.5f,
|
|
|
|
// Titanfall 2-style sliding
|
|
EnableSliding = true,
|
|
SlideSpeed = 10f,
|
|
SlideAcceleration = 15f,
|
|
SlideFriction = 3f,
|
|
SlideMinSpeed = 5f,
|
|
SlideJumpBoost = 1.2f,
|
|
SlideDuration = 1.2f,
|
|
|
|
EnableSurfing = true,
|
|
};
|
|
|
|
MouseSensitivity = 0.35f;
|
|
EnableDebugOutput = false;
|
|
|
|
#if DEBUG
|
|
GD.Print($"InputManager assigned: {InputManager != null}");
|
|
GD.Print($"CameraNode assigned: {CameraNode != null}");
|
|
GD.Print($"Config assigned: {Config != null}");
|
|
#endif
|
|
|
|
SubscribeToStateChanges(OnMovementStateChanged);
|
|
|
|
MovementEvents.PlayerJumped += OnPlayerJumped;
|
|
MovementEvents.SpeedChanged += OnSpeedChanged;
|
|
|
|
#if DEBUG
|
|
GD.Print("TestPlayer initialized with all libraries!");
|
|
GD.Print("- Movement: Enabled");
|
|
GD.Print("- Input: Enabled");
|
|
GD.Print("- Events: Enabled");
|
|
GD.Print("- StateManagement: Enabled");
|
|
GD.Print("- Camera: Enabled");
|
|
#endif
|
|
}
|
|
|
|
public override void _Process(double delta)
|
|
{
|
|
base._Process(delta);
|
|
|
|
UpdateUI();
|
|
}
|
|
|
|
private void UpdateUI()
|
|
{
|
|
if (SpeedVal != null && Mathf.Abs(CurrentSpeed - _lastDisplayedSpeed) > UI_UPDATE_THRESHOLD)
|
|
{
|
|
SpeedVal.Text = $"{CurrentSpeed:F1} u/s ";
|
|
_lastDisplayedSpeed = CurrentSpeed;
|
|
}
|
|
|
|
if (StateVal != null)
|
|
{
|
|
string state = "Unknown";
|
|
if (IsCrouching)
|
|
{
|
|
state = "Crouching";
|
|
}
|
|
else if (IsOnFloor())
|
|
{
|
|
if (WishDirection.LengthSquared() > 0.01f)
|
|
state = "Walking";
|
|
else
|
|
state = "Idle";
|
|
}
|
|
else
|
|
{
|
|
state = "Airborne";
|
|
}
|
|
|
|
if (state != _lastDisplayedState)
|
|
{
|
|
StateVal.Text = $"{state} ";
|
|
_lastDisplayedState = state;
|
|
}
|
|
}
|
|
|
|
if (PositionVal != null && GlobalPosition.DistanceSquaredTo(_lastDisplayedPosition) > UI_UPDATE_THRESHOLD)
|
|
{
|
|
PositionVal.Text =
|
|
$"({GlobalPosition.X:F1}, {GlobalPosition.Y:F1}, {GlobalPosition.Z:F1}) ";
|
|
_lastDisplayedPosition = GlobalPosition;
|
|
}
|
|
|
|
if (VelocityVal != null && Velocity.DistanceSquaredTo(_lastDisplayedVelocity) > UI_UPDATE_THRESHOLD)
|
|
{
|
|
VelocityVal.Text = $"({Velocity.X:F1}, {Velocity.Y:F1}, {Velocity.Z:F1}) ";
|
|
_lastDisplayedVelocity = Velocity;
|
|
}
|
|
}
|
|
|
|
private void OnMovementStateChanged(MovementStateChangedEvent evt)
|
|
{
|
|
#if DEBUG
|
|
GD.Print($"[Movement] State changed to: {evt.StateName} (Speed: {evt.Speed:F1})");
|
|
#endif
|
|
MovementEvents.RaiseStateChanged(evt.StateName);
|
|
}
|
|
|
|
private void OnPlayerJumped()
|
|
{
|
|
#if DEBUG
|
|
GD.Print($"[Event] Player jumped at speed: {CurrentSpeed:F1}");
|
|
#endif
|
|
}
|
|
|
|
private void OnSpeedChanged(float speed)
|
|
{
|
|
if (Mathf.Abs(speed - _lastSpeed) > 50f)
|
|
{
|
|
#if DEBUG
|
|
GD.Print($"[Event] Speed changed significantly: {_lastSpeed:F1} -> {speed:F1}");
|
|
#endif
|
|
_lastSpeed = speed;
|
|
}
|
|
}
|
|
|
|
public override void _Input(InputEvent @event)
|
|
{
|
|
base._Input(@event);
|
|
|
|
if (@event.IsActionPressed("ui_cancel"))
|
|
{
|
|
if (Godot.Input.MouseMode == Godot.Input.MouseModeEnum.Captured)
|
|
{
|
|
Godot.Input.MouseMode = Godot.Input.MouseModeEnum.Visible;
|
|
}
|
|
else
|
|
{
|
|
Godot.Input.MouseMode = Godot.Input.MouseModeEnum.Captured;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void _ExitTree()
|
|
{
|
|
MovementEvents.PlayerJumped -= OnPlayerJumped;
|
|
MovementEvents.SpeedChanged -= OnSpeedChanged;
|
|
|
|
base._ExitTree();
|
|
}
|
|
}
|