diff --git a/components/health/CHealth.cs b/components/health/CHealth.cs index 0a09a15e..dd3a80e3 100644 --- a/components/health/CHealth.cs +++ b/components/health/CHealth.cs @@ -20,7 +20,6 @@ public partial class CHealth : Node, IHealthable public void ReduceHealth(IDamageable source, DamageRecord damageRecord) { - GD.Print(CurrentHealth); CurrentHealth -= damageRecord.Damage.DamageDealt; HealthChanged?.Invoke(this, CurrentHealth); diff --git a/player_controller/PlayerController.tscn b/player_controller/PlayerController.tscn index 7df61d80..028c4ff9 100644 --- a/player_controller/PlayerController.tscn +++ b/player_controller/PlayerController.tscn @@ -1,18 +1,22 @@ -[gd_scene load_steps=52 format=3 uid="uid://bei4nhkf8lwdo"] +[gd_scene load_steps=55 format=3 uid="uid://bei4nhkf8lwdo"] [ext_resource type="Script" uid="uid://bbbrf5ckydfna" path="res://player_controller/Scripts/PlayerController.cs" id="1_poq2x"] [ext_resource type="PackedScene" uid="uid://cf3rrgr1imvv4" path="res://scenes/path/path.tscn" id="2_6lejt"] [ext_resource type="Resource" uid="uid://bl5crtu1gkrtr" path="res://systems/inputs/base_mode/base_mode.tres" id="3_cresl"] -[ext_resource type="Script" uid="uid://b0u23nkpaimyc" path="res://components/damage/CDamageable.cs" id="4_q7bng"] +[ext_resource type="PackedScene" uid="uid://c4ikbhojckpnc" path="res://components/health/CHealth.tscn" id="3_q7bng"] +[ext_resource type="Resource" uid="uid://bjyd801wvverk" path="res://player_controller/resources/player_health.tres" id="4_m8gvy"] [ext_resource type="Resource" uid="uid://cpdaw41ah5gic" path="res://systems/inputs/base_mode/rotate_y.tres" id="4_rxwoh"] [ext_resource type="Resource" uid="uid://ccrb5xsnphc8" path="res://systems/inputs/base_mode/rotate_floorplane.tres" id="5_4u7i3"] -[ext_resource type="Script" uid="uid://b6y3ugfydvch0" path="res://components/damage/RDamageModifier.cs" id="5_q7bng"] +[ext_resource type="PackedScene" uid="uid://hpsg4fqwrx1u" path="res://components/damage/CDamageable.tscn" id="5_jb43f"] [ext_resource type="Resource" uid="uid://f3vs6l4m623s" path="res://systems/inputs/base_mode/move_left.tres" id="5_q14ux"] +[ext_resource type="Resource" uid="uid://dyru7mxo121w6" path="res://player_controller/resources/player_normal_damage_mod.tres" id="6_cmijs"] [ext_resource type="Resource" uid="uid://t612lts1wi1s" path="res://systems/inputs/base_mode/move_right.tres" id="6_q7bng"] [ext_resource type="Script" uid="uid://cwbvxlfvmocc1" path="res://player_controller/Scripts/StairsSystem.cs" id="7_bmt5a"] [ext_resource type="Resource" uid="uid://brswsknpgwal2" path="res://systems/inputs/base_mode/move_front.tres" id="7_m8gvy"] +[ext_resource type="PackedScene" uid="uid://bctpe34ddamg5" path="res://components/knockback/CKnockback.tscn" id="7_x835q"] [ext_resource type="Resource" uid="uid://s1l0n1iitc6m" path="res://systems/inputs/base_mode/move_back.tres" id="8_jb43f"] [ext_resource type="Resource" uid="uid://j1o5ud0plk4" path="res://systems/inputs/base_mode/aim_release.tres" id="8_lhb11"] +[ext_resource type="Resource" uid="uid://bs8b0oojixm4q" path="res://player_controller/resources/player_knockback.tres" id="8_m8gvy"] [ext_resource type="Resource" uid="uid://c3e0ivgaxrsyb" path="res://systems/inputs/base_mode/aim_down.tres" id="8_obsfv"] [ext_resource type="PackedScene" uid="uid://wq1okogkhc5l" path="res://systems/mantle/mantle_system.tscn" id="8_qu4wy"] [ext_resource type="Resource" uid="uid://bebstkm608wxx" path="res://systems/inputs/base_mode/aim_pressed.tres" id="9_nob5r"] @@ -43,11 +47,6 @@ [ext_resource type="Texture2D" uid="uid://chvt6g0xn5c2m" path="res://systems/dash/light-ring.jpg" id="32_lgpc8"] [ext_resource type="Script" uid="uid://b4dwolbvt8our" path="res://addons/godot_state_charts/history_state.gd" id="41_ruloh"] -[sub_resource type="Resource" id="Resource_jb43f"] -script = ExtResource("5_q7bng") -Modifier = 3.0 -metadata/_custom_type_script = "uid://b6y3ugfydvch0" - [sub_resource type="CapsuleMesh" id="CapsuleMesh_xc2g5"] height = 1.7 @@ -77,10 +76,9 @@ radius = 0.4 [sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_2q0ik"] blend_mode = 1 -[node name="Player" type="CharacterBody3D" node_paths=PackedStringArray("CDamage")] +[node name="Player" type="CharacterBody3D"] collision_mask = 272 script = ExtResource("1_poq2x") -CDamage = NodePath("CDamageable") WalkSpeed = 7.5 AccelerationFloor = 4.0 DecelerationFloor = 3.0 @@ -118,10 +116,14 @@ WallHugGravityLesseningFactor = 15.0 WallHugDownwardMaxSpeed = 4.0 WallHugHorizontalDeceleration = 1.0 -[node name="CDamageable" type="Node" parent="."] -script = ExtResource("4_q7bng") -DamageModifiers = Array[Object]([SubResource("Resource_jb43f")]) -metadata/_custom_type_script = "uid://b0u23nkpaimyc" +[node name="CHealth" parent="." instance=ExtResource("3_q7bng")] +RHealth = ExtResource("4_m8gvy") + +[node name="CDamageable" parent="." instance=ExtResource("5_jb43f")] +DamageModifiers = Array[Object]([ExtResource("6_cmijs")]) + +[node name="CKnockback" parent="." instance=ExtResource("7_x835q")] +RKnockback = ExtResource("8_m8gvy") [node name="WallRunSnapper" type="RayCast3D" parent="."] unique_name_in_owner = true @@ -186,6 +188,7 @@ unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.6, 0) collision_layer = 0 collision_mask = 16 +monitoring = false monitorable = false [node name="CollisionShape3D" type="CollisionShape3D" parent="HeadSystem/WeaponHitbox"] @@ -301,6 +304,13 @@ collision_mask = 256 target_position = Vector3(0, -2, 0) collision_mask = 256 +[node name="InvincibilityTime" type="Timer" parent="."] +one_shot = true + +[node name="AttackCooldown" type="Timer" parent="."] +wait_time = 0.3 +one_shot = true + [node name="DashCooldown" type="Timer" parent="."] wait_time = 0.8 one_shot = true diff --git a/player_controller/Scripts/PlayerController.cs b/player_controller/Scripts/PlayerController.cs index 671fe5bf..4792413d 100644 --- a/player_controller/Scripts/PlayerController.cs +++ b/player_controller/Scripts/PlayerController.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Godot; using GodotStateCharts; using Movementtests.addons.godot_state_charts.csharp; @@ -9,7 +11,9 @@ using RustyOptions; public partial class PlayerController : CharacterBody3D, IDamageable, - IDamageDealer + IDamageDealer, + IHealthable, + IKnockbackable { // Enums public enum AllowedInputs @@ -68,8 +72,7 @@ public partial class PlayerController : CharacterBody3D, [ExportGroup("Damage")] [Export] public RDamage RDamage { get; set; } - [Export] - public CDamageable CDamage { get; set; } + public Node CDamageable { get; set; } [ExportCategory("Movement")] [ExportGroup("Ground")] @@ -279,6 +282,8 @@ public partial class PlayerController : CharacterBody3D, private Timer _simpleDashCooldownTimer; private Timer _airborneDashCooldownTimer; private Timer _powerCooldownTimer; + private Timer _invincibilityTimer; + private Timer _attackCooldown; // State chart private StateChart _playerState; @@ -316,6 +321,8 @@ public partial class PlayerController : CharacterBody3D, private Transition _onGroundSlideJump; private Transition _onAirGlideDoubleJump; + + private List _hitEnemies = new List(); public override void _Ready() { @@ -366,8 +373,29 @@ public partial class PlayerController : CharacterBody3D, var playerShape = StandingCollider.GetShape() as CapsuleShape3D; _playerHeight = playerShape!.Height; _playerRadius = playerShape.Radius; - + + // Combat stuff WeaponHitbox = GetNode("%WeaponHitbox"); + WeaponHitbox.Monitoring = false; + WeaponHitbox.BodyEntered += RegisterHitEnnemy; + + CHealth = GetNode("CHealth"); + if (CHealth is IHealthable healthable && RHealth != null) + { + healthable.RHealth = RHealth; + healthable.CurrentHealth = RHealth.StartingHealth; + } + CKnockback = GetNode("CKnockback"); + if (CKnockback is IKnockbackable knockbackable && RKnockback != null) knockbackable.RKnockback = RKnockback; + CDamageable = GetNode("CDamageable"); + + if (CDamageable is IDamageable damageable) + { + damageable.DamageTaken += ReduceHealth; + damageable.DamageTaken += RegisterKnockback; + } + if (CHealth is IHealthable healthable2) + healthable2.HealthDepleted += Kill; // State management _playerState = StateChart.Of(GetNode("StateChart")); @@ -412,6 +440,8 @@ public partial class PlayerController : CharacterBody3D, _timeScaleAimInAirTimer = GetNode("TimeScaleAimInAir"); _simpleDashCooldownTimer = GetNode("DashCooldown"); _airborneDashCooldownTimer = GetNode("AirborneDashCooldown"); + _invincibilityTimer = GetNode("InvincibilityTime"); + _attackCooldown = GetNode("AttackCooldown"); /////////////////////////// // Initialize components // @@ -419,6 +449,8 @@ public partial class PlayerController : CharacterBody3D, // Camera stuff HeadSystem.Init(); + HeadSystem.HitboxActivated += OnHitboxActivated; + HeadSystem.HitboxDeactivated += OnHitboxDeactivated; // Movement stuff // Getting universal setting from GODOT editor to be in sync @@ -437,6 +469,9 @@ public partial class PlayerController : CharacterBody3D, /////////////////////////// // Signal setup /////////// /////////////////////////// + _invincibilityTimer.Timeout += ResetInvincibility; + _attackCooldown.Timeout += ResetAttackCooldown; + _aiming.StatePhysicsProcessing += HandleAiming; _aiming.StateEntered += OnAimingEntered; _aiming.StateExited += ResetTimeScale; @@ -1267,6 +1302,7 @@ public partial class PlayerController : CharacterBody3D, /////////////////////////// private bool _isSlideInputDown = false; + public void OnInputSlideStarted() { _isSlideInputDown = true; @@ -1701,6 +1737,7 @@ public partial class PlayerController : CharacterBody3D, if (_currentInputBufferFrames > 0) _currentInputBufferFrames -= 1; LookAround(delta); + Velocity += ComputeKnockback(); MoveSlideAndHandleStairs((float) delta); MantleSystem.ProcessMantle(_grounded.Active); @@ -1719,8 +1756,17 @@ public partial class PlayerController : CharacterBody3D, public DamageRecord TakeDamage(DamageRecord damageRecord) { - var finalDamage = CDamage.TakeDamage(damageRecord); + if (CDamageable is not IDamageable damageable || _isInvincible) + return damageRecord with { Damage = new RDamage(0, damageRecord.Damage.DamageType) }; + + var finalDamage = damageable.TakeDamage(damageRecord); DamageTaken?.Invoke(this, finalDamage); + + TriggerHitstop(); + + _isInvincible = true; + _invincibilityTimer.Start(); + return finalDamage; } @@ -1735,18 +1781,99 @@ public partial class PlayerController : CharacterBody3D, } if (!WeaponSystem.InHandState.Active) return; + if (!_canAttack) return; + + _canAttack = false; + _attackCooldown.Start(); PerformHit(); } + public void ResetAttackCooldown() + { + _canAttack = true; + } + public void PerformHit() { HeadSystem.OnHit(); + } + + public void OnHitboxActivated() + { + WeaponHitbox.Monitoring = true; + } + public void OnHitboxDeactivated() + { + WeaponHitbox.Monitoring = false; + TriggerDamage(); + } + + public void RegisterHitEnnemy(Node3D body) + { + if (body is not IDamageable damageable) return; + _hitEnemies.Add(damageable); + } + + public void TriggerDamage() + { + if (_hitEnemies.Count == 0) return; - var bodies = WeaponHitbox.GetOverlappingBodies(); - foreach (var body in bodies) + foreach (var damageable in _hitEnemies) { - if(body is IDamageable spawnable) - spawnable.TakeDamage(new DamageRecord(this, RDamage)); + damageable.TakeDamage(new DamageRecord(this, RDamage)); } + _hitEnemies.Clear(); + TriggerHitstop(); + } + + public void TriggerHitstop() + { + Engine.SetTimeScale(0.01); + var timer = GetTree().CreateTimer(0.1, true, false, true); + timer.Timeout += OnHitstopEnded; + } + + public void OnHitstopEnded() + { + ResetTimeScale(); + } + + public Node CHealth { get; set; } + public Node CKnockback { get; set; } + + public event Action HealthChanged; + public event Action HealthDepleted; + public RHealth RHealth { get; set; } + public float CurrentHealth { get; set; } + public void ReduceHealth(IDamageable source, DamageRecord damageRecord) + { + if (CHealth is not IHealthable healthable) return; + healthable.ReduceHealth(source, damageRecord); + HealthChanged?.Invoke(this, healthable.CurrentHealth); + } + + public RKnockback RKnockback { get; set; } + public void RegisterKnockback(IDamageable source, DamageRecord damageRecord) + { + if (CKnockback is not IKnockbackable knockbackable) return; + knockbackable.RegisterKnockback(source, damageRecord); + } + + public Vector3 ComputeKnockback() + { + if (CKnockback is not IKnockbackable knockbackable) return Vector3.Zero; + return knockbackable.ComputeKnockback(); + } + + public void Kill(IHealthable source) + { + GD.Print("Player died!"); + } + + private bool _isInvincible; + private bool _canAttack = true; + public void ResetInvincibility() + { + _isInvincible = false; } } diff --git a/player_controller/resources/player_health.tres b/player_controller/resources/player_health.tres new file mode 100644 index 00000000..959dd863 --- /dev/null +++ b/player_controller/resources/player_health.tres @@ -0,0 +1,8 @@ +[gd_resource type="Resource" script_class="RHealth" load_steps=2 format=3 uid="uid://bjyd801wvverk"] + +[ext_resource type="Script" uid="uid://baiapod3csndf" path="res://components/health/RHealth.cs" id="1_tv6ah"] + +[resource] +script = ExtResource("1_tv6ah") +StartingHealth = 1.0 +metadata/_custom_type_script = "uid://baiapod3csndf" diff --git a/player_controller/resources/player_knockback.tres b/player_controller/resources/player_knockback.tres new file mode 100644 index 00000000..22c98bc7 --- /dev/null +++ b/player_controller/resources/player_knockback.tres @@ -0,0 +1,8 @@ +[gd_resource type="Resource" script_class="RKnockback" load_steps=2 format=3 uid="uid://bs8b0oojixm4q"] + +[ext_resource type="Script" uid="uid://b44cse62qru7j" path="res://components/knockback/RKnockback.cs" id="1_dthjm"] + +[resource] +script = ExtResource("1_dthjm") +Modifier = 30.0 +metadata/_custom_type_script = "uid://b44cse62qru7j" diff --git a/player_controller/resources/player_normal_damage_mod.tres b/player_controller/resources/player_normal_damage_mod.tres new file mode 100644 index 00000000..c67b33a5 --- /dev/null +++ b/player_controller/resources/player_normal_damage_mod.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" script_class="RDamageModifier" load_steps=2 format=3 uid="uid://dyru7mxo121w6"] + +[ext_resource type="Script" uid="uid://b6y3ugfydvch0" path="res://components/damage/RDamageModifier.cs" id="1_7i47t"] + +[resource] +script = ExtResource("1_7i47t") +metadata/_custom_type_script = "uid://b6y3ugfydvch0" diff --git a/scenes/enemies/Enemy.cs b/scenes/enemies/Enemy.cs index 6a315145..b72e6137 100644 --- a/scenes/enemies/Enemy.cs +++ b/scenes/enemies/Enemy.cs @@ -73,7 +73,6 @@ public partial class Enemy : CharacterBody3D, public void SetupSignals() { - _damageBox.BodyEntered += OnDamageBoxTriggered; if (CDamage is IDamageable damageable) { damageable.DamageTaken += ReduceHealth; @@ -84,6 +83,9 @@ public partial class Enemy : CharacterBody3D, public override void _PhysicsProcess(double delta) { + // Only trigger gameplay related effects 4 times per second + if(Engine.GetPhysicsFrames() % 15 == 0) ProcessGameplay(delta); + var targetPlanar = new Vector3(Target.GlobalPosition.X, GlobalPosition.Y, Target.GlobalPosition.Z); LookAt(targetPlanar); @@ -98,6 +100,16 @@ public partial class Enemy : CharacterBody3D, Velocity += ComputeKnockback(); MoveAndSlide(); } + + public void ProcessGameplay(double delta) + { + var bodies = _damageBox.GetOverlappingBodies(); + foreach (var body in bodies) + { + if(body is IDamageable spawnable) + spawnable.TakeDamage(new DamageRecord(this, RDamage)); + } + } public Vector3 ComputeVelocity(MovementInputs inputs) { @@ -105,14 +117,6 @@ public partial class Enemy : CharacterBody3D, return movement!.ComputeVelocity(inputs); } - public void OnDamageBoxTriggered(Node3D body) - { - if (body is not IDamageable damageable) return; - - var damageRecord = new DamageRecord(this, RDamage); - damageable.TakeDamage(damageRecord); - } - public DamageRecord TakeDamage(DamageRecord damageRecord) { if (CDamage is not IDamageable damageable) @@ -120,8 +124,6 @@ public partial class Enemy : CharacterBody3D, var finalDamage = damageable.TakeDamage(damageRecord); DamageTaken?.Invoke(this, finalDamage); - - GD.Print($"Received damage: {finalDamage.Damage.DamageDealt}"); return finalDamage; } @@ -129,6 +131,7 @@ public partial class Enemy : CharacterBody3D, { if (CHealth is not IHealthable healthable) return; healthable.ReduceHealth(source, damageRecord); + HealthChanged?.Invoke(this, healthable.CurrentHealth); } public void Kill(IHealthable source) diff --git a/scenes/enemies/flying_enemy/flying_enemy_knockback.tres b/scenes/enemies/flying_enemy/flying_enemy_knockback.tres index 8d294eb8..e2e6ba15 100644 --- a/scenes/enemies/flying_enemy/flying_enemy_knockback.tres +++ b/scenes/enemies/flying_enemy/flying_enemy_knockback.tres @@ -4,5 +4,5 @@ [resource] script = ExtResource("1_yq03x") -Modifier = 10.0 +Modifier = 20.0 metadata/_custom_type_script = "uid://b44cse62qru7j" diff --git a/scenes/enemies/grounded_enemy/grounded_enemy_knockback.tres b/scenes/enemies/grounded_enemy/grounded_enemy_knockback.tres index 0bdfb577..5d88902c 100644 --- a/scenes/enemies/grounded_enemy/grounded_enemy_knockback.tres +++ b/scenes/enemies/grounded_enemy/grounded_enemy_knockback.tres @@ -4,5 +4,5 @@ [resource] script = ExtResource("1_vdia8") -Modifier = 10.0 +Modifier = 30.0 metadata/_custom_type_script = "uid://b44cse62qru7j" diff --git a/systems/head/HeadSystem.cs b/systems/head/HeadSystem.cs index dea61e1d..1f286901 100644 --- a/systems/head/HeadSystem.cs +++ b/systems/head/HeadSystem.cs @@ -6,6 +6,11 @@ namespace Movementtests.systems; public partial class HeadSystem : Node3D { + [Signal] + public delegate void HitboxActivatedEventHandler(); + [Signal] + public delegate void HitboxDeactivatedEventHandler(); + public record CameraParameters( double Delta, Vector2 LookDir, @@ -111,12 +116,12 @@ public partial class HeadSystem : Node3D public void OnHitboxActivated() { - GD.Print("Hitbox activated"); + EmitSignalHitboxActivated(); } public void OnHitboxDeactivated() { - GD.Print("Hitbox deactivated"); + EmitSignalHitboxDeactivated(); } public void LookAround(CameraParameters inputs)