diff --git a/BoundingBoxes/Hitbox.cs b/BoundingBoxes/Hitbox.cs index 9ac8ea4..8556dc3 100644 --- a/BoundingBoxes/Hitbox.cs +++ b/BoundingBoxes/Hitbox.cs @@ -1,37 +1,54 @@ using System.Collections.Generic; using Godot; using SupaLidlGame.Characters; +using SupaLidlGame.Utils; namespace SupaLidlGame.BoundingBoxes { - public partial class Hitbox : Area2D + public partial class Hitbox : Area2D, IFaction { private HashSet _ignoreList = new HashSet(); [Export] - public float Damage { get; set; } = 0; + public float Damage { get; set; } + /// + /// Getter/setter for the CollisionShape2D's Disabled property. + /// [Export] - public bool IsEnabled { get; set; } + public bool IsDisabled + { + get => _collisionShape.Disabled; + set => _collisionShape.Disabled = value; + } [Export] public float Knockback { get; set; } + [Export] + public ushort Faction { get; set; } + public Character Inflictor { get; set; } + private CollisionShape2D _collisionShape; + + public override void _Ready() + { + _collisionShape = GetNode("CollisionShape2D"); + } + public void _on_area_entered(Area2D area) { - if (!IsEnabled) - { - return; - } - if (area is Hurtbox hurtbox) { - if (!_ignoreList.Contains(hurtbox)) + // we don't want to hurt teammates + if (Faction != hurtbox.Faction) { - _ignoreList.Add(hurtbox); - hurtbox.InflictDamage(Damage, Inflictor, Knockback); + if (!_ignoreList.Contains(hurtbox)) + { + _ignoreList.Add(hurtbox); + hurtbox.InflictDamage(Damage, Inflictor, Knockback); + } } } } diff --git a/BoundingBoxes/Hurtbox.cs b/BoundingBoxes/Hurtbox.cs index 22fbcea..7917ba9 100644 --- a/BoundingBoxes/Hurtbox.cs +++ b/BoundingBoxes/Hurtbox.cs @@ -1,12 +1,29 @@ using Godot; using SupaLidlGame.Characters; +using SupaLidlGame.Utils; namespace SupaLidlGame.BoundingBoxes { - public partial class Hurtbox : Area2D + public partial class Hurtbox : Area2D, IFaction { [Signal] - public delegate void ReceivedDamageEventHandler(float damage); + public delegate void ReceivedDamageEventHandler( + float damage, + Character inflictor, + float knockback, + Vector2 knockbackOrigin = default, + Vector2 knockbackVector = default); + + [Export] + public ushort Faction { get; set; } + + public override void _Ready() + { + if (Faction == default && GetParent() is IFaction factionEntity) + { + Faction = factionEntity.Faction; + } + } public void InflictDamage( float damage, @@ -16,7 +33,7 @@ namespace SupaLidlGame.BoundingBoxes Vector2 knockbackVector = default) { EmitSignal( - "ReceivedDamage", + SignalName.ReceivedDamage, damage, inflictor, knockback, diff --git a/Characters/Character.cs b/Characters/Character.cs index 97d7b0a..936dffa 100644 --- a/Characters/Character.cs +++ b/Characters/Character.cs @@ -1,8 +1,10 @@ using Godot; +using SupaLidlGame.Items; +using SupaLidlGame.Utils; namespace SupaLidlGame.Characters { - public partial class Character : CharacterBody2D + public partial class Character : CharacterBody2D, IFaction { [Export] public float Speed { get; protected set; } = 32.0f; @@ -30,17 +32,50 @@ namespace SupaLidlGame.Characters public Vector2 Target { get; set; } = Vector2.Zero; - public float Health { get; set; } + public float Health + { + get => _health; + set + { + if (!IsAlive && value < 0) + { + return; + } + + _health = value; + GD.Print(_health); + if (_health <= 0) + { + Die(); + } + } + } + + protected float _health = 100f; + + public bool IsAlive => Health > 0; + + [Export] + public AnimatedSprite2D Sprite { get; set; } + + [Export] + public Inventory Inventory { get; set; } [Export] public State.Machine StateMachine { get; set; } + [Export] + public ushort Faction { get; set; } + public override void _Process(double delta) { if (StateMachine != null) { StateMachine.Process(delta); } + + Sprite.FlipH = Target.x < 0; + DrawTarget(); } public override void _Input(InputEvent @event) @@ -59,6 +94,20 @@ namespace SupaLidlGame.Characters } } + /// + /// Modify the Character's velocity + /// + public virtual void ModifyVelocity() + { + + } + + public virtual void Die() + { + GD.Print("lol died"); + QueueFree(); + } + public void ApplyImpulse(Vector2 impulse, bool resetVelocity = false) { // delta p = F delta t @@ -67,9 +116,45 @@ namespace SupaLidlGame.Characters Velocity += impulse / Mass; } - public void _on_hurtbox_received_damage(float damage) + protected void DrawTarget() + { + Vector2 target = Target; + float angle = Mathf.Atan2(target.y, Mathf.Abs(target.x)); + Vector2 scale = Inventory.Scale; + if (target.x < 0) + { + scale.y = -1; + angle = Mathf.Pi - angle; + } + else + { + scale.y = 1; + } + Inventory.Scale = scale; + Inventory.Rotation = angle; + } + + public void _on_hurtbox_received_damage(float damage, + Character inflictor, + float knockback, + Vector2 knockbackOrigin = default, + Vector2 knockbackVector = default) { Health -= damage; + /* + Vector2 knockbackDir = knockbackVector; + if (knockbackDir == default) + { + if (knockbackOrigin == default) + { + knockbackOrigin = inflictor.GlobalPosition; + } + + knockbackDir = knockbackOrigin.DirectionTo(GlobalPosition); + } + + ApplyImpulse(knockback.) + */ } } } diff --git a/Characters/Enemy.cs b/Characters/Enemy.cs index 093aa24..b33b6e1 100644 --- a/Characters/Enemy.cs +++ b/Characters/Enemy.cs @@ -5,5 +5,15 @@ namespace SupaLidlGame.Characters { public partial class Enemy : NPC { + public override void _Ready() + { + Inventory.SelectedItem = Inventory.GetNode("Sword"); + base._Ready(); + } + + public override void Die() + { + base.Die(); + } } } diff --git a/Characters/ExampleEnemy.tscn b/Characters/ExampleEnemy.tscn index 6444d31..785d6a4 100644 --- a/Characters/ExampleEnemy.tscn +++ b/Characters/ExampleEnemy.tscn @@ -1,25 +1,74 @@ -[gd_scene load_steps=9 format=3 uid="uid://dymwd5ihpwyqm"] +[gd_scene load_steps=19 format=3 uid="uid://dymwd5ihpwyqm"] -[ext_resource type="Script" path="res://Characters/NPC.cs" id="1_4x3dm"] -[ext_resource type="Texture2D" uid="uid://bw052v8ikfget" path="res://icon.svg" id="2_ujqd7"] +[ext_resource type="Script" path="res://Characters/Enemy.cs" id="1_2yopk"] +[ext_resource type="Texture2D" uid="uid://dxymfduyrbuvx" path="res://Sprites/Characters/forsen.png" id="2_jfku3"] [ext_resource type="Script" path="res://Characters/States/Machine.cs" id="3_k4ypw"] [ext_resource type="Script" path="res://Characters/States/NPCIdleState.cs" id="4_8r2qn"] [ext_resource type="Script" path="res://Characters/States/NPCMoveState.cs" id="5_utogm"] [ext_resource type="PackedScene" uid="uid://cjgxyhgcyvsv7" path="res://BoundingBoxes/Hurtbox.tscn" id="6_jo0cg"] +[ext_resource type="Script" path="res://Items/Inventory.cs" id="7_43gq8"] +[ext_resource type="PackedScene" uid="uid://cajlwb67xenfy" path="res://Items/Weapons/Sword.tscn" id="8_s3c8r"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_6d2tf"] +atlas = ExtResource("2_jfku3") +region = Rect2(0, 0, 24, 24) + +[sub_resource type="AtlasTexture" id="AtlasTexture_bdyma"] +atlas = ExtResource("2_jfku3") +region = Rect2(24, 0, 24, 24) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0dwbr"] +atlas = ExtResource("2_jfku3") +region = Rect2(48, 0, 24, 24) + +[sub_resource type="AtlasTexture" id="AtlasTexture_r7fn6"] +atlas = ExtResource("2_jfku3") +region = Rect2(72, 0, 24, 24) + +[sub_resource type="AtlasTexture" id="AtlasTexture_py8k0"] +atlas = ExtResource("2_jfku3") +region = Rect2(96, 0, 24, 24) + +[sub_resource type="AtlasTexture" id="AtlasTexture_g3nb2"] +atlas = ExtResource("2_jfku3") +region = Rect2(120, 0, 24, 24) + +[sub_resource type="AtlasTexture" id="AtlasTexture_jauql"] +atlas = ExtResource("2_jfku3") +region = Rect2(144, 0, 24, 24) + +[sub_resource type="SpriteFrames" id="SpriteFrames_4tm2b"] +animations = [{ +"frames": [SubResource("AtlasTexture_6d2tf"), SubResource("AtlasTexture_bdyma")], +"loop": true, +"name": &"idle", +"speed": 5.0 +}, { +"frames": [SubResource("AtlasTexture_0dwbr"), SubResource("AtlasTexture_r7fn6"), SubResource("AtlasTexture_py8k0"), SubResource("AtlasTexture_g3nb2"), SubResource("AtlasTexture_jauql")], +"loop": true, +"name": &"move", +"speed": 12.0 +}] [sub_resource type="RectangleShape2D" id="RectangleShape2D_uict5"] -size = Vector2(32, 16) +size = Vector2(16, 8) [sub_resource type="RectangleShape2D" id="RectangleShape2D_8lxmf"] -size = Vector2(32, 32) +size = Vector2(16, 24) -[node name="ExampleEnemy" type="CharacterBody2D" node_paths=PackedStringArray("StateMachine")] -script = ExtResource("1_4x3dm") +[node name="ExampleEnemy" type="CharacterBody2D" node_paths=PackedStringArray("Sprite", "Inventory", "StateMachine")] +texture_filter = 3 +y_sort_enabled = true +script = ExtResource("1_2yopk") +Sprite = NodePath("Sprite") +Inventory = NodePath("Inventory") StateMachine = NodePath("StateMachine") +Faction = 2 -[node name="Icon" type="Sprite2D" parent="."] -scale = Vector2(0.25, 0.25) -texture = ExtResource("2_ujqd7") +[node name="Sprite" type="AnimatedSprite2D" parent="."] +frames = SubResource("SpriteFrames_4tm2b") +animation = &"move" +playing = true [node name="CollisionShape2D" type="CollisionShape2D" parent="."] position = Vector2(0, 8) @@ -39,10 +88,17 @@ script = ExtResource("5_utogm") IdleState = NodePath("../Idle") [node name="Hurtbox" parent="." instance=ExtResource("6_jo0cg")] +Faction = 2 [node name="CollisionShape2D" parent="Hurtbox" index="0"] shape = SubResource("RectangleShape2D_8lxmf") +[node name="Inventory" type="Node2D" parent="."] +y_sort_enabled = true +script = ExtResource("7_43gq8") + +[node name="Sword" parent="Inventory" instance=ExtResource("8_s3c8r")] + [connection signal="ReceivedDamage" from="Hurtbox" to="." method="_on_hurtbox_received_damage"] [editable path="Hurtbox"] diff --git a/Characters/NPC.cs b/Characters/NPC.cs index 970957d..62b7213 100644 --- a/Characters/NPC.cs +++ b/Characters/NPC.cs @@ -1,4 +1,5 @@ using Godot; +using SupaLidlGame.Items; using System; namespace SupaLidlGame.Characters @@ -8,7 +9,7 @@ namespace SupaLidlGame.Characters /// /// Time in seconds it takes for the NPC to think FeelsDankCube /// - public const float ThinkTime = 0.25f; + public const float ThinkTime = 0.125f; public float[] Weights => _weights; @@ -71,7 +72,7 @@ namespace SupaLidlGame.Characters Character bestChar = null; foreach (Node node in GetParent().GetChildren()) { - if (node != this && node is Player character) + if (node is Character character && character.Faction != Faction) { float dist = Position.DistanceTo(character.Position); if (dist < bestDist) @@ -95,12 +96,23 @@ namespace SupaLidlGame.Characters Direction = _weightDirs[_bestWeightIdx]; } - private void Think() + protected virtual void Think() { Vector2 pos = FindBestTarget().GlobalPosition; - Vector2 dir = Target = GlobalPosition.DirectionTo(pos); + Target = pos - GlobalPosition;//GlobalPosition.DirectionTo(pos); + Vector2 dir = Target.Normalized(); float dist = GlobalPosition.DistanceSquaredTo(pos); + if (Target.LengthSquared() < 1024) + { + GD.Print("lol"); + if (Inventory.SelectedItem is Weapon weapon) + { + GD.Print("use"); + weapon.Use(); + } + } + for (int i = 0; i < 16; i++) { float directDot = _weightDirs[i].Dot(dir); @@ -122,17 +134,19 @@ namespace SupaLidlGame.Characters // "When will I use math in the real world" Clueful - if (dist > 1024) + if (dist > 4096) { _weights[i] = directDot; } else if (dist > 64) { - float directDotWeighting = (dist - 64) / 960; + //float directDotWeighting = dist / 4096; + //float directDotWeighting = Mathf.Log(dist) / _log1024; + float directDotWeighting = Mathf.Sqrt(dist / 4096); float horizDotWeighting = 1 - directDotWeighting; _weights[i] = (directDot * directDotWeighting) + - (horizDot * horizDotWeighting); + (horizDot * horizDotWeighting * 0.5f); } else { @@ -169,7 +183,7 @@ namespace SupaLidlGame.Characters } else { - float dot = _weightDirs[i].Dot(_weightDirs[j]); + float dot = _weightDirs[i].Dot(_weightDirs[j]) / 2; _weights[j] -= (dot + 1) / 2; } } diff --git a/Characters/Player.cs b/Characters/Player.cs index 457ee19..9b94b8c 100644 --- a/Characters/Player.cs +++ b/Characters/Player.cs @@ -1,13 +1,63 @@ using Godot; -using System; +using SupaLidlGame.Items; namespace SupaLidlGame.Characters { public partial class Player : Character { + private AnimatedSprite2D _sprite; + private string _spriteAnim; + + public string Animation + { + get => _sprite.Animation; + set + { + // the Player.Animation property might be set before this node is + // even ready, so if _sprite is null, then we just hold the new + // animation in a temp value, which will be assigned once this + // node is ready + if (_sprite is null) + { + _spriteAnim = value; + return; + } + + _sprite.Animation = value; + } + } + public override void _Ready() { + _sprite = GetNode("Sprite"); + if (_spriteAnim != default) + { + _sprite.Animation = _spriteAnim; + } + } + public override void ModifyVelocity() + { + if (StateMachine.State is State.PlayerRollState) + { + Velocity *= 2; + } + + if (Inventory.SelectedItem is Weapon weapon) + { + /*if (weapon is Sword sword) + { + if (sword.IsAttacking) + { + Velocity *= 0.5f; + } + } + else*/ + if (weapon.IsUsing) + { + Velocity *= 0.75f; + } + } } } } diff --git a/Characters/Player.tscn b/Characters/Player.tscn index 87373b7..789082e 100644 --- a/Characters/Player.tscn +++ b/Characters/Player.tscn @@ -1,28 +1,81 @@ -[gd_scene load_steps=11 format=3 uid="uid://bncaar8vp3b84"] +[gd_scene load_steps=22 format=3 uid="uid://bncaar8vp3b84"] [ext_resource type="Script" path="res://Characters/Player.cs" id="1_flygr"] -[ext_resource type="Texture2D" uid="uid://bw052v8ikfget" path="res://icon.svg" id="2_xmgd1"] +[ext_resource type="Texture2D" uid="uid://dxymfduyrbuvx" path="res://Sprites/Characters/forsen.png" id="2_swjho"] [ext_resource type="Script" path="res://Characters/States/Machine.cs" id="3_npkjp"] [ext_resource type="Script" path="res://Characters/States/PlayerIdleState.cs" id="4_4k4mb"] [ext_resource type="Script" path="res://Characters/States/PlayerMoveState.cs" id="5_tx5rw"] [ext_resource type="Script" path="res://Characters/States/PlayerRollState.cs" id="6_6bgrj"] +[ext_resource type="Script" path="res://Characters/States/PlayerAttackState.cs" id="7_4cuhw"] [ext_resource type="PackedScene" uid="uid://cajlwb67xenfy" path="res://Items/Weapons/Sword.tscn" id="7_4rxuv"] [ext_resource type="Script" path="res://Items/Inventory.cs" id="7_xyenu"] +[ext_resource type="PackedScene" uid="uid://cjgxyhgcyvsv7" path="res://BoundingBoxes/Hurtbox.tscn" id="9_avyu4"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_us1ce"] +atlas = ExtResource("2_swjho") +region = Rect2(0, 0, 24, 24) + +[sub_resource type="AtlasTexture" id="AtlasTexture_hn4kf"] +atlas = ExtResource("2_swjho") +region = Rect2(24, 0, 24, 24) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wfbeq"] +atlas = ExtResource("2_swjho") +region = Rect2(48, 0, 24, 24) + +[sub_resource type="AtlasTexture" id="AtlasTexture_qlmwk"] +atlas = ExtResource("2_swjho") +region = Rect2(72, 0, 24, 24) + +[sub_resource type="AtlasTexture" id="AtlasTexture_l1vgu"] +atlas = ExtResource("2_swjho") +region = Rect2(96, 0, 24, 24) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ytlaa"] +atlas = ExtResource("2_swjho") +region = Rect2(120, 0, 24, 24) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1q30d"] +atlas = ExtResource("2_swjho") +region = Rect2(144, 0, 24, 24) + +[sub_resource type="SpriteFrames" id="SpriteFrames_2h7cf"] +animations = [{ +"frames": [SubResource("AtlasTexture_us1ce"), SubResource("AtlasTexture_hn4kf")], +"loop": true, +"name": &"idle", +"speed": 5.0 +}, { +"frames": [SubResource("AtlasTexture_wfbeq"), SubResource("AtlasTexture_qlmwk"), SubResource("AtlasTexture_l1vgu"), SubResource("AtlasTexture_ytlaa"), SubResource("AtlasTexture_1q30d")], +"loop": true, +"name": &"move", +"speed": 12.0 +}] [sub_resource type="RectangleShape2D" id="RectangleShape2D_bfqew"] -size = Vector2(32, 16) +size = Vector2(16, 8) [sub_resource type="LabelSettings" id="LabelSettings_q5h1n"] font_size = 24 -[node name="Player" type="CharacterBody2D" node_paths=PackedStringArray("StateMachine")] +[sub_resource type="RectangleShape2D" id="RectangleShape2D_cjk6b"] +size = Vector2(16, 24) + +[node name="Player" type="CharacterBody2D" node_paths=PackedStringArray("Sprite", "Inventory", "StateMachine")] +texture_filter = 3 +y_sort_enabled = true script = ExtResource("1_flygr") Speed = 64.0 +Mass = 1.0 +Sprite = NodePath("Sprite") +Inventory = NodePath("Inventory") StateMachine = NodePath("StateMachine") +Faction = 1 -[node name="Sprite2D" type="Sprite2D" parent="."] -scale = Vector2(0.25, 0.25) -texture = ExtResource("2_xmgd1") +[node name="Sprite" type="AnimatedSprite2D" parent="."] +frames = SubResource("SpriteFrames_2h7cf") +animation = &"move" +playing = true [node name="CollisionShape2D" type="CollisionShape2D" parent="."] position = Vector2(0, 8) @@ -38,19 +91,24 @@ InitialState = NodePath("Idle") Character = NodePath("..") DebugLevel = 2 -[node name="Idle" type="Node" parent="StateMachine" node_paths=PackedStringArray("MoveState")] +[node name="Idle" type="Node" parent="StateMachine" node_paths=PackedStringArray("MoveState", "IdleState")] script = ExtResource("4_4k4mb") MoveState = NodePath("../Move") +IdleState = NodePath("") -[node name="Move" type="Node" parent="StateMachine" node_paths=PackedStringArray("IdleState", "RollState")] +[node name="Move" type="Node" parent="StateMachine" node_paths=PackedStringArray("RollState", "IdleState")] script = ExtResource("5_tx5rw") -IdleState = NodePath("../Idle") RollState = NodePath("../Roll") +IdleState = NodePath("../Idle") [node name="Roll" type="Node" parent="StateMachine" node_paths=PackedStringArray("IdleState")] script = ExtResource("6_6bgrj") IdleState = NodePath("../Idle") +[node name="Attack" type="Node" parent="StateMachine" node_paths=PackedStringArray("IdleState")] +script = ExtResource("7_4cuhw") +IdleState = NodePath("../Idle") + [node name="Debug" type="Control" parent="."] layout_mode = 3 anchors_preset = 0 @@ -67,7 +125,17 @@ horizontal_alignment = 1 [node name="Node" type="Node" parent="."] [node name="Inventory" type="Node2D" parent="."] +y_sort_enabled = true script = ExtResource("7_xyenu") [node name="Sword" parent="Inventory" instance=ExtResource("7_4rxuv")] -position = Vector2(21, 0) + +[node name="Hurtbox" parent="." instance=ExtResource("9_avyu4")] +Faction = 1 + +[node name="CollisionShape2D" parent="Hurtbox" index="0"] +shape = SubResource("RectangleShape2D_cjk6b") + +[connection signal="ReceivedDamage" from="Hurtbox" to="." method="_on_hurtbox_received_damage"] + +[editable path="Hurtbox"] diff --git a/Characters/States/CharacterState.cs b/Characters/States/CharacterState.cs index 2d64e41..185a6d0 100644 --- a/Characters/States/CharacterState.cs +++ b/Characters/States/CharacterState.cs @@ -27,6 +27,7 @@ namespace SupaLidlGame.Characters.State public virtual CharacterState PhysicsProcess(double delta) { Character.Velocity = Character.Direction * Character.Speed; + Character.ModifyVelocity(); Character.MoveAndSlide(); return null; } diff --git a/Characters/States/Machine.cs b/Characters/States/Machine.cs index bba2291..07f284e 100644 --- a/Characters/States/Machine.cs +++ b/Characters/States/Machine.cs @@ -24,6 +24,9 @@ namespace SupaLidlGame.Characters.State public void ChangeState(CharacterState nextState, bool isProxied = false) { + if (nextState is null) + return; + if (DebugLevel >= 2) { if (State is not null) diff --git a/Characters/States/NPCIdleState.cs b/Characters/States/NPCIdleState.cs index 45085d5..3fd4b95 100644 --- a/Characters/States/NPCIdleState.cs +++ b/Characters/States/NPCIdleState.cs @@ -16,5 +16,11 @@ namespace SupaLidlGame.Characters.State } return null; } + + public override CharacterState Enter(CharacterState previousState) + { + Character.Sprite.Play("idle"); + return base.Enter(previousState); + } } } diff --git a/Characters/States/NPCMoveState.cs b/Characters/States/NPCMoveState.cs index 4e250b0..4350009 100644 --- a/Characters/States/NPCMoveState.cs +++ b/Characters/States/NPCMoveState.cs @@ -16,5 +16,11 @@ namespace SupaLidlGame.Characters.State } return null; } + + public override CharacterState Enter(CharacterState previousState) + { + Character.Sprite.Play("move"); + return base.Enter(previousState); + } } } diff --git a/Characters/States/PlayerAttackState.cs b/Characters/States/PlayerAttackState.cs index 8bf1a59..730eafc 100644 --- a/Characters/States/PlayerAttackState.cs +++ b/Characters/States/PlayerAttackState.cs @@ -1,25 +1,38 @@ using Godot; -using Godot.Collections; -using Godot.NativeInterop; +using SupaLidlGame.Items; namespace SupaLidlGame.Characters.State { public partial class PlayerAttackState : PlayerState { - [Export] - public CharacterState IdleState { get; set; } - - private float _attackTime = 0; + private double _attackTime = 0; public override CharacterState Enter(CharacterState previousState) { - _attackTime = 0; + if (Character.Inventory.SelectedItem is Weapon weapon) + { + _attackTime = weapon.UseTime; + weapon.Visible = true; + Character.Inventory.SelectedItem.Use(); + } + else + { + return IdleState; + } return base.Enter(previousState); } public override void Exit(CharacterState nextState) { - _attackTime = 0; + if (Character.Inventory.SelectedItem is null) + { + + } + Character.Inventory.SelectedItem.Deuse(); + if (Character.Inventory.SelectedItem is Weapon weapon) + { + //weapon.Visible = false; + } base.Exit(nextState); } @@ -30,11 +43,16 @@ namespace SupaLidlGame.Characters.State public override CharacterState PhysicsProcess(double delta) { + Character.Velocity *= 0.5f; return base.PhysicsProcess(delta); } public override CharacterState Process(double delta) { + if ((_attackTime -= delta) <= 0) + { + return IdleState; + } return base.Process(delta); } } diff --git a/Characters/States/PlayerIdleState.cs b/Characters/States/PlayerIdleState.cs index 080ad51..aa84471 100644 --- a/Characters/States/PlayerIdleState.cs +++ b/Characters/States/PlayerIdleState.cs @@ -12,7 +12,7 @@ namespace SupaLidlGame.Characters.State GD.Print("Entered idle state"); if (previousState is not PlayerMoveState) { - if (Character.Direction.LengthSquared() > 0) + if (Character.Direction.LengthSquared() > 0.01f) { // other states like attacking or rolling can just delegate // the work of checking if the player is moving to this idle @@ -21,6 +21,7 @@ namespace SupaLidlGame.Characters.State return MoveState; } } + _player.Animation = "idle"; return base.Enter(previousState); } diff --git a/Characters/States/PlayerMoveState.cs b/Characters/States/PlayerMoveState.cs index 440824a..9f27a20 100644 --- a/Characters/States/PlayerMoveState.cs +++ b/Characters/States/PlayerMoveState.cs @@ -1,18 +1,17 @@ using Godot; +using SupaLidlGame.Items; namespace SupaLidlGame.Characters.State { public partial class PlayerMoveState : PlayerState { [Export] - public CharacterState IdleState { get; set; } - - [Export] - public CharacterState RollState { get; set; } + public PlayerRollState RollState { get; set; } public override CharacterState Enter(CharacterState previousState) { Godot.GD.Print("Started moving"); + _player.Animation = "move"; return base.Enter(previousState); } @@ -30,9 +29,19 @@ namespace SupaLidlGame.Characters.State { if (@event.IsActionPressed("roll")) { - return RollState; + if (Character.Inventory.SelectedItem is Weapon weapon) + { + if (!weapon.IsUsing) + { + return RollState; + } + } + else + { + return RollState; + } } - return null; + return base.Input(@event); } } } diff --git a/Characters/States/PlayerRollState.cs b/Characters/States/PlayerRollState.cs index 808d8aa..14ee519 100644 --- a/Characters/States/PlayerRollState.cs +++ b/Characters/States/PlayerRollState.cs @@ -4,9 +4,6 @@ namespace SupaLidlGame.Characters.State { public partial class PlayerRollState : PlayerState { - [Export] - public CharacterState IdleState { get; set; } - private double _timeLeftToRoll = 0; private Vector2 _rollDirection = Vector2.Zero; @@ -16,6 +13,7 @@ namespace SupaLidlGame.Characters.State _timeLeftToRoll = 0.5; // roll the direction we were previously moving in _rollDirection = Character.Direction; + Character.Target = Character.Direction; return base.Enter(previousState); } @@ -38,11 +36,13 @@ namespace SupaLidlGame.Characters.State return null; } + /* public override CharacterState PhysicsProcess(double delta) { Character.Velocity = Character.Direction * Character.Speed * 1.5f; Character.MoveAndSlide(); return null; } + */ } } diff --git a/Characters/States/PlayerState.cs b/Characters/States/PlayerState.cs index c802eea..b15283f 100644 --- a/Characters/States/PlayerState.cs +++ b/Characters/States/PlayerState.cs @@ -1,3 +1,6 @@ +using Godot; +using SupaLidlGame.Items; + namespace SupaLidlGame.Characters.State { public partial class PlayerState : CharacterState @@ -5,10 +8,59 @@ namespace SupaLidlGame.Characters.State //public PlayerMachine PlayerMachine => Machine as PlayerMachine; protected Player _player => Character as Player; + [Export] + public PlayerIdleState IdleState { get; set; } + + public override CharacterState Input(InputEvent @event) + { + /* + if (@event is InputEventKey inputEventKey) + { + GD.Print("hello"); + if (inputEventKey.Keycode == Key.G) + { + GD.Print("hi"); + } + }*/ + if (@event.IsActionPressed("equip")) + { + Character.Inventory.SelectedItem = Character.Inventory.GetNode("Sword"); + //Character.Inventory.AddItem(); + } + + if (@event.IsActionPressed("attack1")) + { + if (Character.Inventory.SelectedItem is not null) + { + Character.Inventory.SelectedItem.Use(); + //return AttackState; + } + } + + //if (this is PlayerAttackState) + //{ + // if (@event.IsActionReleased("attack1")) + // { + // return IdleState; + // } + //} + + return base.Input(@event); + } + public override CharacterState Process(double delta) { Character.Direction = Godot.Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down"); + Vector2 mousePos = Character.GetGlobalMousePosition(); + Vector2 dirToMouse = Character.GlobalPosition.DirectionTo(mousePos); + if (Character.Inventory.SelectedItem is Weapon weapon) + { + if (!weapon.IsUsing) + { + Character.Target = dirToMouse; + } + } return base.Process(delta); } } diff --git a/Items/Inventory.cs b/Items/Inventory.cs index 0a1346d..bee0019 100644 --- a/Items/Inventory.cs +++ b/Items/Inventory.cs @@ -32,7 +32,11 @@ namespace SupaLidlGame.Items _selectedItem = value; - _selectedItem.Equip(Character); + // this is to handle if item was manually unequipped + if (_selectedItem is not null) + { + _selectedItem.Equip(Character); + } } } @@ -43,23 +47,27 @@ namespace SupaLidlGame.Items return null; } + item.CharacterOwner = Character; + item.Visible = false; Items.Add(item); return item; } public Item DropItem(Item item) { + item.CharacterOwner = null; + item.Visible = true; throw new System.NotImplementedException(); } public override void _Ready() { - Owner = GetParent(); + Character = GetParent(); foreach (Node child in GetChildren()) { if (child is Item item) { - Items.Add(item); + AddItem(item); } } base._Ready(); diff --git a/Items/Item.cs b/Items/Item.cs index af78ec6..c1813b5 100644 --- a/Items/Item.cs +++ b/Items/Item.cs @@ -8,6 +8,8 @@ namespace SupaLidlGame.Items [Export] public string Description { get; set; } + public Character CharacterOwner { get; set; } + public abstract void Equip(Character character); public abstract void Unequip(Character character); diff --git a/Items/Weapon.cs b/Items/Weapon.cs index 62815ae..1c6619a 100644 --- a/Items/Weapon.cs +++ b/Items/Weapon.cs @@ -7,7 +7,7 @@ namespace SupaLidlGame.Items { public double RemainingUseTime { get; protected set; } = 0; - public bool CanStartAttack => RemainingUseTime <= 0; + public bool IsUsing => RemainingUseTime > 0; /// /// How much damage in HP that this weapon deals. @@ -46,9 +46,25 @@ namespace SupaLidlGame.Items Character = null; } + public override void Use() + { + RemainingUseTime = UseTime; + } + public override void Deuse() { } + + public override void _Process(double delta) + { + if (RemainingUseTime > 0) + { + if ((RemainingUseTime -= delta) <= 0) + { + Deuse(); + } + } + } } } diff --git a/Items/Weapons/Sword.cs b/Items/Weapons/Sword.cs index 9fe20d3..b6c115f 100644 --- a/Items/Weapons/Sword.cs +++ b/Items/Weapons/Sword.cs @@ -6,22 +6,81 @@ namespace SupaLidlGame.Items.Weapons { public partial class Sword : Weapon { + public double RemainingAttackTime { get; protected set; } = 0; + + public bool IsAttacking => RemainingAttackTime > 0; + [Export] public Hitbox Hitbox { get; set; } + [Export] + public AnimationPlayer AnimationPlayer { get; set; } + + /// + /// The time frame in seconds for which the weapon will deal damage. + /// + /// + /// The value of AttackTime should be less than the + /// value of UseTime + /// + [Export] + public double AttackTime { get; set; } = 0; + public override void Equip(Character character) { + Visible = true; base.Equip(character); + Hitbox.Faction = character.Faction; // character is null before base + } + + public override void Unequip(Character character) + { + Visible = false; + base.Unequip(character); } public override void Use() { - Hitbox.IsEnabled = true; + if (RemainingUseTime > 0) + { + return; + } + + RemainingAttackTime = AttackTime; + + AnimationPlayer.Play("use"); + Hitbox.IsDisabled = false; + base.Use(); } public override void Deuse() { - Hitbox.IsEnabled = false; + AnimationPlayer.Stop(); + Deattack(); + base.Deuse(); + } + + protected void Deattack() + { + Hitbox.IsDisabled = true; + Hitbox.ResetIgnoreList(); + } + + public override void _Ready() + { + Hitbox.Damage = Damage; + } + + public override void _Process(double delta) + { + if (RemainingAttackTime > 0) + { + if ((RemainingAttackTime -= delta) <= 0) + { + Deattack(); + } + } + base._Process(delta); } } } diff --git a/Items/Weapons/Sword.tscn b/Items/Weapons/Sword.tscn index 73e6f9f..f6496dc 100644 --- a/Items/Weapons/Sword.tscn +++ b/Items/Weapons/Sword.tscn @@ -1,26 +1,159 @@ -[gd_scene load_steps=5 format=3 uid="uid://cajlwb67xenfy"] +[gd_scene load_steps=13 format=3 uid="uid://cajlwb67xenfy"] [ext_resource type="Script" path="res://Items/Weapons/Sword.cs" id="1_mlo73"] [ext_resource type="Texture2D" uid="uid://dt6u8p4h6g7le" path="res://Sprites/knife.png" id="2_dmsp2"] [ext_resource type="PackedScene" uid="uid://du5vhccg75nrq" path="res://BoundingBoxes/Hitbox.tscn" id="3_up3ob"] +[ext_resource type="PackedScene" uid="uid://cojxmcin13ihm" path="res://Utils/Trail.tscn" id="4_pt6lq"] [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_yln58"] radius = 8.0 -height = 24.0 +height = 32.0 -[node name="Sword" type="Node2D" node_paths=PackedStringArray("Hitbox")] +[sub_resource type="Curve" id="Curve_4cxtp"] +_data = [Vector2(0.00687286, 1), 0.0, 0.0, 0, 0, Vector2(0.879725, 0.190909), -2.93145, -2.93145, 0, 0, Vector2(1, 0.0454545), 0.0483926, 0.0, 0, 0] +point_count = 3 + +[sub_resource type="Gradient" id="Gradient_2ablm"] +offsets = PackedFloat32Array(0.835938, 0.992188) +colors = PackedColorArray(1, 1, 1, 0.498039, 1, 1, 1, 0) + +[sub_resource type="Animation" id="Animation_b7327"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Anchor:rotation") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [-0.785398] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("Anchor/Sprite2D:rotation") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [-1.19209e-07] +} +tracks/2/type = "value" +tracks/2/imported = false +tracks/2/enabled = true +tracks/2/path = NodePath("Anchor/Sprite2D:position") +tracks/2/interp = 1 +tracks/2/loop_wrap = true +tracks/2/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(8, 1)] +} + +[sub_resource type="Animation" id="Animation_mv7y2"] +resource_name = "idle" + +[sub_resource type="Animation" id="Animation_orc8t"] +resource_name = "use" +length = 0.5 +loop_mode = 1 +step = 0.05 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Anchor:rotation") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.1, 0.3, 0.5), +"transitions": PackedFloat32Array(1, 1, 1, 1), +"update": 0, +"values": [-0.785398, 1.5708, 1.5708, -0.785398] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("Anchor/Sprite2D:rotation") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 0.1, 0.3, 0.5), +"transitions": PackedFloat32Array(1, 1, 1, 1), +"update": 0, +"values": [-1.19209e-07, 0.785398, 0.785398, -1.19209e-07] +} +tracks/2/type = "value" +tracks/2/imported = false +tracks/2/enabled = true +tracks/2/path = NodePath("Anchor/Sprite2D:position") +tracks/2/interp = 1 +tracks/2/loop_wrap = true +tracks/2/keys = { +"times": PackedFloat32Array(0, 0.1, 0.3, 0.5), +"transitions": PackedFloat32Array(1, 1, 1, 1), +"update": 0, +"values": [Vector2(8, 1), Vector2(12, -1), Vector2(12, -1), Vector2(8, 1)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_tao4k"] +_data = { +"RESET": SubResource("Animation_b7327"), +"idle": SubResource("Animation_mv7y2"), +"use": SubResource("Animation_orc8t") +} + +[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_1lid1"] + +[node name="Sword" type="Node2D" node_paths=PackedStringArray("Hitbox", "AnimationPlayer")] texture_filter = 3 +y_sort_enabled = true script = ExtResource("1_mlo73") -Hitbox = NodePath("Hitbox") +Hitbox = NodePath("Anchor/Sprite2D/Hitbox") +AnimationPlayer = NodePath("AnimationPlayer") +AttackTime = 0.1 +Damage = 20.0 +UseTime = 0.5 +Description = "A basic sword." -[node name="Sprite2D" type="Sprite2D" parent="."] +[node name="Anchor" type="Node2D" parent="."] +rotation = -0.785398 +y_sort_enabled = true + +[node name="Sprite2D" type="Sprite2D" parent="Anchor"] +position = Vector2(8, 1) +y_sort_enabled = true texture = ExtResource("2_dmsp2") -[node name="Hitbox" parent="." instance=ExtResource("3_up3ob")] +[node name="Hitbox" parent="Anchor/Sprite2D" instance=ExtResource("3_up3ob")] +IsDisabled = true -[node name="CollisionShape2D" parent="Hitbox" index="0"] +[node name="CollisionShape2D" parent="Anchor/Sprite2D/Hitbox" index="0"] +position = Vector2(0, -4) shape = SubResource("CapsuleShape2D_yln58") +disabled = true + +[node name="Trail" parent="Anchor" node_paths=PackedStringArray("Tracking") instance=ExtResource("4_pt6lq")] +position = Vector2(2.40734, -0.55655) +rotation = 0.945464 +scale = Vector2(1, 1) +width_curve = SubResource("Curve_4cxtp") +gradient = SubResource("Gradient_2ablm") +Tracking = NodePath("../Sprite2D") [node name="AnimationPlayer" type="AnimationPlayer" parent="."] +libraries = { +"": SubResource("AnimationLibrary_tao4k") +} -[editable path="Hitbox"] +[node name="AnimationTree" type="AnimationTree" parent="."] +tree_root = SubResource("AnimationNodeAnimation_1lid1") +anim_player = NodePath("../AnimationPlayer") +active = true + +[editable path="Anchor/Sprite2D/Hitbox"] diff --git a/README.org b/README.org new file mode 100644 index 0000000..19a9833 --- /dev/null +++ b/README.org @@ -0,0 +1,7 @@ +#+TITLE: SupaLidlGame + +Forsen-related game + +*NOTE:* although the latest version at the time of writing is ~4.0.beta5.mono~, this project only works under ~4.0.beta4.mono~. The latest beta removes node exports, which was previously a feature in Beta 4. + +[[https://github.com/godotengine/godot/pull/67055]] diff --git a/Scenes/Level.tscn b/Scenes/Level.tscn index 1ef7d43..2603503 100644 --- a/Scenes/Level.tscn +++ b/Scenes/Level.tscn @@ -1109,7 +1109,7 @@ tile_set = SubResource("TileSet_18c7j") format = 2 layer_0/name = "Walls" layer_0/y_sort_enabled = true -layer_0/tile_data = PackedInt32Array(-131067, 458752, 0, 131065, 458752, 0) +layer_0/tile_data = PackedInt32Array(131065, 458752, 0, -65531, 458752, 0) layer_1/name = "Ground 2" layer_1/enabled = true layer_1/modulate = Color(1, 1, 1, 1) @@ -1134,17 +1134,16 @@ layer_3/tile_data = PackedInt32Array(-458765, 393216, 1, -393229, 327680, 0, -39 [node name="Player" parent="TileMap" instance=ExtResource("1_m35hr")] position = Vector2(-81, -34) -scale = Vector2(0.25, 0.25) motion_mode = 1 [node name="ExampleEnemy" parent="TileMap" instance=ExtResource("2_uti3y")] -position = Vector2(-4, 60) -scale = Vector2(0.25, 0.25) +position = Vector2(38, 42) +scale = Vector2(1.00571, 1) [node name="ExampleEnemy2" parent="TileMap" instance=ExtResource("2_uti3y")] -position = Vector2(-17, -4) -scale = Vector2(0.25, 0.25) +position = Vector2(156, 58) +scale = Vector2(1.00571, 1) [node name="ExampleEnemy3" parent="TileMap" instance=ExtResource("2_uti3y")] -position = Vector2(34, 25) -scale = Vector2(0.25, 0.25) +position = Vector2(175, -15) +scale = Vector2(1.00571, 1) diff --git a/Sprites/Characters/forsen.ase b/Sprites/Characters/forsen.ase new file mode 100644 index 0000000..042eb66 Binary files /dev/null and b/Sprites/Characters/forsen.ase differ diff --git a/Sprites/Characters/forsen.png b/Sprites/Characters/forsen.png new file mode 100644 index 0000000..4628a63 Binary files /dev/null and b/Sprites/Characters/forsen.png differ diff --git a/Sprites/Characters/forsen.png.import b/Sprites/Characters/forsen.png.import new file mode 100644 index 0000000..7ce2853 --- /dev/null +++ b/Sprites/Characters/forsen.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxymfduyrbuvx" +path="res://.godot/imported/forsen.png-fce73bdb02d4ae9ad3a1c7a29f120a10.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Sprites/Characters/forsen.png" +dest_files=["res://.godot/imported/forsen.png-fce73bdb02d4ae9ad3a1c7a29f120a10.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Tests/HitboxTest.tscn b/Tests/HitboxTest.tscn index aafb0e9..503263d 100644 --- a/Tests/HitboxTest.tscn +++ b/Tests/HitboxTest.tscn @@ -17,7 +17,6 @@ position = Vector2(377, 214) texture = ExtResource("1_jh0yt") [node name="Hitbox" parent="Node2D" instance=ExtResource("2_gtebh")] -IsEnabled = true [node name="OscillatingBody" type="CharacterBody2D" parent="."] script = ExtResource("3_exvxj") diff --git a/Utils/IFaction.cs b/Utils/IFaction.cs new file mode 100644 index 0000000..9f3a479 --- /dev/null +++ b/Utils/IFaction.cs @@ -0,0 +1,13 @@ +using Godot; + +namespace SupaLidlGame.Utils +{ + public interface IFaction + { + /// + /// The faction index that this entity belongs to. + /// + [Export] + public ushort Faction { get; set; } + } +} diff --git a/Utils/Trail.cs b/Utils/Trail.cs new file mode 100644 index 0000000..56c9825 --- /dev/null +++ b/Utils/Trail.cs @@ -0,0 +1,21 @@ +using Godot; + +public partial class Trail : Line2D +{ + [Export] + public int MaximumPoints { get; set; } + + [Export] + public Node2D Tracking { get; set; } + + public override void _Process(double delta) + { + Vector2 point = Tracking.Position; + AddPoint(point); + + while (Points.Length > MaximumPoints) + { + RemovePoint(0); + } + } +} diff --git a/Utils/Trail.tscn b/Utils/Trail.tscn new file mode 100644 index 0000000..69af05e --- /dev/null +++ b/Utils/Trail.tscn @@ -0,0 +1,8 @@ +[gd_scene load_steps=2 format=3 uid="uid://cojxmcin13ihm"] + +[ext_resource type="Script" path="res://Utils/Trail.cs" id="1_t42kk"] + +[node name="Trail" type="Line2D"] +show_behind_parent = true +script = ExtResource("1_t42kk") +MaximumPoints = 16 diff --git a/project.godot b/project.godot index c9241d8..bafb510 100644 --- a/project.godot +++ b/project.godot @@ -51,6 +51,16 @@ roll={ , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"unicode":0,"echo":false,"script":null) ] } +attack1={ +"deadzone": 0.5, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"pressed":false,"double_click":false,"script":null) +] +} +equip={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"unicode":0,"echo":false,"script":null) +] +} [physics]