fighter/player_controller.cs
// PlayerController.cs
using System;
using Sandbox;
public sealed class PlayerController : Component
{
[Property, Group( "Camera & Pivot" )] public GameObject EyePivot { get; set; }
[Property, Group( "Camera & Pivot" )] public CameraComponent Camera { get; set; }
[Property, Group( "Camera & Pivot" )] public float LookSensitivity { get; set; } = 0.12f;
[Property, Group( "Camera & Pivot" )] public string CameraAttachmentName { get; set; } = "camera_view";
[Property, Group( "Renderers" )] public SkinnedModelRenderer ArmsRenderer1PP { get; set; }
[Property, Group( "Renderers" )] public SkinnedModelRenderer BodyRenderer3PP { get; set; }
[Property, Group( "Ramassage (Grab F)" )] public float GrabDistance { get; set; } = 130f;
[Property, Group( "Ramassage (Grab F)" )] public float GrabConeAngle { get; set; } = 55f;
[Property, Group( "Jet d'Arme" )] public float MaxThrowChargeTime { get; set; } = 0.45f;
[Property, Group( "Jet d'Arme" )] public float MinThrowSpeed { get; set; } = 650f;
[Property, Group( "Jet d'Arme" )] public float MaxThrowSpeed { get; set; } = 1500f;
[Property, Group( "Input Actions" )] public string GrabAction { get; set; } = "Grab";
[Property, Group( "Input Actions" )] public string DropAction { get; set; } = "Drop";
[Property, Group( "Input Actions" )] public string ThrowAction { get; set; } = "Throw";
[Property, Group( "Input Actions" )] public string ThrowAltAction { get; set; } = "ThrowAlt";
private FighterMovement _movement;
private FighterCombat _combat;
private Angles _eyeAngles;
private Vector3? _restPosInPivot;
private Rotation? _restRotInPivot;
private TimeSince _timeSinceStart = 0f;
private bool _isHoldingKick = false;
protected override void OnAwake()
{
_movement = Components.Get<FighterMovement>();
_combat = Components.Get<FighterCombat>();
Camera ??= Components.GetInChildren<CameraComponent>();
if ( _combat != null && ArmsRenderer1PP != null )
{
_combat.PreferredRenderer = ArmsRenderer1PP;
}
}
protected override void OnStart()
{
if ( EyePivot != null )
_eyeAngles = EyePivot.WorldRotation.Angles();
_timeSinceStart = 0f;
}
protected override void OnUpdate()
{
if ( UIStateManager.IsGameplayLocked )
{
if ( _movement != null )
{
_movement.WishVelocity = Vector3.Zero;
_movement.WantsSprint = false;
_movement.WantsCrouch = false;
}
_isHoldingKick = false;
_combat?.CancelAnticipation();
return;
}
// 1. Rotation de la vue
var look = Input.AnalogLook * LookSensitivity;
_eyeAngles.pitch = Math.Clamp( _eyeAngles.pitch + look.pitch, -85f, 85f );
_eyeAngles.yaw += look.yaw;
WorldRotation = Rotation.FromYaw( _eyeAngles.yaw );
if ( EyePivot != null )
{
EyePivot.LocalRotation = Rotation.FromPitch( _eyeAngles.pitch );
}
// 2. Déplacement physique
var moveInput = Input.AnalogMove;
var forward = WorldRotation.Forward * moveInput.x;
var right = WorldRotation.Right * -moveInput.y;
_movement.WishVelocity = (forward + right).Normal;
_movement.WantsSprint = Input.Down( "run" );
_movement.WantsCrouch = Input.Down( "duck" );
if ( Input.Pressed( "jump" ) )
_movement.Jump();
var eyePos = EyePivot != null ? EyePivot.WorldPosition : WorldPosition + Vector3.Up * 64f;
var aimDir = EyePivot != null ? EyePivot.WorldRotation.Forward : WorldRotation.Forward;
// 3. COMBAT : Attaque Principale
if ( Input.Pressed( "attack1" ) && !_isHoldingKick && !_combat.IsThrowAnticipating )
{
_combat.StartPrimaryAttack( eyePos, aimDir );
}
else if ( Input.Released( "attack1" ) && !_isHoldingKick && !_combat.IsThrowAnticipating )
{
_combat.ReleaseAction( eyePos, aimDir );
}
// 4. COMBAT : Spartan Kick sur 'use'
if ( Input.Pressed( "use" ) && !_combat.IsAnticipating )
{
_isHoldingKick = true;
_combat.StartKick( eyePos, aimDir );
}
else if ( Input.Released( "use" ) && _isHoldingKick )
{
_isHoldingKick = false;
_combat.ReleaseAction( eyePos, aimDir );
}
// 5. COMBAT : Jet d'arme
UpdateThrowInput( aimDir );
// 6. Parade & Feinte
bool wantsParry = Input.Down( "attack2" );
if ( wantsParry && _combat.IsAnticipating )
{
_isHoldingKick = false;
_combat.CancelAnticipation();
}
_combat.SetParry( wantsParry );
// 7. Ramassage d'arme sur 'F'
if ( Input.Pressed( GrabAction ) )
TryGrabWeapon( eyePos, aimDir );
// 8. Lâcher l'arme (Drop)
if ( Input.Pressed( DropAction ) )
{
_isHoldingKick = false;
if ( _combat.MainWeapon != null )
_combat.DropMainWeapon();
else if ( _combat.ShieldItem != null )
_combat.DropShield();
}
var inCombat = wantsParry || _combat.CurrentWeapon != null || _combat.IsAnticipating || _combat.IsExecutingThrow;
UpdateRenderers( inCombat, aimDir );
}
private void TryGrabWeapon( Vector3 eyePos, Vector3 aimDir )
{
var trace = Scene.Trace.Sphere( 24f, eyePos, eyePos + aimDir * GrabDistance )
.WithTag( "weapon_drop" )
.IgnoreGameObjectHierarchy( GameObject )
.Run();
var targetedWeapon = trace.GameObject?.Components.Get<Weapon>( FindMode.EverythingInSelfAndAncestors );
if ( targetedWeapon == null )
{
var cosHalf = MathF.Cos( MathX.DegreeToRadian( GrabConeAngle * 0.5f ) );
targetedWeapon = SpatialQuery.GetNearest<Weapon>( eyePos, GrabDistance, w =>
{
if ( w.IsEquipped || !w.GameObject.IsValid ) return false;
var dirToW = (w.WorldPosition - eyePos).Normal;
return Vector3.Dot( aimDir, dirToW ) >= cosHalf;
} );
}
if ( targetedWeapon != null && !targetedWeapon.IsEquipped )
{
_combat.EquipWeapon( targetedWeapon );
Log.Info( $"✋ [GRAB] Arme équipée : {targetedWeapon.WeaponName}" );
}
}
private void UpdateThrowInput( Vector3 aimDir )
{
bool throwPressed = Input.Pressed( ThrowAction ) || Input.Pressed( ThrowAltAction );
bool throwReleased = Input.Released( ThrowAction ) || Input.Released( ThrowAltAction );
if ( throwPressed && _combat.CurrentWeapon != null )
{
_combat.StartThrow();
}
else if ( throwReleased && _combat.IsThrowAnticipating )
{
var chargeRatio = Math.Clamp( _combat.CurrentChargeTime / MaxThrowChargeTime, 0f, 1f );
var speed = MathX.Lerp( MinThrowSpeed, MaxThrowSpeed, chargeRatio );
_combat.ReleaseThrow( aimDir, speed );
}
}
protected override void OnPreRender()
{
ApplyCameraBoneMotion();
}
private void UpdateRenderers( bool inCombat, Vector3 aimDir )
{
if ( BodyRenderer3PP != null )
{
BodyRenderer3PP.Set( "in_combat", inCombat );
BodyRenderer3PP.Set( "in_combat_weight", inCombat ? 1.0f : 0.0f );
BodyRenderer3PP.Set( "aim_pitch", _eyeAngles.pitch );
BodyRenderer3PP.Set( "aim_target", WorldPosition + aimDir * 120f + Vector3.Up * 60f );
}
if ( ArmsRenderer1PP != null )
{
ArmsRenderer1PP.Set( "in_combat", inCombat );
ArmsRenderer1PP.Set( "is_parrying", Input.Down( "attack2" ) );
}
}
private void ApplyCameraBoneMotion()
{
if ( Camera == null || ArmsRenderer1PP == null || EyePivot == null ) return;
var attach = ArmsRenderer1PP.GetAttachment( CameraAttachmentName );
if ( !attach.HasValue )
{
Camera.LocalPosition = Vector3.Zero;
Camera.LocalRotation = Rotation.Identity;
return;
}
var currentPosInPivot = EyePivot.WorldTransform.PointToLocal( attach.Value.Position );
var currentRotInPivot = EyePivot.WorldRotation.Inverse * attach.Value.Rotation;
if ( !_restPosInPivot.HasValue && _timeSinceStart > 0.15f )
{
_restPosInPivot = currentPosInPivot;
_restRotInPivot = currentRotInPivot;
}
if ( !_restPosInPivot.HasValue ) return;
var deltaPos = currentPosInPivot - _restPosInPivot.Value;
var deltaRot = _restRotInPivot.Value.Inverse * currentRotInPivot;
Camera.LocalPosition = deltaPos;
Camera.LocalRotation = deltaRot;
}
}