diff --git a/Characters/Character.cs b/Characters/Character.cs index c7e4d86..a437696 100644 --- a/Characters/Character.cs +++ b/Characters/Character.cs @@ -159,7 +159,8 @@ namespace SupaLidlGame.Characters return; } - if (Inventory.SelectedItem is Weapon weapon) + // TODO: support for offhand items + if (Inventory.PrimaryItem is Weapon weapon) { weapon.Use(); } diff --git a/Characters/Enemy.cs b/Characters/Enemy.cs index b33b6e1..aac648f 100644 --- a/Characters/Enemy.cs +++ b/Characters/Enemy.cs @@ -7,7 +7,8 @@ namespace SupaLidlGame.Characters { public override void _Ready() { - Inventory.SelectedItem = Inventory.GetNode("Sword"); + //Inventory.SelectedItem = Inventory.GetNode("Sword"); + Inventory.SelectFirstItem(); base._Ready(); } diff --git a/Characters/ExampleEnemy.tscn b/Characters/ExampleEnemy.tscn index 6654a4f..3508c9e 100644 --- a/Characters/ExampleEnemy.tscn +++ b/Characters/ExampleEnemy.tscn @@ -165,7 +165,6 @@ y_sort_enabled = true script = ExtResource("7_43gq8") [node name="Sword" parent="Inventory" instance=ExtResource("8_s3c8r")] -Knockback = 40.0 [node name="FlashAnimation" type="AnimationPlayer" parent="."] libraries = { diff --git a/Characters/NPC.cs b/Characters/NPC.cs index 164c4dd..ef61bf4 100644 --- a/Characters/NPC.cs +++ b/Characters/NPC.cs @@ -288,7 +288,8 @@ namespace SupaLidlGame.Characters if (Target.LengthSquared() < 1024) { - if (Inventory.SelectedItem is Weapon weapon) + // TODO: offhand items + if (Inventory.PrimaryItem is Weapon weapon) { UseCurrentItem(); } diff --git a/Characters/Player.cs b/Characters/Player.cs index 81ecdb3..65bf361 100644 --- a/Characters/Player.cs +++ b/Characters/Player.cs @@ -61,5 +61,19 @@ namespace SupaLidlGame.Characters GD.Print("died"); //base.Die(); } + + public bool IsUsingAnyWeapon() + { + bool checkItem(Items.Item item) + { + if (item is Items.Weapon weapon) + { + return weapon.IsUsing; + } + return false; + } + return checkItem(Inventory.PrimaryItem) || + checkItem(Inventory.OffhandItem); + } } } diff --git a/Characters/Player.tscn b/Characters/Player.tscn index dedeeaa..96ec5b9 100644 --- a/Characters/Player.tscn +++ b/Characters/Player.tscn @@ -8,7 +8,6 @@ [ext_resource type="PackedScene" uid="uid://cl56eadpklnbo" path="res://Utils/PlayerCamera.tscn" id="4_ym125"] [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="PackedScene" uid="uid://d72ehtv1ks0e" 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"] [ext_resource type="AudioStream" uid="uid://njun3e6v4854" path="res://Assets/Sounds/hurt.wav" id="12_h0x0g"] @@ -86,6 +85,69 @@ size = Vector2(16, 8) [sub_resource type="LabelSettings" id="LabelSettings_q5h1n"] font_size = 24 +[sub_resource type="CSharpScript" id="CSharpScript_rcje3"] +script/source = "using Godot; +using SupaLidlGame.Characters; + +namespace SupaLidlGame.Items +{ + public partial class ItemInfo : Resource + { + [Export] + public string ItemName { get; set; } = \"Item Name\"; + + [Export] + public string Description { get; set; } = \"Item description.\"; + + [Export] + public bool CanStack { get; set; } = false; + + [Export] + public int Count { get; set; } = 1; + + [Export] + public bool IsOneHanded { get; set; } = false; + + [Export] + public Character CharacterOwner { get; set; } = null; + + [Export] + public Texture2D Texture { get; set; } + + [Export] + public string ScenePath { get; set; } + + /// + /// Determines if this item can directly stack with other items + /// + public virtual bool StacksWith(ItemInfo itemInfo) + { + if (!CanStack) + { + return false; + } + + if (ItemName != itemInfo.ItemName) + { + return false; + } + + // several more conditions may be added soon + + return true; + } + + public Item InstantiateItem(string name = \"Primary\") + { + var scene = ResourceLoader.Load(ScenePath); + var instance = scene.Instantiate(); + instance.Name = name; + return instance; + } + } +} +" + [sub_resource type="RectangleShape2D" id="RectangleShape2D_cjk6b"] size = Vector2(16, 24) @@ -193,8 +255,7 @@ horizontal_alignment = 1 y_sort_enabled = true position = Vector2(0, 2) script = ExtResource("7_xyenu") - -[node name="Sword" parent="Inventory" instance=ExtResource("7_4rxuv")] +Items = Array[Resource]([SubResource("CSharpScript_rcje3")]) [node name="Hurtbox" parent="." instance=ExtResource("9_avyu4")] Faction = 1 diff --git a/Characters/States/PlayerAttackState.cs b/Characters/States/PlayerAttackState.cs index 730eafc..69e50a4 100644 --- a/Characters/States/PlayerAttackState.cs +++ b/Characters/States/PlayerAttackState.cs @@ -9,11 +9,11 @@ namespace SupaLidlGame.Characters.State public override CharacterState Enter(CharacterState previousState) { - if (Character.Inventory.SelectedItem is Weapon weapon) + if (Character.Inventory.PrimaryItem is Weapon weapon) { - _attackTime = weapon.UseTime; + _attackTime = weapon.Info.UseTime; weapon.Visible = true; - Character.Inventory.SelectedItem.Use(); + Character.Inventory.PrimaryItem.Use(); } else { @@ -24,12 +24,12 @@ namespace SupaLidlGame.Characters.State public override void Exit(CharacterState nextState) { - if (Character.Inventory.SelectedItem is null) + if (Character.Inventory.PrimaryItem is null) { } - Character.Inventory.SelectedItem.Deuse(); - if (Character.Inventory.SelectedItem is Weapon weapon) + Character.Inventory.PrimaryItem.Deuse(); + if (Character.Inventory.PrimaryItem is Weapon weapon) { //weapon.Visible = false; } diff --git a/Characters/States/PlayerMoveState.cs b/Characters/States/PlayerMoveState.cs index 9f27a20..6006765 100644 --- a/Characters/States/PlayerMoveState.cs +++ b/Characters/States/PlayerMoveState.cs @@ -29,14 +29,7 @@ namespace SupaLidlGame.Characters.State { if (@event.IsActionPressed("roll")) { - if (Character.Inventory.SelectedItem is Weapon weapon) - { - if (!weapon.IsUsing) - { - return RollState; - } - } - else + if (!_player.IsUsingAnyWeapon()) { return RollState; } diff --git a/Characters/States/PlayerState.cs b/Characters/States/PlayerState.cs index 763fc59..a158a81 100644 --- a/Characters/States/PlayerState.cs +++ b/Characters/States/PlayerState.cs @@ -16,7 +16,9 @@ namespace SupaLidlGame.Characters.State #if DEBUG if (@event.IsActionPressed("equip")) { - Character.Inventory.SelectedItem = Character.Inventory.GetNode("Sword"); + //Character.Inventory.SelectedItem = Character.Inventory.GetNode("Sword"); + //Character.Inventory.SelectedItem = Character. + Character.Inventory.SelectFirstItem(); } #endif @@ -29,7 +31,7 @@ namespace SupaLidlGame.Characters.State "ui_up", "ui_down"); Vector2 mousePos = Character.GetGlobalMousePosition(); Vector2 dirToMouse = Character.GlobalPosition.DirectionTo(mousePos); - if (Character.Inventory.SelectedItem is Weapon weapon) + if (Character.Inventory.PrimaryItem is Weapon weapon) { if (!weapon.IsUsing) { @@ -39,7 +41,7 @@ namespace SupaLidlGame.Characters.State if (Godot.Input.IsActionPressed("attack1")) { - if (Character.Inventory.SelectedItem is not null) + if (Character.Inventory.PrimaryItem is not null) { Character.UseCurrentItem(); } diff --git a/Extensions/Resource.cs b/Extensions/Resource.cs new file mode 100644 index 0000000..b14b931 --- /dev/null +++ b/Extensions/Resource.cs @@ -0,0 +1,12 @@ +using Godot; + +namespace SupaLidlGame.Extensions +{ + public static class ResourceExtensions + { + public static string GetFileName(this Resource resource) + { + return resource.ResourcePath.GetFile(); + } + } +} diff --git a/Items/Inventory.cs b/Items/Inventory.cs index 55f787d..8220e89 100644 --- a/Items/Inventory.cs +++ b/Items/Inventory.cs @@ -8,39 +8,96 @@ namespace SupaLidlGame.Items { public Character Character { get; private set; } - public List Items { get; private set; } = new List(); + [Export] + public Godot.Collections.Array Items { get; private set; } + + [Export] + public ItemInfo Test { get; set; } public const int MaxCapacity = 32; - private Item _selectedItem; + private Item _primaryItem; private Item _offhandItem; - public Item SelectedItem + public Item PrimaryItem { - get => _selectedItem; - set => EquipItem(value, ref _selectedItem); + get => _primaryItem; + private set => _primaryItem = value; } public Item OffhandItem { - get => _selectedItem; - set => EquipItem(value, ref _offhandItem); + get => _offhandItem; + private set => _offhandItem = value; } + public Inventory() + { + Items = new Godot.Collections.Array(); + } + + public bool EquipItem(ItemInfo info, bool offhand = false) + { + if (UnequipItem() == info) + { + // if the item that was unequipped was our item, then we stop + return true; + } + + var idx = Items.IndexOf(info); + + if (idx < 0) + { + return false; + } + + ItemInfo item = Items[idx]; + + string name = offhand ? "Offhand" : "Primary"; + PrimaryItem = item.InstantiateItem(name); + PrimaryItem.Equip(Character); + PrimaryItem.CharacterOwner = Character; + //PrimaryItem.CharacterOwner + AddChild(PrimaryItem); + return true; + } + + public ItemInfo UnequipItem(bool offhand = false) + { + Item item; + if (!offhand) + { + item = Character.Inventory.GetNode("Primary"); + } + else + { + item = Character.Inventory.GetNode("Offhand"); + } + + if (item is null) + { + return null; + } + + item.QueueFree(); + return item.Info; + } + + /* private bool EquipItem(Item item, ref Item slot) { - if (item is not null && item.IsOneHanded) + if (item is not null && item.Info.IsOneHanded) { // we can not equip this if either hand is occupied by // two-handed item - if (_selectedItem is not null && !_selectedItem.IsOneHanded) + if (_selectedItem is not null && !_selectedItem.Info.IsOneHanded) { return false; } - if (_offhandItem is not null && !_offhandItem.IsOneHanded) + if (_offhandItem is not null && !_offhandItem.Info.IsOneHanded) { return false; } @@ -66,39 +123,59 @@ namespace SupaLidlGame.Items return true; } + */ - public Item AddItem(Item item) + public ItemInfo AddItem(ItemInfo info) { if (Items.Count >= MaxCapacity) { return null; } - item.CharacterOwner = Character; - item.Visible = false; - Items.Add(item); - return item; + info.CharacterOwner = Character; + //item.Visible = false; + Items.Add(info); + return info; } - public Item DropItem(Item item) + public Item DropItem(ItemInfo item) { item.CharacterOwner = null; - item.Visible = true; - var e = SelectedItem = item; + //item.Visible = true; + //var e = SelectedItem = item; throw new System.NotImplementedException(); } public override void _Ready() { Character = GetParent(); + EnsureChildrenItems(); + base._Ready(); + } + + private void EnsureChildrenItems() + { foreach (Node child in GetChildren()) { if (child is Item item) { - AddItem(item); + GD.Print(item.Info.Count); + if (Items.IndexOf(item.Info) < 0) + { + AddItem(item.Info); + PrimaryItem = item; + } } } - base._Ready(); + } + + public void SelectFirstItem() + { + EnsureChildrenItems(); + if (Items.Count > 0) + { + EquipItem(Items[0]); + } } } } diff --git a/Items/Item.cs b/Items/Item.cs index c2e64d1..ad53c89 100644 --- a/Items/Item.cs +++ b/Items/Item.cs @@ -6,17 +6,7 @@ namespace SupaLidlGame.Items public abstract partial class Item : Node2D { [Export] - public string ItemName { get; set; } - - [Export] - public string Description { get; set; } - - [Export] - public bool CanStack { get; set; } = false; - - public int Count { get; set; } = 1; - - public bool IsOneHanded { get; set; } = false; + public ItemInfo Info { get; set; } public Character CharacterOwner { get; set; } @@ -25,12 +15,12 @@ namespace SupaLidlGame.Items /// public virtual bool StacksWith(Item item) { - if (!CanStack) + if (!Info.CanStack) { return false; } - if (ItemName != item.ItemName) + if (Info.ItemName != item.Info.ItemName) { return false; } diff --git a/Items/ItemDatabase.cs b/Items/ItemDatabase.cs new file mode 100644 index 0000000..87ccbea --- /dev/null +++ b/Items/ItemDatabase.cs @@ -0,0 +1,7 @@ +namespace SupaLidlGame +{ + public class ItemDatabase + { + + } +} diff --git a/Items/ItemInfo.cs b/Items/ItemInfo.cs new file mode 100644 index 0000000..9617f6a --- /dev/null +++ b/Items/ItemInfo.cs @@ -0,0 +1,63 @@ +using Godot; +using SupaLidlGame.Characters; +using MonoCustomResourceRegistry; + +namespace SupaLidlGame.Items +{ + [RegisteredType(nameof(ItemInfo))] + public partial class ItemInfo : Resource + { + [Export] + public string ItemName { get; set; } = "Item Name"; + + [Export] + public string Description { get; set; } = "Item description."; + + [Export] + public bool CanStack { get; set; } = false; + + [Export] + public int Count { get; set; } = 1; + + [Export] + public bool IsOneHanded { get; set; } = false; + + [Export] + public Character CharacterOwner { get; set; } = null; + + [Export] + public Texture2D Texture { get; set; } + + [Export] + public string ScenePath { get; set; } + + /// + /// Determines if this item can directly stack with other items + /// + public virtual bool StacksWith(ItemInfo itemInfo) + { + if (!CanStack) + { + return false; + } + + if (ItemName != itemInfo.ItemName) + { + return false; + } + + // several more conditions may be added soon + + return true; + } + + public Item InstantiateItem(string name = "Primary") + { + var scene = ResourceLoader.Load(ScenePath); + var instance = scene.Instantiate(); + instance.Name = name; + instance.Info = this; + return instance; + } + } +} diff --git a/Items/MeleeInfo.cs b/Items/MeleeInfo.cs new file mode 100644 index 0000000..40ea42d --- /dev/null +++ b/Items/MeleeInfo.cs @@ -0,0 +1,17 @@ +using Godot; + +namespace SupaLidlGame.Items.Weapons +{ + public partial class MeleeInfo : WeaponInfo + { + /// + /// 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; + } +} diff --git a/Items/Weapon.cs b/Items/Weapon.cs index 4db25d1..33f29c5 100644 --- a/Items/Weapon.cs +++ b/Items/Weapon.cs @@ -10,30 +10,7 @@ namespace SupaLidlGame.Items public bool IsUsing => RemainingUseTime > 0; - /// - /// How much damage in HP that this weapon deals. - /// - [Export] - public float Damage { get; set; } = 0; - - /// - /// The time in seconds it takes for this weapon to become available - /// again after using. - /// - [Export] - public double UseTime { get; set; } = 0; - - /// - /// The magnitude of the knockback force of the weapon. - /// - [Export] - public float Knockback { get; set; } = 0; - - /// - /// The initial velocity of any projectile the weapon may spawn. - /// - [Export] - public float InitialVelocity { get; set; } = 0; + public new WeaponInfo Info => base.Info as WeaponInfo; /// /// Whether or not the weapon can parry other weapons and is @@ -51,6 +28,7 @@ namespace SupaLidlGame.Items public override void Equip(Character character) { + GD.Print("Equipped by " + character.Name); Character = character; } @@ -61,7 +39,7 @@ namespace SupaLidlGame.Items public override void Use() { - RemainingUseTime = UseTime; + RemainingUseTime = Info.UseTime; } public override void Deuse() @@ -84,7 +62,7 @@ namespace SupaLidlGame.Items { if (box is Hurtbox hurtbox) { - hurtbox.InflictDamage(Damage, Character, Knockback); + hurtbox.InflictDamage(Info.Damage, Character, Info.Knockback); } } } diff --git a/Items/WeaponInfo.cs b/Items/WeaponInfo.cs new file mode 100644 index 0000000..302b6e7 --- /dev/null +++ b/Items/WeaponInfo.cs @@ -0,0 +1,33 @@ +using Godot; + +namespace SupaLidlGame.Items +{ + public partial class WeaponInfo : ItemInfo + { + /// + /// How much damage in HP that this weapon deals. + /// + [Export] + public float Damage { get; set; } = 0; + + /// + /// The time in seconds it takes for this weapon to become available + /// again after using. + /// + [Export] + public double UseTime { get; set; } = 0; + + /// + /// The magnitude of the knockback force of the weapon. + /// + [Export] + public float Knockback { get; set; } = 0; + + /// + /// The initial velocity of any projectile the weapon may spawn. + /// + [Export] + public float InitialVelocity { get; set; } = 0; + + } +} diff --git a/Items/Weapons/Sword.cs b/Items/Weapons/Sword.cs index d618958..3cb8fa3 100644 --- a/Items/Weapons/Sword.cs +++ b/Items/Weapons/Sword.cs @@ -15,16 +15,6 @@ namespace SupaLidlGame.Items.Weapons [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; - [Export] public CpuParticles2D ParryParticles { get; set; } @@ -103,7 +93,7 @@ namespace SupaLidlGame.Items.Weapons public override void _Ready() { - Hitbox.Damage = Damage; + Hitbox.Damage = Info.Damage; } public override void _Process(double delta) @@ -123,7 +113,10 @@ namespace SupaLidlGame.Items.Weapons GD.Print("processing hit"); if (box is Hurtbox hurtbox) { - hurtbox.InflictDamage(Damage, Character, Knockback); + hurtbox.InflictDamage( + Info.Damage, + Character, + Info.Knockback); } } } @@ -165,10 +158,13 @@ namespace SupaLidlGame.Items.Weapons { if (hurt.GetParent() is Character c) { - var item = c.Inventory.SelectedItem; - if (item is Weapon w) + if (c.Inventory.PrimaryItem is Weapon primary) { - AttemptParry(w); + AttemptParry(primary); + } + if (c.Inventory.OffhandItem is Weapon offhand) + { + AttemptParry(offhand); } } } diff --git a/Items/Weapons/Sword.tres b/Items/Weapons/Sword.tres new file mode 100644 index 0000000..430cb43 --- /dev/null +++ b/Items/Weapons/Sword.tres @@ -0,0 +1,17 @@ +[gd_resource type="Resource" load_steps=2 format=3 uid="uid://dbqk11m54e72e"] + +[ext_resource type="Script" path="res://Items/MeleeInfo.cs" id="1_w5yvw"] + +[resource] +script = ExtResource("1_w5yvw") +AttackTime = 0.1 +Damage = 20.0 +UseTime = 0.8 +Knockback = 80.0 +InitialVelocity = 0.0 +ItemName = "Sword" +Description = "A basic sword." +CanStack = false +Count = 1 +IsOneHanded = false +ScenePath = "" diff --git a/Items/Weapons/Sword.tscn b/Items/Weapons/Sword.tscn index 1790005..2c36031 100644 --- a/Items/Weapons/Sword.tscn +++ b/Items/Weapons/Sword.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=20 format=3 uid="uid://d72ehtv1ks0e"] +[gd_scene load_steps=21 format=3 uid="uid://d72ehtv1ks0e"] [ext_resource type="Script" path="res://Items/Weapons/Sword.cs" id="1_mlo73"] +[ext_resource type="Resource" uid="uid://dbqk11m54e72e" path="res://Items/Weapons/Sword.tres" id="2_5vlf6"] [ext_resource type="Texture2D" uid="uid://dt6u8p4h6g7le" path="res://Assets/Sprites/knife.png" id="2_rnfo4"] [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"] @@ -302,13 +303,8 @@ position = Vector2(2, 0) script = ExtResource("1_mlo73") Hitbox = NodePath("Hitbox") AnimationPlayer = NodePath("AnimationPlayer") -AttackTime = 0.1 ParryParticles = NodePath("Anchor/Sprite2D/ParryParticles") -Damage = 20.0 -UseTime = 0.8 -Knockback = 80.0 -ItemName = "Sword" -Description = "A basic sword." +Info = ExtResource("2_5vlf6") [node name="Anchor" type="Node2D" parent="."] y_sort_enabled = true diff --git a/Utils/AudioBuilder.cs b/Utils/AudioBuilder.cs new file mode 100644 index 0000000..62eec7f --- /dev/null +++ b/Utils/AudioBuilder.cs @@ -0,0 +1,14 @@ +using Godot; + +namespace SupaLidlGame.Utils +{ + public class AudioBuilder + { + private AudioStreamPlayer2D _audio; + + public AudioBuilder(AudioStreamPlayer2D baseAudio) + { + _audio = baseAudio; + } + } +} diff --git a/addons/MonoCustomResourceRegistry/Plugin.cs b/addons/MonoCustomResourceRegistry/Plugin.cs new file mode 100644 index 0000000..c185101 --- /dev/null +++ b/addons/MonoCustomResourceRegistry/Plugin.cs @@ -0,0 +1,208 @@ +using Godot; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +// Originally written by wmigor +// Edited by Atlinx to recursively search for files. +// Edited by bls220 to update for Godot 4.0 +// wmigor's Public Repo: https://github.com/wmigor/godot-mono-custom-resource-register +namespace MonoCustomResourceRegistry +{ +#if TOOLS + [Tool] + public partial class Plugin : EditorPlugin + { + // We're not going to hijack the Mono Build button since it actually takes time to build + // and we can't be sure how long that is. I guess we have to leave refreshing to the user for now. + // There isn't any automation we can do to fix that. + // private Button MonoBuildButton => GetNode