diff --git a/Assets/Dialogue/books.dialogue.import b/Assets/Dialogue/books.dialogue.import index a2928b8..fd22018 100644 --- a/Assets/Dialogue/books.dialogue.import +++ b/Assets/Dialogue/books.dialogue.import @@ -1,6 +1,6 @@ [remap] -importer="dialogue_manager_compiler_11" +importer="dialogue_manager_compiler_12" type="Resource" uid="uid://dilmuoilweoeh" path="res://.godot/imported/books.dialogue-cc272ebae322ae3ca46820dca11a3437.tres" diff --git a/Assets/Dialogue/clone-machine.dialogue.import b/Assets/Dialogue/clone-machine.dialogue.import index 70159e2..629926e 100644 --- a/Assets/Dialogue/clone-machine.dialogue.import +++ b/Assets/Dialogue/clone-machine.dialogue.import @@ -1,6 +1,6 @@ [remap] -importer="dialogue_manager_compiler_11" +importer="dialogue_manager_compiler_12" type="Resource" uid="uid://c2om4y0fm81yr" path="res://.godot/imported/clone-machine.dialogue-8810934a67eacdad52469e9ef5f970fb.tres" diff --git a/Assets/Dialogue/doc.dialogue.import b/Assets/Dialogue/doc.dialogue.import index 68f302f..491bd4e 100644 --- a/Assets/Dialogue/doc.dialogue.import +++ b/Assets/Dialogue/doc.dialogue.import @@ -1,6 +1,6 @@ [remap] -importer="dialogue_manager_compiler_11" +importer="dialogue_manager_compiler_12" type="Resource" uid="uid://dntkvjjr8mrgf" path="res://.godot/imported/doc.dialogue-8f95f6a09d3ac685b71d7e07c49df1c6.tres" diff --git a/Assets/Sprites/UI/hotbar-active.ase b/Assets/Sprites/UI/hotbar-active.ase new file mode 100644 index 0000000..76abcee Binary files /dev/null and b/Assets/Sprites/UI/hotbar-active.ase differ diff --git a/Assets/Sprites/UI/hotbar-active.png b/Assets/Sprites/UI/hotbar-active.png new file mode 100644 index 0000000..678ef9f Binary files /dev/null and b/Assets/Sprites/UI/hotbar-active.png differ diff --git a/Assets/Sprites/UI/hotbar-active.png.import b/Assets/Sprites/UI/hotbar-active.png.import new file mode 100644 index 0000000..150ed23 --- /dev/null +++ b/Assets/Sprites/UI/hotbar-active.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dc1gcsbhkchvg" +path="res://.godot/imported/hotbar-active.png-7657e1b4001be05323aa0d0697509f58.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Assets/Sprites/UI/hotbar-active.png" +dest_files=["res://.godot/imported/hotbar-active.png-7657e1b4001be05323aa0d0697509f58.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +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/Assets/Sprites/UI/hotbar-inactive.ase b/Assets/Sprites/UI/hotbar-inactive.ase index cc58d37..f7ff4d9 100644 Binary files a/Assets/Sprites/UI/hotbar-inactive.ase and b/Assets/Sprites/UI/hotbar-inactive.ase differ diff --git a/Assets/Sprites/UI/hotbar-inactive.png b/Assets/Sprites/UI/hotbar-inactive.png index 584cce5..1df834c 100644 Binary files a/Assets/Sprites/UI/hotbar-inactive.png and b/Assets/Sprites/UI/hotbar-inactive.png differ diff --git a/Assets/Sprites/UI/menu-rect-no-bg-white.ase b/Assets/Sprites/UI/menu-rect-no-bg-white.ase new file mode 100644 index 0000000..8062cf4 Binary files /dev/null and b/Assets/Sprites/UI/menu-rect-no-bg-white.ase differ diff --git a/Assets/Sprites/UI/menu-rect-no-bg-white.png b/Assets/Sprites/UI/menu-rect-no-bg-white.png new file mode 100644 index 0000000..d13149c Binary files /dev/null and b/Assets/Sprites/UI/menu-rect-no-bg-white.png differ diff --git a/Assets/Sprites/UI/menu-rect-no-bg-white.png.import b/Assets/Sprites/UI/menu-rect-no-bg-white.png.import new file mode 100644 index 0000000..cdcd8be --- /dev/null +++ b/Assets/Sprites/UI/menu-rect-no-bg-white.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://uhmowtsi3wfh" +path="res://.godot/imported/menu-rect-no-bg-white.png-5ea2d275879af97991070fb370031211.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Assets/Sprites/UI/menu-rect-no-bg-white.png" +dest_files=["res://.godot/imported/menu-rect-no-bg-white.png-5ea2d275879af97991070fb370031211.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +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/Assets/Sprites/forsenLevel.ase b/Assets/Sprites/forsenLevel.ase new file mode 100644 index 0000000..e424a53 Binary files /dev/null and b/Assets/Sprites/forsenLevel.ase differ diff --git a/Assets/Sprites/forsenLevel.png b/Assets/Sprites/forsenLevel.png new file mode 100644 index 0000000..baebf74 Binary files /dev/null and b/Assets/Sprites/forsenLevel.png differ diff --git a/Assets/Sprites/forsenLevel.png.import b/Assets/Sprites/forsenLevel.png.import new file mode 100644 index 0000000..9c8119e --- /dev/null +++ b/Assets/Sprites/forsenLevel.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://vjdyrv8wp7gl" +path="res://.godot/imported/forsenLevel.png-ffd03264deccb9275d087fadbb57e56f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Assets/Sprites/forsenLevel.png" +dest_files=["res://.godot/imported/forsenLevel.png-ffd03264deccb9275d087fadbb57e56f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +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/BoundingBoxes/BoundingBox.cs b/BoundingBoxes/BoundingBox.cs index f1972ef..5c6d377 100644 --- a/BoundingBoxes/BoundingBox.cs +++ b/BoundingBoxes/BoundingBox.cs @@ -5,6 +5,6 @@ namespace SupaLidlGame.BoundingBoxes; public abstract partial class BoundingBox : Area2D, IFaction { - [Export] - public ushort Faction { get; set; } + [Export(PropertyHint.Flags)] + public FactionName Faction { get; set; } } diff --git a/Characters/Character.cs b/Characters/Character.cs index 7a067ea..afd9ff8 100644 --- a/Characters/Character.cs +++ b/Characters/Character.cs @@ -93,8 +93,8 @@ public partial class Character : CharacterBody2D, IFaction [Export] public BoundingBoxes.Hurtbox Hurtbox { get; set; } - [Export] - public ushort Faction { get; set; } + [Export(PropertyHint.Flags)] + public FactionName Faction { get; set; } public AnimationPlayer MovementAnimation { get; set; } diff --git a/Characters/Doc.tscn b/Characters/Doc.tscn index 3643012..6e90a99 100644 --- a/Characters/Doc.tscn +++ b/Characters/Doc.tscn @@ -573,6 +573,7 @@ Sprite = NodePath("Sprite") Inventory = NodePath("Inventory") StateMachine = NodePath("StateMachine") Hurtbox = NodePath("Hurtbox") +Faction = 2 [node name="Stats" type="Node" parent="."] script = ExtResource("5_a7fiw") diff --git a/Characters/NPC.cs b/Characters/NPC.cs index 2b438e7..a5806a3 100644 --- a/Characters/NPC.cs +++ b/Characters/NPC.cs @@ -3,6 +3,7 @@ using Godot; using SupaLidlGame.Extensions; using SupaLidlGame.Items; +using SupaLidlGame.Utils; using System; namespace SupaLidlGame.Characters; @@ -127,6 +128,37 @@ public partial class NPC : Character return bestChar; } + /// + /// Finds the best character whose faction aligns with this character's. + /// + public virtual Character FindBestNeutral() + { + float bestScore = float.MaxValue; + Character bestChar = null; + // NOTE: this relies on all Characters being under the Entities node + foreach (Node node in GetParent().GetChildren()) + { + if (node is Character character) + { + bool isFriendly = ((IFaction)this).AlignsWith(character); + if (isFriendly || character.Health <= 0) + { + continue; + } + + float score = 0; + score -= Position.DistanceTo(character.Position); + + if (score < bestScore) + { + bestScore = score; + bestChar = character; + } + } + } + return bestChar; + } + public override void _Process(double delta) { ThinkerStateMachine.Process(delta); diff --git a/Characters/SnusDealer.tscn b/Characters/SnusDealer.tscn new file mode 100644 index 0000000..569eb06 --- /dev/null +++ b/Characters/SnusDealer.tscn @@ -0,0 +1,74 @@ +[gd_scene load_steps=11 format=3 uid="uid://rd08pot25h00"] + +[ext_resource type="Script" path="res://Characters/NPC.cs" id="1_04gcf"] +[ext_resource type="Script" path="res://State/Character/CharacterStateMachine.cs" id="2_kynkg"] +[ext_resource type="Texture2D" uid="uid://bej8thq7ruyty" path="res://Assets/Sprites/Characters/forsen2.png" id="2_s5nik"] +[ext_resource type="Script" path="res://State/Character/NPCIdleState.cs" id="3_pcrll"] +[ext_resource type="Script" path="res://State/Thinker/ThinkerStateMachine.cs" id="4_mo4wj"] +[ext_resource type="Script" path="res://State/Thinker/VendorIdle.cs" id="5_oau5d"] +[ext_resource type="PackedScene" uid="uid://dldnp8eunxj3q" path="res://BoundingBoxes/InteractionTrigger.tscn" id="5_sjs24"] +[ext_resource type="Script" path="res://Utils/InteractionTriggerDialogue.cs" id="5_yknpw"] +[ext_resource type="Resource" uid="uid://c4n7vhoxybu70" path="res://Dialogue/snus-dealer.dialogue" id="6_isvnq"] +[ext_resource type="Script" path="res://Items/Inventory.cs" id="7_vip6b"] + +[node name="SnusDealer" type="CharacterBody2D" node_paths=PackedStringArray("ThinkerStateMachine", "Sprite", "Inventory", "StateMachine")] +script = ExtResource("1_04gcf") +ThinkerStateMachine = NodePath("Thinker") +Sprite = NodePath("Sprites/Sprite") +Inventory = NodePath("Inventory") +StateMachine = NodePath("StateMachine") + +[node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("InitialState", "Character")] +script = ExtResource("2_kynkg") +InitialState = NodePath("Idle") +Character = NodePath("..") + +[node name="Idle" type="Node" parent="StateMachine" node_paths=PackedStringArray("Character")] +script = ExtResource("3_pcrll") +Character = NodePath("../..") + +[node name="Thinker" type="Node" parent="." node_paths=PackedStringArray("InitialState")] +script = ExtResource("4_mo4wj") +InitialState = NodePath("Idle") + +[node name="Idle" type="Node" parent="Thinker" node_paths=PackedStringArray("NPC")] +script = ExtResource("5_oau5d") +NPC = NodePath("../..") + +[node name="Animations" type="Node" parent="."] + +[node name="Movement" type="AnimationPlayer" parent="Animations"] + +[node name="Hurt" type="AnimationPlayer" parent="Animations"] + +[node name="Stun" type="AnimationPlayer" parent="Animations"] + +[node name="Attack" type="AnimationPlayer" parent="Animations"] + +[node name="Stats" type="Node" parent="."] + +[node name="Sprites" type="Node2D" parent="."] + +[node name="Sprite" type="Sprite2D" parent="Sprites"] +texture = ExtResource("2_s5nik") +centered = false +offset = Vector2(-12, -20) +hframes = 46 + +[node name="Inventory" type="Node2D" parent="." node_paths=PackedStringArray("Hotbar")] +script = ExtResource("7_vip6b") +Hotbar = [] + +[node name="Interaction" type="Node2D" parent="." node_paths=PackedStringArray("InteractionTrigger")] +position = Vector2(0, -4) +script = ExtResource("5_yknpw") +InteractionTrigger = NodePath("InteractionTrigger") +DialogueResource = ExtResource("6_isvnq") +DialogueTitle = "start" + +[node name="InteractionTrigger" parent="Interaction" instance=ExtResource("5_sjs24")] + +[node name="Label" parent="Interaction/InteractionTrigger/Popup" index="0"] +text = "Listen" + +[editable path="Interaction/InteractionTrigger"] diff --git a/Characters/Vendor.cs b/Characters/Vendor.cs new file mode 100644 index 0000000..8487415 --- /dev/null +++ b/Characters/Vendor.cs @@ -0,0 +1,27 @@ +//using Godot; +//using SupaLidlGame.BoundingBoxes; +//using SupaLidlGame.Extensions; +// +//namespace SupaLidlGame.Characters; +// +//public partial class Vendor : NPC +//{ +// [Export] +// public InteractionTrigger InteractionTrigger { get; set; } +// +// [Export(PropertyHint.File, "*.dialogue")] +// public Resource DialogueResource { get; set; } +// +// [Export] +// public string DialogueTitle { get; set; } +// +// public override void _Ready() +// { +// InteractionTrigger.Interaction += OnInteraction; +// } +// +// private void OnInteraction() +// { +// this.GetWorld().DialogueBalloon.Start(DialogueResource, DialogueTitle); +// } +//} diff --git a/Debug/DebugConsole.cs b/Debug/DebugConsole.cs index 580f6b0..659c988 100644 --- a/Debug/DebugConsole.cs +++ b/Debug/DebugConsole.cs @@ -34,6 +34,8 @@ public sealed partial class DebugConsole : Control private Node ctx => Context; + public const double DEBUG_REFRESH_INTERVAL = 0.25; + public override void _Ready() { _entry = GetNode("%Entry"); @@ -61,6 +63,39 @@ public sealed partial class DebugConsole : Control } } }; + + GD.Print("DebugConsole init"); + // TODO: put this in a separate class + // watch godot.log + bool isFileLoggingEnabled = ProjectSettings + .GetSetting("debug/file_logging/enable_file_logging.pc") + .AsBool(); + + if (isFileLoggingEnabled) + { + GD.Print("File logging is enabled."); + string logPath = ProjectSettings + .GetSetting("debug/file_logging/log_path") + .AsString(); + var fs = FileAccess.Open(logPath, FileAccess.ModeFlags.Read); + + var timer = new Timer(); + AddChild(timer); + timer.Timeout += () => + { + // push + while (fs.GetPosition() < fs.GetLength()) + { + string line = fs.GetLine(); + _output.Text += line; + } + }; + timer.Start(DEBUG_REFRESH_INTERVAL); + } + else + { + GD.PushWarning("File logging is not enabled."); + } } public IEnumerable SplitPath(NodePath path) @@ -209,13 +244,21 @@ public sealed partial class DebugConsole : Control Godot.Expression exp = new(); - string[] reserved = { "from", "set_context", "context", "set_prop", "to_node_path" }; + string[] reserved = { + "from", + "set_context", + "context", + "set_prop", + "to_node_path", + "load", + }; Godot.Collections.Array reservedMap = new(); reservedMap.Add(new Callable(this, MethodName.From)); reservedMap.Add(new Callable(this, MethodName.SetContext)); reservedMap.Add(Context); reservedMap.Add(new Callable(this, MethodName.SetProp)); reservedMap.Add(new Callable(this, MethodName.ToNodePath)); + reservedMap.Add(new Callable(this, MethodName.Load)); var err = exp.Parse(str, reserved); if (err != Error.Ok) @@ -246,4 +289,9 @@ public sealed partial class DebugConsole : Control { Context = node; } + + private Resource Load(string path) + { + return ResourceLoader.Load(path); + } } diff --git a/Dialogue/snus-dealer.dialogue b/Dialogue/snus-dealer.dialogue new file mode 100644 index 0000000..8572f45 --- /dev/null +++ b/Dialogue/snus-dealer.dialogue @@ -0,0 +1,23 @@ +~ start + +Snus Dealer: Hey kid, wanna buy some snus? +- Alright. => shop +- No, I don't think so. => END +- Snus? => dont_snus + +=> END + +~ dont_snus + +Snus Dealer: You know what they say. +Snus Dealer: If you don't snus... +Snus Dealer: you lose. +- Pepepains + +=> start + +~ shop + +do emit("EnterShop", "res://Items/Shops/SnusDealer.tres") + +=> END diff --git a/Dialogue/snus-dealer.dialogue.import b/Dialogue/snus-dealer.dialogue.import new file mode 100644 index 0000000..bc5cd9b --- /dev/null +++ b/Dialogue/snus-dealer.dialogue.import @@ -0,0 +1,15 @@ +[remap] + +importer="dialogue_manager_compiler_12" +type="Resource" +uid="uid://c4n7vhoxybu70" +path="res://.godot/imported/snus-dealer.dialogue-69dbddee28632f18888364bae03f393d.tres" + +[deps] + +source_file="res://Dialogue/snus-dealer.dialogue" +dest_files=["res://.godot/imported/snus-dealer.dialogue-69dbddee28632f18888364bae03f393d.tres"] + +[params] + +defaults=true diff --git a/Entities/DynamicDoor.cs b/Entities/DynamicDoor.cs index 7670eb8..38661e7 100644 --- a/Entities/DynamicDoor.cs +++ b/Entities/DynamicDoor.cs @@ -58,7 +58,7 @@ public partial class DynamicDoor : StaticBody2D foreach (var navmesh in Rebake) { // rebake navmesh so NPCs can correctly travel conditionally - GD.Print("rebaking"); + GD.Print("Dynamic door updated; rebaking navmeshes..."); navmesh.BakeNavigationPolygon(); } } diff --git a/Entities/TorchLamp.tscn b/Entities/TorchLamp.tscn index 11df0f7..d858e18 100644 --- a/Entities/TorchLamp.tscn +++ b/Entities/TorchLamp.tscn @@ -102,12 +102,12 @@ y_sort_enabled = true texture_filter = 1 sprite_frames = SubResource("SpriteFrames_gf7ku") autoplay = "default" -frame = 6 -frame_progress = 0.743234 +frame = 9 +frame_progress = 0.966501 offset = Vector2(0, -12) [node name="PointLight2D" type="PointLight2D" parent="."] -color = Color(1, 0.9525, 0.85, 1) +color = Color(1, 0.811765, 0.537255, 1) energy = 1.2 blend_mode = 2 shadow_filter_smooth = 3.0 diff --git a/Events/EventBus.cs b/Events/EventBus.cs index 9aa4885..e158812 100644 --- a/Events/EventBus.cs +++ b/Events/EventBus.cs @@ -51,6 +51,9 @@ public partial class EventBus : Node [Signal] public delegate void ExitTransitionEventHandler(); + [Signal] + public delegate void EnterShopEventHandler(string shopResourcePath); + public override void _Ready() { ProcessMode = ProcessModeEnum.Always; diff --git a/Extensions/Timer.cs b/Extensions/Timer.cs new file mode 100644 index 0000000..bac4566 --- /dev/null +++ b/Extensions/Timer.cs @@ -0,0 +1,12 @@ +using Godot; + +namespace SupaLidlGame.Extensions; + +public static class TimerExtensions +{ + public static void Restart(this Timer timer, double timeSec = -1) + { + timer.Stop(); + timer.Start(timeSec); + } +} diff --git a/Items/IItemCollection.cs b/Items/IItemCollection.cs new file mode 100644 index 0000000..c715fb9 --- /dev/null +++ b/Items/IItemCollection.cs @@ -0,0 +1,15 @@ +namespace SupaLidlGame.Items; + +public interface IItemCollection +{ + public System.Collections.Generic.IEnumerable GetItems(); + + public int Capacity { get; } +} + +public interface IItemCollection : IItemCollection +{ + public bool Add(T item); + + public bool Remove(T item); +} diff --git a/Items/Inventory.cs b/Items/Inventory.cs index 772d6e4..80f286c 100644 --- a/Items/Inventory.cs +++ b/Items/Inventory.cs @@ -4,7 +4,7 @@ using Godot.Collections; namespace SupaLidlGame.Items; -public partial class Inventory : Node2D +public partial class Inventory : Node2D, IItemCollection { public Character Character { get; private set; } @@ -20,7 +20,15 @@ public partial class Inventory : Node2D [Signal] public delegate void UsedItemEventHandler(Item item); - public const int MaxCapacity = 3; + [Signal] + public delegate void EquippedItemEventHandler(Item newItem, Item prevItem); + + [Signal] + public delegate void ItemAddedEventHandler(ItemMetadata newItemMetadata); + + public int Capacity { get; set; } = 30; + + public const int HotbarCapacity = 3; private Item _selectedItem; @@ -87,7 +95,8 @@ public partial class Inventory : Node2D return false; } - _selectedItem?.Unequip(Character); + Item prevItem = _selectedItem; + prevItem?.Unequip(Character); _selectedIndex = index; if (index >= 0) @@ -100,6 +109,8 @@ public partial class Inventory : Node2D _selectedItem = null; } + EmitSignal(SignalName.EquippedItem, prevItem, _selectedItem); + GD.Print($"Inventory: {index} is new selected index."); return true; @@ -135,8 +146,9 @@ public partial class Inventory : Node2D return null; } - public Item AddItemToHotbar(ItemMetadata metadata) + public Item AddToHotbar(ItemMetadata metadata) { + //AddItemMetadata(metadata); var item = metadata.Instance.Instantiate(); AddItem(item); AddChild(item); @@ -146,7 +158,7 @@ public partial class Inventory : Node2D public Item AddItem(Item item) { - if (Hotbar.Count >= MaxCapacity) + if (Hotbar.Count >= HotbarCapacity) { return null; } @@ -157,9 +169,32 @@ public partial class Inventory : Node2D { Hotbar.Add(item); } + return item; } + public System.Collections.Generic.IEnumerable GetItems() + { + return Items; + } + + public bool Add(ItemMetadata item) + { + if (Items.Count >= Capacity) + { + return false; + } + + Items.Add(item); + EmitSignal(SignalName.ItemAdded, item); + return true; + } + + public bool Remove(ItemMetadata item) + { + return Items.Remove(item); + } + public Item DropItem(Item item) { item.CharacterOwner = null; diff --git a/Items/Shop.cs b/Items/Shop.cs new file mode 100644 index 0000000..4ba249a --- /dev/null +++ b/Items/Shop.cs @@ -0,0 +1,45 @@ +using Godot; +using Godot.Collections; +using GodotUtilities.Collections; +using System.Linq; +using SupaLidlGame.Utils; + +namespace SupaLidlGame.Items; + +[GlobalClass] +public partial class Shop : Resource, IItemCollection +{ + [Export] + protected Godot.Collections.Array Entries { get; private set; } + + public System.Collections.Generic.IEnumerable GetItems() + { + var mapState = World.Instance.GlobalState.MapState; + return Entries + .Where( + (entry) => + { + var condition = entry.MapStateCondition; + if (string.IsNullOrEmpty(condition)) + { + return true; + } + return mapState.GetBoolean(condition) ?? false; + } + ) + .Select((entry) => entry.Item); + } + + public bool Add(ShopEntry entry) + { + Entries.Add(entry); + return true; + } + + public bool Remove(ShopEntry entry) + { + return Entries.Remove(entry); + } + + public int Capacity => Entries.Count; +} diff --git a/Items/ShopEntry.cs b/Items/ShopEntry.cs new file mode 100644 index 0000000..b5617ad --- /dev/null +++ b/Items/ShopEntry.cs @@ -0,0 +1,23 @@ +using Godot; + +namespace SupaLidlGame.Items; + +[GlobalClass] +public partial class ShopEntry : Resource +{ + public ShopEntry() : base() + { + + } + + public ShopEntry(ItemMetadata item) : base() + { + Item = item; + } + + [Export] + public ItemMetadata Item { get; set; } + + [Export] + public string MapStateCondition { get; set; } +} diff --git a/Items/Shops/SnusDealer.tres b/Items/Shops/SnusDealer.tres new file mode 100644 index 0000000..ed8d52d --- /dev/null +++ b/Items/Shops/SnusDealer.tres @@ -0,0 +1,54 @@ +[gd_resource type="Resource" script_class="Shop" load_steps=16 format=3 uid="uid://djqd88vdkoi6d"] + +[ext_resource type="Script" path="res://Items/Shop.cs" id="1_betbc"] +[ext_resource type="Resource" uid="uid://cjsh0dcgbfn77" path="res://Items/Weapons/Bow.tres" id="1_ntroj"] +[ext_resource type="Script" path="res://Items/ShopEntry.cs" id="2_xgvwu"] +[ext_resource type="Resource" uid="uid://iqe6rgnb3jur" path="res://Items/Weapons/Pugio.tres" id="3_nfeft"] +[ext_resource type="Resource" uid="uid://dkm216ug0vj2h" path="res://Items/Weapons/Shotgun.tres" id="4_aw0ju"] + +[sub_resource type="Resource" id="Resource_jdx0p"] +script = ExtResource("2_xgvwu") +Item = ExtResource("1_ntroj") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_min4b"] +script = ExtResource("2_xgvwu") +Item = ExtResource("3_nfeft") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_t0x08"] +script = ExtResource("2_xgvwu") +Item = ExtResource("4_aw0ju") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_exmab"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_jlgb3"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_8rd47"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_pqgh5"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_8mift"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_4e8it"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_jp7ms"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[resource] +script = ExtResource("1_betbc") +Entries = Array[Object]([SubResource("Resource_jdx0p"), SubResource("Resource_min4b"), SubResource("Resource_t0x08"), SubResource("Resource_exmab"), SubResource("Resource_jlgb3"), SubResource("Resource_8rd47"), SubResource("Resource_pqgh5"), SubResource("Resource_8mift"), SubResource("Resource_4e8it"), SubResource("Resource_jp7ms")]) diff --git a/Items/Weapons/Bow.tres b/Items/Weapons/Bow.tres index 7e4edc9..5f2a8b1 100644 --- a/Items/Weapons/Bow.tres +++ b/Items/Weapons/Bow.tres @@ -1,5 +1,6 @@ -[gd_resource type="Resource" script_class="ItemMetadata" load_steps=4 format=3 uid="uid://cjsh0dcgbfn77"] +[gd_resource type="Resource" script_class="ItemMetadata" load_steps=5 format=3 uid="uid://cjsh0dcgbfn77"] +[ext_resource type="Texture2D" uid="uid://vjdyrv8wp7gl" path="res://Assets/Sprites/forsenLevel.png" id="1_5blro"] [ext_resource type="Script" path="res://Utils/ScenePath.cs" id="1_haiji"] [ext_resource type="Script" path="res://Items/ItemMetadata.cs" id="2_hjbs0"] @@ -10,6 +11,7 @@ Path = "res://Items/Weapons/Bow.tscn" [resource] script = ExtResource("2_hjbs0") Instance = SubResource("Resource_mjj1w") +Icon = ExtResource("1_5blro") Name = "Bow" Description = "A bow and arrow." BuyPrice = 0 diff --git a/Items/Weapons/ProjectileSpawner.cs b/Items/Weapons/ProjectileSpawner.cs index 6362899..33d5ad2 100644 --- a/Items/Weapons/ProjectileSpawner.cs +++ b/Items/Weapons/ProjectileSpawner.cs @@ -24,6 +24,15 @@ public partial class ProjectileSpawner : Ranged [Export] public float ProjectileAngleDeviation { get; set; } + public string ProjectilePath + { + get => Projectile?.ResourcePath; + set + { + Projectile = GD.Load(value); + } + } + protected virtual void SpawnProjectile(Scenes.Map map, Vector2 direction, float velocityModifier = 1) { diff --git a/Items/Weapons/Pugio.tres b/Items/Weapons/Pugio.tres index eee0522..a141748 100644 --- a/Items/Weapons/Pugio.tres +++ b/Items/Weapons/Pugio.tres @@ -1,5 +1,6 @@ -[gd_resource type="Resource" script_class="ItemMetadata" load_steps=4 format=3 uid="uid://iqe6rgnb3jur"] +[gd_resource type="Resource" script_class="ItemMetadata" load_steps=5 format=3 uid="uid://iqe6rgnb3jur"] +[ext_resource type="Texture2D" uid="uid://vjdyrv8wp7gl" path="res://Assets/Sprites/forsenLevel.png" id="1_3ptey"] [ext_resource type="Script" path="res://Utils/ScenePath.cs" id="1_o026a"] [ext_resource type="Script" path="res://Items/ItemMetadata.cs" id="2_j4tmu"] @@ -10,6 +11,7 @@ Path = "res://Items/Weapons/Pugio.tscn" [resource] script = ExtResource("2_j4tmu") Instance = SubResource("Resource_abrg1") +Icon = ExtResource("1_3ptey") Name = "Pugio" Description = "A sidearm dagger." BuyPrice = 0 diff --git a/Items/Weapons/Shotgun.tres b/Items/Weapons/Shotgun.tres new file mode 100644 index 0000000..651fe91 --- /dev/null +++ b/Items/Weapons/Shotgun.tres @@ -0,0 +1,18 @@ +[gd_resource type="Resource" script_class="ItemMetadata" load_steps=5 format=3 uid="uid://dkm216ug0vj2h"] + +[ext_resource type="Script" path="res://Utils/ScenePath.cs" id="1_owc5r"] +[ext_resource type="Texture2D" uid="uid://vjdyrv8wp7gl" path="res://Assets/Sprites/forsenLevel.png" id="1_v76vk"] +[ext_resource type="Script" path="res://Items/ItemMetadata.cs" id="1_vtlr1"] + +[sub_resource type="Resource" id="Resource_6sxq7"] +script = ExtResource("1_owc5r") +Path = "res://Items/Weapons/Shotgun.tscn" + +[resource] +script = ExtResource("1_vtlr1") +Instance = SubResource("Resource_6sxq7") +Icon = ExtResource("1_v76vk") +Name = "Shotgun" +Description = "" +BuyPrice = 1887 +SellPrice = 0 diff --git a/Items/Weapons/Shotgun.tscn b/Items/Weapons/Shotgun.tscn index 1f84f6d..a7beaf0 100644 --- a/Items/Weapons/Shotgun.tscn +++ b/Items/Weapons/Shotgun.tscn @@ -1,9 +1,10 @@ -[gd_scene load_steps=26 format=3 uid="uid://d1d4vg7we5rjr"] +[gd_scene load_steps=27 format=3 uid="uid://d1d4vg7we5rjr"] [ext_resource type="Script" path="res://Items/Weapons/ProjectileSpawner.cs" id="1_4xyyt"] [ext_resource type="Script" path="res://State/Weapon/WeaponStateMachine.cs" id="2_ag6rd"] [ext_resource type="PackedScene" uid="uid://da1do2r2pbayb" path="res://Entities/ShotgunPellet.tscn" id="2_p3wx2"] [ext_resource type="Script" path="res://State/Weapon/RangedIdleState.cs" id="3_dd6bh"] +[ext_resource type="Resource" uid="uid://dkm216ug0vj2h" path="res://Items/Weapons/Shotgun.tres" id="3_ju036"] [ext_resource type="Script" path="res://State/Weapon/RangedFireState.cs" id="4_bwqd6"] [ext_resource type="Texture2D" uid="uid://b1omx2kdb2x1n" path="res://Assets/Sprites/Items/shotgun.png" id="5_g8d45"] [ext_resource type="Texture2D" uid="uid://c1a7lvb4uuwfy" path="res://Assets/Sprites/Particles/circle-16.png" id="6_va8ee"] @@ -216,11 +217,9 @@ curve = SubResource("Curve_mqgo6") [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_uexo4"] particle_flag_disable_z = true flatness = 1.0 -gravity = Vector3(0, -4, 0) initial_velocity_min = -8.0 initial_velocity_max = 24.0 -orbit_velocity_min = 0.0 -orbit_velocity_max = 0.0 +gravity = Vector3(0, -4, 0) scale_min = 0.75 scale_curve = SubResource("CurveTexture_3omj5") color_ramp = SubResource("GradientTexture1D_83h4s") @@ -236,13 +235,11 @@ gradient = SubResource("Gradient_jks7f") particle_flag_disable_z = true direction = Vector3(-1, -2, 0) spread = 10.0 -gravity = Vector3(0, 64, 0) initial_velocity_min = 16.0 initial_velocity_max = 32.0 angular_velocity_min = 30.0 angular_velocity_max = 60.0 -orbit_velocity_min = 0.0 -orbit_velocity_max = 0.0 +gravity = Vector3(0, 64, 0) scale_min = 2.0 scale_max = 2.0 color_ramp = SubResource("GradientTexture1D_eeskk") @@ -259,6 +256,7 @@ Damage = 12.0 UseTime = 1.5 InitialVelocity = 220.0 PlayerLevelGain = 0.5 +Metadata = ExtResource("3_ju036") [node name="State" type="Node" parent="." node_paths=PackedStringArray("InitialState")] script = ExtResource("2_ag6rd") diff --git a/Scenes/Level.tscn b/Scenes/Level.tscn index 09ca743..72cdf09 100644 --- a/Scenes/Level.tscn +++ b/Scenes/Level.tscn @@ -3,11 +3,10 @@ [ext_resource type="Script" path="res://Utils/World.cs" id="1_1k6ew"] [ext_resource type="PackedScene" uid="uid://c271rdjhd1gfo" path="res://UI/Base.tscn" id="2_mm0qt"] -[node name="World" type="Node2D" node_paths=PackedStringArray("MusicPlayer", "DialogueBalloon")] +[node name="World" type="Node2D" node_paths=PackedStringArray("MusicPlayer")] process_mode = 3 script = ExtResource("1_1k6ew") MusicPlayer = NodePath("MusicPlayer") -DialogueBalloon = NodePath("CanvasLayer/SubViewportContainer/UIViewport/DialogBalloon") [node name="CanvasLayer" parent="." instance=ExtResource("2_mm0qt")] @@ -21,6 +20,7 @@ anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +mouse_filter = 2 [node name="MusicPlayer" type="AudioStreamPlayer" parent="."] bus = &"Music" diff --git a/Scenes/Maps/ForestNew.tscn b/Scenes/Maps/ForestNew.tscn index 369dba3..102854a 100644 --- a/Scenes/Maps/ForestNew.tscn +++ b/Scenes/Maps/ForestNew.tscn @@ -843,7 +843,7 @@ position = Vector2(1272, 0) [node name="Campfire" parent="Entities" index="0" instance=ExtResource("3_ve4i2")] position = Vector2(-24, -8) -[node name="Areas" parent="." index="3"] +[node name="Areas" parent="." index="10"] visible = false [node name="Main" type="NavigationRegion2D" parent="Areas" index="0"] diff --git a/State/Global/GlobalState.cs b/State/Global/GlobalState.cs index a4fe117..8c56b17 100644 --- a/State/Global/GlobalState.cs +++ b/State/Global/GlobalState.cs @@ -14,6 +14,8 @@ public partial class GlobalState : Node [Export] public Stats Stats { get; set; } + public static GlobalState Instance { get; private set; } + [Export] public GameSettings Settings { get; set; } @@ -30,6 +32,13 @@ public partial class GlobalState : Node public override void _Ready() { + if (Instance != null) + { + throw new MultipleSingletonsException(); + } + + Instance = this; + ProcessMode = ProcessModeEnum.Always; LoadSettings(); } diff --git a/State/Global/MapState.cs b/State/Global/MapState.cs index 4c3824b..6b8bf71 100644 --- a/State/Global/MapState.cs +++ b/State/Global/MapState.cs @@ -45,4 +45,13 @@ public partial class MapState : Resource } } } + + public bool? GetBoolean(string key) + { + if (_state[key].VariantType == Variant.Type.Bool) + { + return (bool)_state[key]; + } + return null; + } } diff --git a/State/Thinker/VendorIdle.cs b/State/Thinker/VendorIdle.cs new file mode 100644 index 0000000..d0154f6 --- /dev/null +++ b/State/Thinker/VendorIdle.cs @@ -0,0 +1,17 @@ +using Godot; +using GodotUtilities; + +namespace SupaLidlGame.State.Thinker; + +public partial class VendorIdle : ThinkerState +{ + public override ThinkerState Think() + { + var bestNeutral = NPC.FindBestNeutral(); + if (bestNeutral is not null) + { + NPC.Target = bestNeutral.Position - NPC.Position; + } + return null; + } +} diff --git a/UI/Base.tscn b/UI/Base.tscn index c9699d4..823a282 100644 --- a/UI/Base.tscn +++ b/UI/Base.tscn @@ -1,18 +1,28 @@ -[gd_scene load_steps=14 format=3 uid="uid://c271rdjhd1gfo"] +[gd_scene load_steps=18 format=3 uid="uid://c271rdjhd1gfo"] -[ext_resource type="PackedScene" uid="uid://73jm5qjy52vq" path="res://Dialogue/balloon.tscn" id="1_atjb1"] [ext_resource type="Script" path="res://UI/UIController.cs" id="2_b4b6l"] [ext_resource type="PackedScene" uid="uid://bxo553hblp6nf" path="res://UI/HealthBar.tscn" id="3_j1j6h"] [ext_resource type="PackedScene" uid="uid://01d24ij5av1y" path="res://UI/BossBar.tscn" id="4_igi28"] [ext_resource type="PackedScene" uid="uid://cr7tkxctmyags" path="res://UI/LevelBar.tscn" id="4_rcekd"] [ext_resource type="PackedScene" uid="uid://c77754nvmckn" path="res://UI/LocationDisplay.tscn" id="5_cr6vo"] -[ext_resource type="PackedScene" uid="uid://sfs8dpfitpdu" path="res://UI/Hotbar.tscn" id="5_mmp18"] +[ext_resource type="PackedScene" uid="uid://sfs8dpfitpdu" path="res://UI/Inventory/Hotbar.tscn" id="5_mmp18"] [ext_resource type="PackedScene" uid="uid://d3q1yu3n7cqfj" path="res://UI/SceneTransition.tscn" id="6_j0nhv"] +[ext_resource type="PackedScene" uid="uid://cyggkyqosjk36" path="res://UI/Inventory/ShopMenu.tscn" id="8_ep3ae"] [ext_resource type="PackedScene" uid="uid://2afbrf8asy2a" path="res://UI/PostProcessing/Vignette.tscn" id="9_p1ubd"] [ext_resource type="PackedScene" uid="uid://b1wsryv4bn0cn" path="res://UI/PostProcessing/StunEffect.tscn" id="10_646ma"] [ext_resource type="Shader" path="res://Shaders/Grayscale.gdshader" id="11_w4gn1"] +[ext_resource type="Theme" uid="uid://cksjbu3vrup5" path="res://UI/Themes/supalidl.tres" id="11_wtdum"] [ext_resource type="Texture2D" uid="uid://bw052v8ikfget" path="res://icon.svg" id="12_tyv35"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_a504f"] +bg_color = Color(0.906763, 0, 0.280984, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gheod"] +bg_color = Color(1, 0.315507, 0.447521, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1nm23"] +bg_color = Color(0.951511, 0.387714, 0.416256, 1) + [sub_resource type="ShaderMaterial" id="ShaderMaterial_kbd61"] shader = ExtResource("11_w4gn1") shader_parameter/grayscale_intensity = 0.0 @@ -20,6 +30,35 @@ shader_parameter/grayscale_intensity = 0.0 [node name="BaseUI" type="CanvasLayer"] process_mode = 3 +[node name="Button" type="Button" parent="."] +offset_left = 536.0 +offset_right = 592.0 +offset_bottom = 31.0 +theme_override_styles/focus = SubResource("StyleBoxFlat_a504f") +theme_override_styles/hover = SubResource("StyleBoxFlat_gheod") +theme_override_styles/normal = SubResource("StyleBoxFlat_1nm23") +text = "sdfsdf" + +[node name="PostProcessing" type="CanvasLayer" parent="."] + +[node name="Vignette" parent="PostProcessing" instance=ExtResource("9_p1ubd")] + +[node name="StunEffect" parent="PostProcessing" instance=ExtResource("10_646ma")] + +[node name="Sprite2D" type="TextureRect" parent="PostProcessing"] +visible = false +material = SubResource("ShaderMaterial_kbd61") +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -128.0 +offset_top = -128.0 +grow_horizontal = 0 +grow_vertical = 0 +texture = ExtResource("12_tyv35") + [node name="SubViewportContainer" type="SubViewportContainer" parent="."] anchors_preset = 15 anchor_right = 1.0 @@ -36,9 +75,6 @@ handle_input_locally = false size = Vector2i(640, 360) render_target_update_mode = 4 -[node name="DialogBalloon" parent="SubViewportContainer/UIViewport" instance=ExtResource("1_atjb1")] -layer = 4 - [node name="MainUILayer" type="CanvasLayer" parent="SubViewportContainer/UIViewport"] layer = 3 @@ -52,6 +88,7 @@ grow_horizontal = 2 grow_vertical = 2 size_flags_horizontal = 3 size_flags_vertical = 3 +mouse_filter = 2 script = ExtResource("2_b4b6l") [node name="Top" type="HBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main"] @@ -78,7 +115,6 @@ layout_mode = 2 layout_mode = 2 [node name="Margin2" type="MarginContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Left/VBoxContainer"] -visible = false layout_mode = 2 theme_override_constants/margin_left = 16 theme_override_constants/margin_top = 16 @@ -92,6 +128,21 @@ theme_override_constants/margin_right = 16 layout_mode = 2 _slots = [NodePath("InventorySlot"), NodePath("InventorySlot2"), NodePath("InventorySlot3")] +[node name="BoxContainer" type="HBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main"] +layout_mode = 1 +anchors_preset = 11 +anchor_left = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -236.0 +grow_horizontal = 0 +grow_vertical = 2 +alignment = 2 + +[node name="Panel" parent="SubViewportContainer/UIViewport/MainUILayer/Main/BoxContainer" node_paths=PackedStringArray("_inventoryGrid") instance=ExtResource("8_ep3ae")] +layout_mode = 2 +_inventoryGrid = NodePath("PanelContainer/VBoxContainer/ScrollContainer/InventoryGrid") + [node name="Bottom" type="HBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main"] layout_mode = 1 anchors_preset = 12 @@ -107,27 +158,20 @@ alignment = 1 visible = false layout_mode = 2 +[node name="Button" type="Button" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Bottom"] +layout_mode = 2 +theme = ExtResource("11_wtdum") +theme_type_variation = &"InventorySlotButton" +theme_override_styles/hover = SubResource("StyleBoxFlat_gheod") +text = "sdfsdfa" + +[node name="VBoxContainer" type="VBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main"] +visible = false +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 40.0 + [node name="LocationDisplay" parent="SubViewportContainer/UIViewport/MainUILayer" instance=ExtResource("5_cr6vo")] [node name="SceneTransition" parent="SubViewportContainer/UIViewport/MainUILayer" instance=ExtResource("6_j0nhv")] z_index = 1 - -[node name="PostProcessing" type="CanvasLayer" parent="."] - -[node name="Vignette" parent="PostProcessing" instance=ExtResource("9_p1ubd")] - -[node name="StunEffect" parent="PostProcessing" instance=ExtResource("10_646ma")] - -[node name="Sprite2D" type="TextureRect" parent="PostProcessing"] -visible = false -material = SubResource("ShaderMaterial_kbd61") -anchors_preset = 3 -anchor_left = 1.0 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = -128.0 -offset_top = -128.0 -grow_horizontal = 0 -grow_vertical = 0 -texture = ExtResource("12_tyv35") diff --git a/UI/Hotbar.tscn b/UI/Hotbar.tscn deleted file mode 100644 index 371c5c6..0000000 --- a/UI/Hotbar.tscn +++ /dev/null @@ -1,25 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://sfs8dpfitpdu"] - -[ext_resource type="Script" path="res://UI/Hotbar.cs" id="1_2sak2"] -[ext_resource type="PackedScene" uid="uid://ctad0dkoyw8ad" path="res://UI/InventorySlot.tscn" id="1_ct3cn"] - -[node name="Hotbar" type="GridContainer" node_paths=PackedStringArray("_slots")] -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -112.0 -offset_bottom = 32.0 -grow_horizontal = 0 -theme_override_constants/h_separation = 8 -columns = 3 -script = ExtResource("1_2sak2") -_slots = [NodePath("InventorySlot"), NodePath("InventorySlot2"), NodePath("InventorySlot3")] - -[node name="InventorySlot" parent="." instance=ExtResource("1_ct3cn")] -layout_mode = 2 - -[node name="InventorySlot2" parent="." instance=ExtResource("1_ct3cn")] -layout_mode = 2 - -[node name="InventorySlot3" parent="." instance=ExtResource("1_ct3cn")] -layout_mode = 2 diff --git a/UI/Hotbar.cs b/UI/Inventory/Hotbar.cs similarity index 83% rename from UI/Hotbar.cs rename to UI/Inventory/Hotbar.cs index 5b311da..afbf0c4 100644 --- a/UI/Hotbar.cs +++ b/UI/Inventory/Hotbar.cs @@ -1,7 +1,6 @@ using Godot; -using SupaLidlGame.Items; -namespace SupaLidlGame.UI; +namespace SupaLidlGame.UI.Inventory; public partial class Hotbar : GridContainer { @@ -13,7 +12,7 @@ public partial class Hotbar : GridContainer Events.EventBus.Instance.PlayerInventoryUpdate += OnInventoryUpdate; } - public void OnInventoryUpdate(Inventory inventory) + public void OnInventoryUpdate(Items.Inventory inventory) { GD.Print($"UPDATE: {inventory.SelectedIndex} is selected index."); for (int i = 0; i < 3; i++) diff --git a/UI/Inventory/Hotbar.tscn b/UI/Inventory/Hotbar.tscn new file mode 100644 index 0000000..2ef8600 --- /dev/null +++ b/UI/Inventory/Hotbar.tscn @@ -0,0 +1,30 @@ +[gd_scene load_steps=4 format=3 uid="uid://sfs8dpfitpdu"] + +[ext_resource type="Script" path="res://UI/Inventory/Hotbar.cs" id="1_2sak2"] +[ext_resource type="PackedScene" uid="uid://dmvu2hjyrwc1y" path="res://UI/Inventory/HotbarSlot.tscn" id="2_3axfe"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6jbma"] + +[node name="Hotbar" type="GridContainer" node_paths=PackedStringArray("_slots")] +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -112.0 +offset_bottom = 32.0 +grow_horizontal = 0 +theme_override_constants/h_separation = 8 +columns = 3 +script = ExtResource("1_2sak2") +_slots = [NodePath("InventorySlot"), NodePath("InventorySlot2"), NodePath("InventorySlot3")] + +[node name="InventorySlot" parent="." instance=ExtResource("2_3axfe")] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxEmpty_6jbma") + +[node name="InventorySlot2" parent="." instance=ExtResource("2_3axfe")] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxEmpty_6jbma") + +[node name="InventorySlot3" parent="." instance=ExtResource("2_3axfe")] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxEmpty_6jbma") diff --git a/UI/Inventory/HotbarSlot.cs b/UI/Inventory/HotbarSlot.cs new file mode 100644 index 0000000..e69de29 diff --git a/UI/Inventory/HotbarSlot.tscn b/UI/Inventory/HotbarSlot.tscn new file mode 100644 index 0000000..ba63588 --- /dev/null +++ b/UI/Inventory/HotbarSlot.tscn @@ -0,0 +1,16 @@ +[gd_scene load_steps=4 format=3 uid="uid://dmvu2hjyrwc1y"] + +[ext_resource type="PackedScene" uid="uid://ctad0dkoyw8ad" path="res://UI/Inventory/InventorySlot.tscn" id="1_fb62b"] +[ext_resource type="Texture2D" uid="uid://dp7osg05ip5oo" path="res://Assets/Sprites/sword.png" id="2_aqbyo"] +[ext_resource type="Texture2D" uid="uid://dc1gcsbhkchvg" path="res://Assets/Sprites/UI/hotbar-active.png" id="2_bcv71"] + +[node name="InventorySlot" instance=ExtResource("1_fb62b")] + +[node name="TextureRect" parent="." index="0"] +texture = ExtResource("2_aqbyo") +expand_mode = 3 + +[node name="SelectedFrame" type="NinePatchRect" parent="." index="2"] +visible = false +layout_mode = 2 +texture = ExtResource("2_bcv71") diff --git a/UI/Inventory/InventoryGrid.cs b/UI/Inventory/InventoryGrid.cs new file mode 100644 index 0000000..124b536 --- /dev/null +++ b/UI/Inventory/InventoryGrid.cs @@ -0,0 +1,172 @@ +using Godot; +using GodotUtilities; +using SupaLidlGame.Items; + +namespace SupaLidlGame.UI.Inventory; + +public partial class InventoryGrid : GridContainer +{ + private SupaLidlGame.Items.IItemCollection _source; + + [Export] + private PackedScene _slotScene; + + public ButtonGroup ButtonGroup { get; private set; } + + public SupaLidlGame.Items.IItemCollection Source + { + get => _source; + set + { + GD.Print("Set InventoryGrid source"); + _source = value; + Redraw(); + } + } + + [Signal] + public delegate void SlotFocusedEventHandler(InventorySlot slot); + + [Signal] + public delegate void SlotUnfocusedEventHandler(InventorySlot slot); + + [Signal] + public delegate void SlotSelectedEventHandler(InventorySlot slot); + + public InventoryGrid() + { + ButtonGroup = new(); + } + + public void Redraw() + { + GD.Print("Redrawing inventory grid..."); + + if (_source is null) + { + this.QueueFreeChildren(); + } + + var children = GetChildren(); + + for (int i = children.Count; i < _source.Capacity; i++) + { + AddInventorySlot(); + } + + for (int i = children.Count - 1; i >= _source.Capacity; i--) + { + children[i].QueueFree(); + } + + children = GetChildren(); + + // iterate through items and update the grid + using (var items = _source.GetItems().GetEnumerator()) + { + GD.Print("Updating items..."); + int i; + for (i = 0; items.MoveNext(); i++) + { + InventorySlot slot = children[i] as InventorySlot; + + ItemMetadata item = items.Current; + slot.Item = item; + } + + // make remaining slots display empty + for (; i < _source.Capacity; i++) + { + InventorySlot slot = children[i] as InventorySlot; + + slot.Item = null; + } + } + + for (int i = 0; i < children.Count; i++) + { + var child = children[i] as Control; + if (i > 0) + { + child.FocusPrevious = child.GetPathTo(children[i - 1]); + } + if (i < children.Count - 1) + { + child.FocusNext = child.GetPathTo(children[i + 1]); + } + } + + if (children.Count > 0) + { + var button = children[0] as Button; + button.ButtonPressed = true; + button.GrabFocus(); + } + } + + private InventorySlot AddInventorySlot() + { + var slot = _slotScene.Instantiate(); + AddChild(slot); + slot.ButtonGroup = ButtonGroup; + + void focusedHandler() + { + EmitSignal(SignalName.SlotFocused, slot); + } + + void unfocusedHandler() + { + EmitSignal(SignalName.SlotUnfocused, slot); + } + + void toggledHandler() + { + EmitSignal(SignalName.SlotSelected, slot); + } + + slot.Connect( + InventorySlot.SignalName.FocusEntered, + Callable.From(focusedHandler) + ); + + slot.Connect( + InventorySlot.SignalName.FocusExited, + Callable.From(unfocusedHandler) + ); + + slot.Connect( + InventorySlot.SignalName.MouseEntered, + Callable.From(focusedHandler) + ); + + slot.Connect( + InventorySlot.SignalName.MouseExited, + Callable.From(unfocusedHandler) + ); + + slot.Connect( + InventorySlot.SignalName.Pressed, + Callable.From(toggledHandler) + ); + + return slot; + } + + private void RemoveInventorySlot(InventorySlot slot) + { + RemoveChild(slot); + } + + public bool GrabSlotFocus() + { + var children = GetChildren(); + if (children.Count > 0) + { + var button = children[0] as Button; + button.GrabFocus(); + return true; + } + return false; + } +} diff --git a/UI/Inventory/InventoryGrid.tscn b/UI/Inventory/InventoryGrid.tscn new file mode 100644 index 0000000..291e659 --- /dev/null +++ b/UI/Inventory/InventoryGrid.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=3 format=3 uid="uid://chmokkxsy5vas"] + +[ext_resource type="Script" path="res://UI/Inventory/InventoryGrid.cs" id="1_7128g"] +[ext_resource type="PackedScene" uid="uid://ctad0dkoyw8ad" path="res://UI/Inventory/InventorySlot.tscn" id="2_b6vp8"] + +[node name="InventoryGrid" type="GridContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +columns = 5 +script = ExtResource("1_7128g") +_slotScene = ExtResource("2_b6vp8") diff --git a/UI/Inventory/InventorySlot.cs b/UI/Inventory/InventorySlot.cs new file mode 100644 index 0000000..8d9bda6 --- /dev/null +++ b/UI/Inventory/InventorySlot.cs @@ -0,0 +1,95 @@ +using Godot; +using GodotUtilities; +using GodotUtilities.SourceGenerators; + +namespace SupaLidlGame.UI.Inventory; + +public partial class InventorySlot : Button +{ + private bool _isSelected = false; + + public bool IsSelected + { + get => _isSelected; + set + { + _isSelected = value; + if (_selectedFrame is not null) + { + //_selectedFrame.Visible = _isSelected; + //_frame.Visible = !_isSelected; + } + } + } + + [Export] + public bool UseFocusAsSelected { get; set; } = true; + + private TextureRect _textureRect; + + private NinePatchRect _frame; + + private NinePatchRect _selectedFrame; + + private static Texture2D _placeholderTexture; + + private Items.ItemMetadata _item; + + public Items.ItemMetadata Item + { + get => _item; + set + { + _item = value; + + if (_item is null) + { + //_textureRect.Texture = null; + Icon = null; + } + else + { + //_textureRect.Texture = _item.Icon; + Icon = _item.Icon; + } + } + } + + static InventorySlot() + { + _placeholderTexture = ResourceLoader.Load( + "res://Assets/Sprites/UI/hotbar-inactive.png"); + } + + public override void _Ready() + { + _textureRect = GetNode("TextureRect"); + _frame = GetNode("Frame"); + _selectedFrame = GetNode("SelectedFrame"); + + if (Item is null) + { + // do this to reset the icon + Item = null; + } + + if (UseFocusAsSelected) + { + void focusEntered() + { + IsSelected = true; + } + + void focusExited() + { + IsSelected = false; + } + + Connect(SignalName.FocusEntered, Callable.From(focusEntered)); + Connect(SignalName.FocusExited, Callable.From(focusExited)); + + Connect(SignalName.MouseEntered, Callable.From(focusEntered)); + Connect(SignalName.MouseExited, Callable.From(focusExited)); + } + } +} diff --git a/UI/Inventory/InventorySlot.tscn b/UI/Inventory/InventorySlot.tscn new file mode 100644 index 0000000..275b23e --- /dev/null +++ b/UI/Inventory/InventorySlot.tscn @@ -0,0 +1,41 @@ +[gd_scene load_steps=8 format=3 uid="uid://ctad0dkoyw8ad"] + +[ext_resource type="Script" path="res://UI/Inventory/InventorySlot.cs" id="1_fju5i"] +[ext_resource type="Theme" uid="uid://cksjbu3vrup5" path="res://UI/Themes/supalidl.tres" id="1_wnu7u"] +[ext_resource type="StyleBox" uid="uid://nvb4etac7ee2" path="res://UI/Themes/InventorySlotButtonFocus.tres" id="2_3wx0v"] +[ext_resource type="Texture2D" uid="uid://dc1gcsbhkchvg" path="res://Assets/Sprites/UI/hotbar-active.png" id="2_m56j3"] +[ext_resource type="StyleBox" uid="uid://pqtn0115bqtp" path="res://UI/Themes/InventorySlotButtonPressed.tres" id="3_46bp6"] +[ext_resource type="StyleBox" uid="uid://cfqp0ycwvwx7c" path="res://UI/Themes/InventorySlotButtonNormal.tres" id="4_cc2jo"] + +[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_ajutj"] +size = Vector2(24, 24) + +[node name="InventorySlot" type="Button"] +custom_minimum_size = Vector2(32, 32) +theme = ExtResource("1_wnu7u") +theme_type_variation = &"InventorySlotButton" +theme_override_styles/focus = ExtResource("2_3wx0v") +theme_override_styles/pressed = ExtResource("3_46bp6") +theme_override_styles/normal = ExtResource("4_cc2jo") +toggle_mode = true +action_mode = 0 +icon_alignment = 1 +script = ExtResource("1_fju5i") + +[node name="TextureRect" type="TextureRect" parent="."] +visible = false +layout_mode = 2 +offset_right = 32.0 +offset_bottom = 32.0 +mouse_filter = 2 +texture = SubResource("PlaceholderTexture2D_ajutj") +expand_mode = 2 +stretch_mode = 3 + +[node name="Frame" type="NinePatchRect" parent="."] +visible = false +self_modulate = Color(1, 1, 1, 0.5) +layout_mode = 2 +offset_right = 32.0 +offset_bottom = 32.0 +texture = ExtResource("2_m56j3") diff --git a/UI/Inventory/ItemTooltip.cs b/UI/Inventory/ItemTooltip.cs new file mode 100644 index 0000000..dcf6f08 --- /dev/null +++ b/UI/Inventory/ItemTooltip.cs @@ -0,0 +1,44 @@ +using Godot; +using SupaLidlGame.Items; + +namespace SupaLidlGame.UI.Inventory; + +public partial class ItemTooltip : Control +{ + private ItemMetadata _item; + + public ItemMetadata Item + { + get => _item; + set + { + _item = value; + if (IsNodeReady()) + { + Render(); + } + } + } + + public override void _Ready() + { + Render(); + } + + public void Render() + { + if (_item is null) + { + GetNode