added a parry button and animation that lets player chose their enemy hit behaviour
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Test (push) Successful in 5m57s
Create tag and build when new code gets to main / Export (push) Successful in 7m43s

This commit is contained in:
2026-01-30 13:19:28 +01:00
parent cc973b9f0d
commit 3525f0e3eb
9 changed files with 488 additions and 52 deletions

View File

@@ -336,6 +336,8 @@ public partial class PlayerController : CharacterBody3D,
private StateChartState _attackStandard;
private StateChartState _attackDash;
private StateChartState _parryStandard;
private StateChartState _parryDash;
private Transition _onJumpFromWall;
private Transition _onJumpFromWallFalling;
@@ -481,6 +483,8 @@ public partial class PlayerController : CharacterBody3D,
// Attack states
_attackStandard = StateChartState.Of(GetNode("StateChart/Root/Attack/StandardAttack"));
_attackDash = StateChartState.Of(GetNode("StateChart/Root/Attack/DashAttack"));
_parryStandard = StateChartState.Of(GetNode("StateChart/Root/Attack/StandardParry"));
_parryDash = StateChartState.Of(GetNode("StateChart/Root/Attack/DashParry"));
// State timers
_powerCooldownTimer = GetNode<Timer>("PowerCooldown");
@@ -597,6 +601,8 @@ public partial class PlayerController : CharacterBody3D,
// Attack states
_attackStandard.StateEntered += OnStandardAttackStarted;
_attackDash.StateEntered += OnDashAttackStarted;
_parryStandard.StateEntered += OnStandardParryStarted;
_parryDash.StateEntered += OnDashParryStarted;
// Testing out kill
// GetTree().CreateTimer(2).Timeout += () => Kill(this);
@@ -1709,16 +1715,8 @@ public partial class PlayerController : CharacterBody3D,
///////////////////////////
public void OnInputParryPressed()
{
if (WeaponSystem.FlyingState.Active)
{
DashToFlyingWeapon();
return;
}
if (WeaponSystem.PlantedState.Active)
{
DashToPlantedWeapon();
}
var attackToDo = _isEnemyInDashAttackRange ? "dash_parry" : "standard_parry";
_playerState.SendEvent(attackToDo);
}
///////////////////////////
@@ -1965,7 +1963,7 @@ public partial class PlayerController : CharacterBody3D,
if (DashSystem.CanDashThroughTarget && DashSystem.CollidedObject is ITargetable dashTarget)
{
enemyTargetState = PlayerUi.TargetState.TargetDashThrough;
enemyTargetState = PlayerUi.TargetState.TargetWouldKill;
_targetLocation = dashTarget.GetTargetGlobalPosition();
positionOnScreen = _camera.UnprojectPosition(_targetLocation);
PlayerUi.SetEnemyTargetProperties(new PlayerUi.TargetProperties(enemyTargetState, positionOnScreen));
@@ -1991,19 +1989,14 @@ public partial class PlayerController : CharacterBody3D,
// var targetDistance = _targetLocation.DistanceTo(GlobalPosition);
positionOnScreen = _camera.UnprojectPosition(_targetLocation);
_isEnemyInDashAttackRange = true; //targetDistance < TargetInRangeDistance; // Removing the "almost dash" UI
if (_isEnemyInDashAttackRange)
var wouldKill = false;
if (_targetObject is IHealthable h and IDamageable d)
{
enemyTargetState = PlayerUi.TargetState.TargetDashThrough;
if (_targetObject is IDamageable damageable and IHealthable healthable)
{
var wouldBeDamage = damageable.ComputeDamage(new DamageRecord(GlobalPosition, RDamage));
if (wouldBeDamage.Damage.DamageDealt < healthable.CurrentHealth)
enemyTargetState = PlayerUi.TargetState.TargetInRange;
}
var wouldBeDamage = d.ComputeDamage(new DamageRecord(GlobalPosition, RDamage));
if (h.CurrentHealth < wouldBeDamage.Damage.DamageDealt) wouldKill = true;
}
else enemyTargetState = PlayerUi.TargetState.TargetTooFar;
_isEnemyInDashAttackRange = true;
enemyTargetState = wouldKill ? PlayerUi.TargetState.TargetWouldKill : PlayerUi.TargetState.TargetWouldNotKill;
PlayerUi.SetEnemyTargetProperties(new PlayerUi.TargetProperties(enemyTargetState, positionOnScreen));
}
@@ -2041,6 +2034,14 @@ public partial class PlayerController : CharacterBody3D,
_audioStream!.SwitchToClipByName("attacks");
}
public void OnStandardParryStarted()
{
_attackCooldown.Start();
HeadSystem.OnParry();
_audioStream!.SwitchToClipByName("parry");
}
// TODO: fix repeated code and improve parry knockback
private PhysicsDirectSpaceState3D _spaceState;
public void OnDashAttackStarted()
{
@@ -2063,6 +2064,27 @@ public partial class PlayerController : CharacterBody3D,
var dashTween = CreatePositionTween(plannedDashLocation, AimedDashTime);
dashTween.Finished += OnDashAttackEnded;
}
public void OnDashParryStarted()
{
_audioStream!.SwitchToClipByName("parry");
_isInvincible = true;
var plannedDashLocation = _targetLocation + Vector3.Down*HeadSystem.Position.Y;
var query = PhysicsRayQueryParameters3D.Create(HeadSystem.GlobalPosition, plannedDashLocation, DashSystem.DashCast3D.CollisionMask);
var result = _spaceState.IntersectRay(query);
if (result.Count > 0)
{
plannedDashLocation = (Vector3) result["position"];
}
var travel = plannedDashLocation - GlobalPosition;
_preDashVelocity = Velocity;
_dashDirection = travel.Normalized();
var dashTween = CreatePositionTween(plannedDashLocation, AimedDashTime);
dashTween.Finished += OnDashParryEnded;
}
public void OnDashAttackEnded()
{
@@ -2071,23 +2093,30 @@ public partial class PlayerController : CharacterBody3D,
_hitEnemies.Add(damageable);
TriggerDamage();
}
if (_targetObject is IStunnable stunnable)
{
stunnable.Stun();
}
GlobalPosition = ComputePositionAfterTargetedDash(_targetLocation, _targetHitLocation);
var postDashVelocity = _preDashVelocity.Length() > PostDashSpeed ? _preDashVelocity.Length() : PostDashSpeed;
Velocity = _dashDirection * postDashVelocity;
_isInvincible = false;
_playerState.SendEvent("attack_finished");
}
public void OnDashParryEnded()
{
if (_targetObject is IDamageable damageable)
{
_hitEnemies.Add(damageable);
TriggerDamage();
}
if (_targetObject is IStunnable stunnable)
{
stunnable.Stun();
}
var shouldKnockback = _targetObject is IHealthable { CurrentHealth: > 0 };
if (shouldKnockback)
{
Velocity = -_dashDirection*RKnockback.Modifier;
}
else
{
GlobalPosition = ComputePositionAfterTargetedDash(_targetLocation, _targetHitLocation);
var postDashVelocity = _preDashVelocity.Length() > PostDashSpeed ? _preDashVelocity.Length() : PostDashSpeed;
Velocity = _dashDirection * postDashVelocity;
}
Velocity = -_dashDirection*RKnockback.Modifier;
_isInvincible = false;
_playerState.SendEvent("attack_finished");
}
@@ -2104,6 +2133,17 @@ public partial class PlayerController : CharacterBody3D,
ThrowWeapon();
return;
}
if (WeaponSystem.FlyingState.Active)
{
DashToFlyingWeapon();
return;
}
if (WeaponSystem.PlantedState.Active)
{
DashToPlantedWeapon();
return;
}
var attackToDo = _isEnemyInDashAttackRange ? "dash_attack" : "standard_attack";
_playerState.SendEvent(attackToDo);