Implement Vendor NPCs (#37)

* Add WIP vendors

* Add TimerExtensions

* chore: Update dialogue manager version

* Add Inventory.EquippedItem signal

* Move files to UI.Inventory namespace

* Modify Hotbar and InventorySlot organization

Made InventorySlot a bit more modular.

* Modify hotbar icons

* Implement request to display shop

* Add forsenLevel placeholder icon

* Add shop UI

* Update UI to use theme and click through

* Update item metadata

Added placeholder icons for items without an icon.

* Add Snus Dealer example vendor NPC

* Implement factions with bitflags instead

* Fix typo in Doc dialogue

* Hide and show shop menu

* Add shaders

* Add newline to console output

* Add modal interface
pull/36/merge
HumanoidSandvichDispenser 2024-06-04 22:12:01 -07:00 committed by GitHub
parent 516d79d86f
commit b8a5e641b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
91 changed files with 2925 additions and 1233 deletions

View File

@ -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"

View File

@ -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"

View File

@ -18,11 +18,10 @@ Doc, The Two Time: Nothing but success.
Doc, The Two Time: That's what my life's about. Period.
Doc, The Two Time: And the arena today, ladies and gentlemen...
Doc, The Two Time: is wide open, and the crowds are flooding in. VIP seating. Skybox section. Reserved for the Slick Daddy Club.
Doc, The Two Time: The Slick Daddy Club lofoking so damn good today. I'm feeling so damn good. It's obvious.
Doc, The Two Time: The V of success.
Doc, The Two Time: The Slick Daddy Club looking so damn good today. I'm feeling so damn good. It's obvious.
Challenge [b]Doc, The Two Time[/b] to a duel?
- Yes
do emit("SummonBoss", "Doc")
- No => END
- No, I'm a little chubby cheek wannabe punk kid. => END
=> END

View File

@ -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"

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 B

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

View File

@ -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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

View File

@ -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

View File

@ -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; }
}

View File

@ -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; }

View File

@ -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")

View File

@ -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;
}
/// <summary>
/// Finds the best character whose faction aligns with this character's.
/// </summary>
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);

View File

@ -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"]

View File

@ -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);
// }
//}

View File

@ -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>("%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 + "\n";
}
};
timer.Start(DEBUG_REFRESH_INTERVAL);
}
else
{
GD.PushWarning("File logging is not enabled.");
}
}
public IEnumerable<NodePathToken> 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);
}
}

View File

@ -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

View File

@ -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

View File

@ -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();
}
}

View File

@ -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

View File

@ -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;

View File

@ -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);
}
}

View File

@ -0,0 +1,15 @@
namespace SupaLidlGame.Items;
public interface IItemCollection
{
public System.Collections.Generic.IEnumerable<ItemMetadata> GetItems();
public int Capacity { get; }
}
public interface IItemCollection<T> : IItemCollection
{
public bool Add(T item);
public bool Remove(T item);
}

View File

@ -4,7 +4,7 @@ using Godot.Collections;
namespace SupaLidlGame.Items;
public partial class Inventory : Node2D
public partial class Inventory : Node2D, IItemCollection<ItemMetadata>
{
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<Item>();
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<ItemMetadata> 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;

45
Items/Shop.cs 100644
View File

@ -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<ShopEntry>
{
[Export]
protected Godot.Collections.Array<ShopEntry> Entries { get; private set; }
public System.Collections.Generic.IEnumerable<ItemMetadata> 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;
}

23
Items/ShopEntry.cs 100644
View File

@ -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; }
}

View File

@ -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")])

View File

@ -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

View File

@ -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<PackedScene>(value);
}
}
protected virtual void SpawnProjectile(Scenes.Map map,
Vector2 direction, float velocityModifier = 1)
{

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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"

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=22 format=3 uid="uid://cumi1cbg6xfdd"]
[gd_scene load_steps=23 format=3 uid="uid://cumi1cbg6xfdd"]
[ext_resource type="PackedScene" uid="uid://clwv2owvk6abe" path="res://Scenes/BaseMap.tscn" id="1_oy3cp"]
[ext_resource type="Texture2D" uid="uid://c70cn53osy56w" path="res://Assets/Sprites/dev-tileset.png" id="2_2gdry"]
@ -11,6 +11,7 @@
[ext_resource type="Texture2D" uid="uid://chwwo6vmf8iri" path="res://Assets/Sprites/Props/tree-autumn-leaves-2.png" id="8_4laic"]
[ext_resource type="Texture2D" uid="uid://dhf2f5a1ty502" path="res://Assets/Sprites/Props/tree-autumn-leaves-1.png" id="8_hyhpu"]
[ext_resource type="Texture2D" uid="uid://crvbsxrda5gcj" path="res://Assets/Sprites/Props/tree-autumn-leaves-3.png" id="9_exvkw"]
[ext_resource type="PackedScene" uid="uid://rd08pot25h00" path="res://Characters/SnusDealer.tscn" id="12_eyny2"]
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_s1h2f"]
texture = ExtResource("2_2gdry")
@ -458,8 +459,8 @@ cell_size = 16.0
agent_radius = 8.0
[sub_resource type="NavigationPolygon" id="NavigationPolygon_8xwo5"]
vertices = PackedVector2Array(280, -232, 280, -120, 276, -120, 216, -232, 216, -40, 276, -72, 296, -72, 296, -88, 392, -88, 392, -136, 472, -136, 472, -40, 440, -40, 440, 8, 488, 8, 488, 24, 440, 24, 440, 72, 424, 72, 424, 24, 424, 8, 376, 24, 376, 8, 424, -40, 296, -40, 296, -56, 276, -56, 276, -40)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(4, 3, 2, 5), PackedInt32Array(6, 7, 8), PackedInt32Array(8, 9, 10, 11, 12), PackedInt32Array(13, 14, 15, 16), PackedInt32Array(16, 17, 18, 19), PackedInt32Array(13, 16, 19, 20), PackedInt32Array(19, 21, 22, 20), PackedInt32Array(12, 13, 20, 23), PackedInt32Array(8, 12, 23), PackedInt32Array(8, 23, 24, 25), PackedInt32Array(6, 8, 25), PackedInt32Array(5, 6, 25, 26), PackedInt32Array(4, 5, 26), PackedInt32Array(4, 26, 27)])
vertices = PackedVector2Array(392, -88, 392, -136, 472, -136, 472, -40, 440, -40, 440, 8, 488, 8, 488, 24, 440, 24, 440, 72, 424, 72, 424, 24, 424, 8, 376, 24, 376, 8, 424, -40, 216, -40, 280, -88, 216, -232, 280, -232)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3, 4), PackedInt32Array(5, 6, 7, 8), PackedInt32Array(8, 9, 10, 11), PackedInt32Array(5, 8, 11, 12), PackedInt32Array(11, 13, 14, 12), PackedInt32Array(4, 5, 12, 15), PackedInt32Array(0, 4, 15), PackedInt32Array(0, 15, 16, 17), PackedInt32Array(17, 16, 18, 19)])
outlines = Array[PackedVector2Array]([PackedVector2Array(208, -240, 208, -32, 416, -32, 416.001, -15.9759, 368.001, -15.9787, 368, 32, 416, 32, 416, 80, 448, 80, 448, 32, 496, 32, 496.001, -15.9713, 448.001, -15.9741, 448, -32, 480, -32, 480.008, -143.972, 384.008, -143.978, 384.006, -95.9778, 288, -96, 288, -240)])
source_geometry_mode = 1
source_geometry_group_name = &"navigation"
@ -479,6 +480,9 @@ Markers = NodePath("Markers")
AreaName = "Forest"
MapName = "Forest"
[node name="CanvasModulate" parent="." index="7"]
position = Vector2(0, 1)
[node name="StaticBody2D" type="StaticBody2D" parent="Props" index="0"]
y_sort_enabled = true
position = Vector2(0, -96)
@ -843,7 +847,14 @@ 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="SnusDealer" parent="Entities" index="1" node_paths=PackedStringArray("ThinkerStateMachine", "Sprite", "Inventory", "StateMachine") instance=ExtResource("12_eyny2")]
position = Vector2(79, -6)
ThinkerStateMachine = NodePath("Thinker")
Sprite = NodePath("Sprites/Sprite")
Inventory = NodePath("Inventory")
StateMachine = NodePath("StateMachine")
[node name="Areas" parent="." index="10"]
visible = false
[node name="Main" type="NavigationRegion2D" parent="Areas" index="0"]

View File

@ -3,9 +3,5 @@ shader_type canvas_item;
uniform float amount : hint_range(0.5, 1.0, 0.01) = 1;
void fragment() {
// Place fragment code here.
//COLOR = texture(TEXTURE, UV);
if (UV.x > amount || UV.x < 1.0 - amount) {
COLOR.a = 0.0;
}
COLOR.a *= step(UV.x, amount) * step(1.0 - amount, UV.x);
}

View File

@ -0,0 +1,10 @@
shader_type canvas_item;
uniform float x_amount : hint_range(0.5, 1.0, 0.01) = 1;
uniform float y_amount : hint_range(0.5, 1.0, 0.01) = 0.5;
void fragment() {
COLOR.a *= step(UV.x, x_amount) * step(1.0 - x_amount, UV.x);
//COLOR.a *= step(1.0 - y_amount, UV.y) * step(UV.y, y_amount);
COLOR.a *= 1.0 - (step(1.0 - y_amount, UV.y) * step(UV.y, y_amount));
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -1,13 +1,13 @@
[gd_scene load_steps=14 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"]
@ -20,6 +20,26 @@ shader_parameter/grayscale_intensity = 0.0
[node name="BaseUI" type="CanvasLayer"]
process_mode = 3
[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 +56,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 +69,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 +96,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 +109,23 @@ 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="ShopMenu" parent="SubViewportContainer/UIViewport/MainUILayer/Main/BoxContainer" node_paths=PackedStringArray("_inventoryGrid") instance=ExtResource("8_ep3ae")]
unique_name_in_owner = true
visible = false
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 +141,13 @@ alignment = 1
visible = false
layout_mode = 2
[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")

View File

@ -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

6
UI/IModal.cs 100644
View File

@ -0,0 +1,6 @@
namespace SupaLidlGame.UI;
public interface IModal
{
public void HideModal();
}

View File

@ -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++)

View File

@ -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")

View File

View File

@ -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")

View File

@ -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<InventorySlot>();
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;
}
}

View File

@ -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")

View File

@ -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<Texture2D>(
"res://Assets/Sprites/UI/hotbar-inactive.png");
}
public override void _Ready()
{
_textureRect = GetNode<TextureRect>("TextureRect");
_frame = GetNode<NinePatchRect>("Frame");
_selectedFrame = GetNode<NinePatchRect>("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));
}
}
}

View File

@ -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")

View File

@ -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<Label>("%ItemLabel").Text = "Nothing here";
GetNode<TextureRect>("%ItemTexture").Texture = null;
GetNode<RichTextLabel>("%ItemDescription").Text = "We are ready for My Summer Car";
GetNode<Label>("%Ingredients/Label").Text = "0 Shillings";
return;
}
GetNode<Label>("%ItemLabel").Text = _item.Name;
GetNode<TextureRect>("%ItemTexture").Texture = _item.Icon;
GetNode<RichTextLabel>("%ItemDescription").Text = _item.Description;
GetNode<Label>("%Ingredients/Label").Text = _item.BuyPrice + " Shillings";
}
}

View File

@ -0,0 +1,127 @@
[gd_scene load_steps=12 format=3 uid="uid://bsheehtfcdwhh"]
[ext_resource type="Theme" uid="uid://cksjbu3vrup5" path="res://UI/Themes/supalidl.tres" id="1_elbte"]
[ext_resource type="Script" path="res://UI/Inventory/ItemTooltip.cs" id="2_entlu"]
[ext_resource type="Texture2D" uid="uid://b16461tjso0j7" path="res://Assets/Sprites/UI/hotbar-inactive.png" id="2_mcttf"]
[ext_resource type="Texture2D" uid="uid://cd8ak8gu0104t" path="res://Assets/Sprites/UI/border.png" id="2_sxqyx"]
[ext_resource type="Texture2D" uid="uid://dc1gcsbhkchvg" path="res://Assets/Sprites/UI/hotbar-active.png" id="2_vjw5e"]
[sub_resource type="Animation" id="Animation_mmh32"]
resource_name = "wipe"
[sub_resource type="AnimationLibrary" id="AnimationLibrary_4bp3t"]
_data = {
"wipe": SubResource("Animation_mmh32")
}
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_xhduc"]
content_margin_left = 0.0
content_margin_top = 0.0
content_margin_right = 0.0
content_margin_bottom = 0.0
texture = ExtResource("2_mcttf")
texture_margin_left = 16.0
texture_margin_top = 16.0
texture_margin_right = 16.0
texture_margin_bottom = 16.0
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_rhjxi"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
texture = ExtResource("2_vjw5e")
texture_margin_left = 8.0
texture_margin_top = 8.0
texture_margin_right = 8.0
texture_margin_bottom = 8.0
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_qnr6k"]
size = Vector2(24, 24)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_idehr"]
bg_color = Color(0.976471, 0.956863, 0.956863, 1)
[node name="ItemTooltip" type="BoxContainer"]
offset_right = 92.0
offset_bottom = 161.0
theme = ExtResource("1_elbte")
script = ExtResource("2_entlu")
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
"": SubResource("AnimationLibrary_4bp3t")
}
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 0
[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxTexture_xhduc")
[node name="Margin" type="MarginContainer" parent="VBoxContainer/PanelContainer"]
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/PanelContainer/Margin"]
layout_mode = 2
alignment = 1
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer/Margin/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 8
[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/PanelContainer/Margin/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_vertical = 0
theme_override_styles/panel = SubResource("StyleBoxTexture_rhjxi")
[node name="ItemTexture" type="TextureRect" parent="VBoxContainer/PanelContainer/Margin/VBoxContainer/HBoxContainer/PanelContainer"]
unique_name_in_owner = true
layout_mode = 2
texture = SubResource("PlaceholderTexture2D_qnr6k")
stretch_mode = 2
[node name="ItemLabel" type="Label" parent="VBoxContainer/PanelContainer/Margin/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Really long item name"
autowrap_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/PanelContainer/Margin/VBoxContainer/HBoxContainer"]
visible = false
layout_mode = 2
size_flags_horizontal = 3
[node name="NinePatchRect" type="NinePatchRect" parent="VBoxContainer/PanelContainer/Margin/VBoxContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
texture = ExtResource("2_sxqyx")
region_rect = Rect2(0, 6, 48, 5)
patch_margin_left = 16
patch_margin_top = 5
patch_margin_right = 16
[node name="ItemDescription" type="RichTextLabel" parent="VBoxContainer/PanelContainer/Margin/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/default_color = Color(0.937255, 0.854902, 0.854902, 1)
text = "Speed, violence, momentum"
fit_content = true
[node name="PanelContainer2" type="PanelContainer" parent="VBoxContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_idehr")
[node name="Ingredients" type="MarginContainer" parent="VBoxContainer/PanelContainer2"]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/margin_top = 2
theme_override_constants/margin_bottom = 2
[node name="Label" type="Label" parent="VBoxContainer/PanelContainer2/Ingredients"]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0, 0, 1)
text = "250 Shillings"

View File

@ -0,0 +1,100 @@
using Godot;
using System.Collections.Generic;
namespace SupaLidlGame.UI.Inventory;
public partial class ShopMenu : Control, IModal
{
private Items.IItemCollection<Items.ShopEntry> _source;
public Items.IItemCollection<Items.ShopEntry> Source
{
get => _source;
set
{
_source = value;
_inventoryGrid.Source = value;
}
}
[Export]
private InventoryGrid _inventoryGrid;
private InventorySlot _selected;
public void ShowModal()
{
Show();
var animPlayer = GetNode<AnimationPlayer>("%AnimationPlayer");
animPlayer.Play("open");
}
public void HideModal()
{
Hide();
_source = null;
}
public async void Close()
{
var animPlayer = GetNode<AnimationPlayer>("%AnimationPlayer");
animPlayer.Play("close");
await ToSignal(animPlayer, AnimationPlayer.SignalName.AnimationFinished);
HideModal();
}
public override void _Ready()
{
_inventoryGrid.SlotFocused += (InventorySlot slot) =>
{
if (slot.Item is not null)
{
SetTooltipItem(slot);
}
};
_inventoryGrid.SlotUnfocused += (InventorySlot slot) =>
{
SetTooltipItem(_selected);
};
_inventoryGrid.SlotSelected += (InventorySlot slot) =>
{
_selected = slot;
SetTooltipItem(slot);
GetNode<Button>("%BuyButton").GrabFocus();
};
GetNode<Button>("%BuyButton").GuiInput += (InputEvent @event) =>
{
if (@event.IsActionPressed("ui_cancel"))
{
GetViewport().SetInputAsHandled();
_selected?.GrabFocus();
}
};
}
private void SetTooltipItem(InventorySlot slot)
{
GetNode<ItemTooltip>("%ItemTooltip").Item = slot?.Item;
if (slot == _selected)
{
GetNode<Button>("%BuyButton").Disabled = false;
}
else
{
GetNode<Button>("%BuyButton").Disabled = true;
}
}
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed("ui_cancel"))
{
GetViewport().SetInputAsHandled();
Close();
}
}
}

View File

@ -0,0 +1,217 @@
[gd_scene load_steps=15 format=3 uid="uid://cyggkyqosjk36"]
[ext_resource type="StyleBox" uid="uid://bqhotx2ogucye" path="res://UI/Themes/Panel.tres" id="1_2ffty"]
[ext_resource type="Script" path="res://UI/Inventory/ShopMenu.cs" id="1_8c1y7"]
[ext_resource type="Shader" path="res://Shaders/WipeXY.gdshader" id="2_jqery"]
[ext_resource type="FontFile" uid="uid://cgwa8bjiyv534" path="res://Assets/Fonts/alagard.ttf" id="3_aj4jx"]
[ext_resource type="PackedScene" uid="uid://bsheehtfcdwhh" path="res://UI/Inventory/ItemTooltip.tscn" id="4_n61n7"]
[ext_resource type="PackedScene" uid="uid://chmokkxsy5vas" path="res://UI/Inventory/InventoryGrid.tscn" id="4_sl632"]
[ext_resource type="PackedScene" uid="uid://baawkwo8aiwbu" path="res://UI/Inventory/ShopSlot.tscn" id="5_kfyl0"]
[ext_resource type="Theme" uid="uid://cksjbu3vrup5" path="res://UI/Themes/supalidl.tres" id="7_rvp1r"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gm1xk"]
bg_color = Color(0, 0, 0, 0.784314)
border_width_left = 16
border_width_top = 16
border_width_right = 16
border_width_bottom = 16
border_color = Color(0.145098, 0.145098, 0.145098, 0)
border_blend = true
corner_radius_top_left = 16
corner_radius_top_right = 16
corner_radius_bottom_right = 16
corner_radius_bottom_left = 16
[sub_resource type="ShaderMaterial" id="ShaderMaterial_2hdh3"]
shader = ExtResource("2_jqery")
shader_parameter/x_amount = 1.0
shader_parameter/y_amount = 0.5
[sub_resource type="Animation" id="Animation_yj24f"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Color(1, 1, 1, 1)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("PanelContainer:material:shader_parameter/y_amount")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [0.5]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("PanelContainer:material:shader_parameter/x_amount")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [1.0]
}
[sub_resource type="Animation" id="Animation_tm2as"]
resource_name = "close"
length = 0.5
step = 0.05
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.4),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("PanelContainer:material:shader_parameter/y_amount")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.1),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [0.5, 1.0]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("PanelContainer:material:shader_parameter/x_amount")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0.05, 0.15),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [1.0, 0.5]
}
[sub_resource type="Animation" id="Animation_fgj27"]
resource_name = "open"
length = 0.5
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.3),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 1)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("PanelContainer:material:shader_parameter/y_amount")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.5),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [1.0, 0.5]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("PanelContainer:material:shader_parameter/x_amount")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.3),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [0.5, 1.0]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_0glcp"]
_data = {
"RESET": SubResource("Animation_yj24f"),
"close": SubResource("Animation_tm2as"),
"open": SubResource("Animation_fgj27")
}
[node name="Panel" type="PanelContainer" node_paths=PackedStringArray("_inventoryGrid")]
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -236.0
offset_top = -175.5
offset_bottom = 175.5
grow_horizontal = 0
grow_vertical = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_gm1xk")
script = ExtResource("1_8c1y7")
_inventoryGrid = NodePath("PanelContainer/VBoxContainer/ScrollContainer/InventoryGrid")
[node name="PanelContainer" type="PanelContainer" parent="."]
material = SubResource("ShaderMaterial_2hdh3")
layout_mode = 2
theme_override_styles/panel = ExtResource("1_2ffty")
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_override_fonts/font = ExtResource("3_aj4jx")
text = "Snus Dealer"
[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
follow_focus = true
horizontal_scroll_mode = 0
[node name="InventoryGrid" parent="PanelContainer/VBoxContainer/ScrollContainer" instance=ExtResource("4_sl632")]
layout_mode = 2
size_flags_vertical = 3
_slotScene = ExtResource("5_kfyl0")
[node name="ItemTooltip" parent="PanelContainer/VBoxContainer" instance=ExtResource("4_n61n7")]
unique_name_in_owner = true
layout_mode = 2
[node name="HBoxContainer2" type="HBoxContainer" parent="PanelContainer/VBoxContainer"]
layout_mode = 2
[node name="BuyButton" type="Button" parent="PanelContainer/VBoxContainer/HBoxContainer2"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme = ExtResource("7_rvp1r")
text = "Buy"
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
unique_name_in_owner = true
libraries = {
"": SubResource("AnimationLibrary_0glcp")
}

View File

@ -0,0 +1,14 @@
[gd_scene load_steps=3 format=3 uid="uid://baawkwo8aiwbu"]
[ext_resource type="PackedScene" uid="uid://ctad0dkoyw8ad" path="res://UI/Inventory/InventorySlot.tscn" id="1_tdkv2"]
[ext_resource type="Texture2D" uid="uid://b16461tjso0j7" path="res://Assets/Sprites/UI/hotbar-inactive.png" id="2_af1qu"]
[node name="ShopSlot" instance=ExtResource("1_tdkv2")]
[node name="Frame" parent="." index="1"]
texture = ExtResource("2_af1qu")
[node name="SelectedFrame" type="NinePatchRect" parent="." index="2"]
visible = false
layout_mode = 2
texture = ExtResource("2_af1qu")

View File

@ -1,74 +0,0 @@
using Godot;
using GodotUtilities;
using GodotUtilities.SourceGenerators;
namespace SupaLidlGame.UI;
[Scene]
public partial class InventorySlot : ColorRect
{
[Node("TextureRect")]
private TextureRect _textureRect;
[Node("Selected")]
private NinePatchRect _selected;
[Node("Unselected")]
private NinePatchRect _unselected;
private static Texture2D _placeholderTexture;
private Items.ItemMetadata _item;
public Items.ItemMetadata Item
{
get => _item;
set
{
_item = value;
if (_item is null)
{
//_textureRect.Texture = _placeholderTexture;
_textureRect.Texture = null;
}
else
{
_textureRect.Texture = _item.Icon;
}
}
}
private bool _isSelected = false;
public bool IsSelected
{
get => _isSelected;
set
{
_isSelected = value;
_selected.Visible = _isSelected;
_unselected.Visible = !_isSelected;
}
}
static InventorySlot()
{
_placeholderTexture = ResourceLoader.Load<Texture2D>(
"res://Assets/Sprites/UI/hotbar-inactive.png");
}
public override void _Notification(int what)
{
if (what == NotificationSceneInstantiated)
{
WireNodes();
}
base._Notification(what);
}
public override void _Ready()
{
}
}

View File

@ -1,39 +0,0 @@
[gd_scene load_steps=4 format=3 uid="uid://ctad0dkoyw8ad"]
[ext_resource type="Script" path="res://UI/InventorySlot.cs" id="1_llonk"]
[ext_resource type="Texture2D" uid="uid://bd81g8aivb2ql" path="res://Assets/Sprites/UI/menu-rect-no-bg-32.png" id="2_vvog5"]
[ext_resource type="Texture2D" uid="uid://b16461tjso0j7" path="res://Assets/Sprites/UI/hotbar-inactive.png" id="3_jr23q"]
[node name="InventorySlot" type="ColorRect"]
custom_minimum_size = Vector2(32, 32)
color = Color(1, 1, 1, 0)
script = ExtResource("1_llonk")
[node name="TextureRect" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
stretch_mode = 3
[node name="Selected" type="NinePatchRect" parent="."]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("2_vvog5")
[node name="Unselected" type="NinePatchRect" parent="."]
self_modulate = Color(1, 1, 1, 0.5)
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("3_jr23q")

View File

@ -151,6 +151,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = ExtResource("1_g1mdf")
[node name="VBoxContainer" type="VBoxContainer" parent="."]

View File

@ -107,6 +107,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = ExtResource("1_mx0ay")
[node name="ColorRect" type="ColorRect" parent="."]
@ -117,6 +118,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
color = Color(0, 0, 0, 1)
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]

View File

@ -0,0 +1,14 @@
[gd_resource type="StyleBoxTexture" load_steps=2 format=3 uid="uid://nvb4etac7ee2"]
[ext_resource type="Texture2D" uid="uid://b16461tjso0j7" path="res://Assets/Sprites/UI/hotbar-inactive.png" id="1_el8jk"]
[resource]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
texture = ExtResource("1_el8jk")
texture_margin_left = 4.0
texture_margin_top = 4.0
texture_margin_right = 4.0
texture_margin_bottom = 4.0

View File

@ -0,0 +1,15 @@
[gd_resource type="StyleBoxTexture" load_steps=2 format=3 uid="uid://cfqp0ycwvwx7c"]
[ext_resource type="Texture2D" uid="uid://b16461tjso0j7" path="res://Assets/Sprites/UI/hotbar-inactive.png" id="1_r3gsw"]
[resource]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
texture = ExtResource("1_r3gsw")
texture_margin_left = 4.0
texture_margin_top = 4.0
texture_margin_right = 4.0
texture_margin_bottom = 4.0
modulate_color = Color(1, 1, 1, 0.498039)

View File

@ -0,0 +1,15 @@
[gd_resource type="StyleBoxTexture" load_steps=2 format=3 uid="uid://pqtn0115bqtp"]
[ext_resource type="Texture2D" uid="uid://b16461tjso0j7" path="res://Assets/Sprites/UI/hotbar-inactive.png" id="1_1ahpp"]
[resource]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
texture = ExtResource("1_1ahpp")
texture_margin_left = 4.0
texture_margin_top = 4.0
texture_margin_right = 4.0
texture_margin_bottom = 4.0
modulate_color = Color(0.941176, 0.843137, 0.470588, 1)

View File

@ -0,0 +1,14 @@
[gd_resource type="StyleBoxTexture" load_steps=2 format=3 uid="uid://bqhotx2ogucye"]
[ext_resource type="Texture2D" uid="uid://uhmowtsi3wfh" path="res://Assets/Sprites/UI/menu-rect-no-bg-white.png" id="1_cb20l"]
[resource]
content_margin_left = 16.0
content_margin_top = 16.0
content_margin_right = 16.0
content_margin_bottom = 16.0
texture = ExtResource("1_cb20l")
texture_margin_left = 32.0
texture_margin_top = 32.0
texture_margin_right = 32.0
texture_margin_bottom = 32.0

View File

@ -1,10 +1,58 @@
[gd_resource type="Theme" load_steps=2 format=3 uid="uid://cksjbu3vrup5"]
[gd_resource type="Theme" load_steps=11 format=3 uid="uid://cksjbu3vrup5"]
[ext_resource type="FontFile" uid="uid://6bvgjbmqovau" path="res://Assets/Fonts/calamity/calamity.ttf" id="1_334fe"]
[ext_resource type="StyleBox" uid="uid://nvb4etac7ee2" path="res://UI/Themes/InventorySlotButtonFocus.tres" id="2_3w5h1"]
[ext_resource type="Texture2D" uid="uid://b16461tjso0j7" path="res://Assets/Sprites/UI/hotbar-inactive.png" id="2_6sv27"]
[ext_resource type="StyleBox" uid="uid://bqhotx2ogucye" path="res://UI/Themes/Panel.tres" id="2_jlgx8"]
[ext_resource type="StyleBox" uid="uid://cfqp0ycwvwx7c" path="res://UI/Themes/InventorySlotButtonNormal.tres" id="3_nuiio"]
[ext_resource type="StyleBox" uid="uid://pqtn0115bqtp" path="res://UI/Themes/InventorySlotButtonPressed.tres" id="4_mllnb"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_wk5ww"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
texture = ExtResource("2_6sv27")
texture_margin_left = 16.0
texture_margin_top = 16.0
texture_margin_right = 16.0
texture_margin_bottom = 16.0
modulate_color = Color(0.105882, 0.0470588, 0.117647, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5w4ck"]
bg_color = Color(0.937255, 0.854902, 0.854902, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2qibe"]
content_margin_left = 2.0
content_margin_top = 2.0
content_margin_right = 2.0
content_margin_bottom = 2.0
bg_color = Color(0.976471, 0.956863, 0.956863, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_iyd8w"]
bg_color = Color(0.105882, 0.0470588, 0.117647, 1)
[resource]
default_font = ExtResource("1_334fe")
default_font_size = 15
/colors/background = Color(0.105882, 0.0470588, 0.117647, 1)
/font_sizes/Normal = 16
/font_sizes/Normal = 15
/fonts/Calamity = ExtResource("1_334fe")
Button/colors/font_color = Color(0.105882, 0.0470588, 0.117647, 1)
Button/colors/font_focus_color = Color(0.105882, 0.0470588, 0.117647, 1)
Button/colors/font_hover_color = Color(0.105882, 0.0470588, 0.117647, 1)
Button/colors/font_pressed_color = Color(0.976471, 0.956863, 0.956863, 1)
Button/styles/focus = SubResource("StyleBoxTexture_wk5ww")
Button/styles/hover = SubResource("StyleBoxFlat_5w4ck")
Button/styles/normal = SubResource("StyleBoxFlat_2qibe")
Button/styles/pressed = SubResource("StyleBoxFlat_iyd8w")
InventorySlotButton/base_type = &"Button"
InventorySlotButton/styles/focus = ExtResource("2_3w5h1")
InventorySlotButton/styles/hover = ExtResource("2_3w5h1")
InventorySlotButton/styles/normal = ExtResource("3_nuiio")
InventorySlotButton/styles/pressed = ExtResource("4_mllnb")
MarginContainer/constants/margin_bottom = 8
MarginContainer/constants/margin_left = 8
MarginContainer/constants/margin_right = 8
MarginContainer/constants/margin_top = 8
PanelContainer/styles/panel = ExtResource("2_jlgx8")

View File

@ -1,4 +1,5 @@
using Godot;
using System.Collections.Generic;
namespace SupaLidlGame.UI;
@ -9,4 +10,17 @@ public partial class UIController : Control
[Export]
public BossBar BossBar { get; set; }
private Stack<IModal> _modals;
public override void _Ready()
{
Events.EventBus.Instance.EnterShop += (string path) =>
{
var shop = ResourceLoader.Load<Items.Shop>(path);
var shopMenu = GetNode<Inventory.ShopMenu>("%ShopMenu");
shopMenu.Source = shop;
shopMenu.ShowModal();
};
}
}

View File

@ -1,4 +1,5 @@
using Godot;
using SupaLidlGame.Extensions;
namespace SupaLidlGame.Utils;
@ -52,8 +53,7 @@ public partial class CharacterStats : Node
else
{
_shouldDecayStagger = false;
_staggerDecayTimer.Stop();
_staggerDecayTimer.Start(1);
_staggerDecayTimer.Restart(1);
}
}

View File

@ -2,13 +2,20 @@ using Godot;
namespace SupaLidlGame.Utils;
[System.Flags]
public enum FactionName
{
Player = 1,
Doc = 2,
}
public interface IFaction
{
/// <summary>
/// The faction index that this entity belongs to.
/// </summary>
[Export]
public ushort Faction { get; set; }
public FactionName Faction { get; set; }
public bool AlignsWith(IFaction other)
{

View File

@ -206,6 +206,13 @@ namespace DialogueManagerRuntime
public partial class DialogueLine : RefCounted
{
private string id = "";
public string Id
{
get => id;
set => id = value;
}
private string type = "dialogue";
public string Type
{

View File

@ -70,26 +70,28 @@ func _ready() -> void:
func _gui_input(event: InputEvent) -> void:
# Handle shortcuts that come from the editor
if event is InputEventKey and event.is_pressed():
match event.as_text():
"Ctrl+Equal", "Command+Equal":
self.font_size += 1
get_viewport().set_input_as_handled()
"Ctrl+Minus", "Command+Minus":
self.font_size -= 1
get_viewport().set_input_as_handled()
"Ctrl+0", "Command+0":
self.font_size = theme_overrides.font_size
get_viewport().set_input_as_handled()
"Ctrl+K", "Command+K":
var shortcut: String = Engine.get_meta("DialogueManagerPlugin").get_editor_shortcut(event)
match shortcut:
"toggle_comment":
toggle_comment()
get_viewport().set_input_as_handled()
"Alt+Up":
"move_up":
move_line(-1)
get_viewport().set_input_as_handled()
"Alt+Down":
"move_down":
move_line(1)
get_viewport().set_input_as_handled()
"text_size_increase":
self.font_size += 1
get_viewport().set_input_as_handled()
"text_size_decrease":
self.font_size -= 1
get_viewport().set_input_as_handled()
"text_size_reset":
self.font_size = theme_overrides.font_size
get_viewport().set_input_as_handled()
elif event is InputEventMouse:
match event.as_text():

View File

@ -26,11 +26,11 @@ var regex_dict: RegEx = RegEx.create_from_string("^\\{((?>[^\\{\\}]+|(?R))*)\\}$
var regex_kvdict: RegEx = RegEx.create_from_string("^\\s*(?<left>.*?)\\s*(?<colon>:|=)\\s*(?<right>[^\\/]+)$")
var regex_commas: RegEx = RegEx.create_from_string("([^,]+)(?:\\s*,\\s*)?")
var regex_assignment: RegEx = RegEx.create_from_string("^\\s*(?<var>[a-zA-Z_][a-zA-Z_0-9]*)(?:(?<attr>(?:\\.[a-zA-Z_][a-zA-Z_0-9]*)+)|(?:\\[(?<key>[^\\]]+)\\]))?\\s*(?<op>(?:\\/|\\*|-|\\+)?=)\\s*(?<val>.*)$")
var regex_varname: RegEx = RegEx.create_from_string("^\\s*(?!true|false|and|or|not|in|null)(?<var>[a-zA-Z_][a-zA-Z_0-9]*)(?:(?<attr>(?:\\.[a-zA-Z_][a-zA-Z_0-9]*)+)|(?:\\[(?<key>[^\\]]+)\\]))?\\s*$")
var regex_varname: RegEx = RegEx.create_from_string("^\\s*(?!true|false|and|or|&&|\\|\\|not|in|null)(?<var>[a-zA-Z_][a-zA-Z_0-9]*)(?:(?<attr>(?:\\.[a-zA-Z_][a-zA-Z_0-9]*)+)|(?:\\[(?<key>[^\\]]+)\\]))?\\s*$")
var regex_keyword: RegEx = RegEx.create_from_string("^\\s*(true|false|null)\\s*$")
var regex_function: RegEx = RegEx.create_from_string("^\\s*([a-zA-Z_][a-zA-Z_0-9]*\\s*)\\(")
var regex_comparison: RegEx = RegEx.create_from_string("^(?<left>.*?)\\s*(?<op>==|>=|<=|<|>|!=)\\s*(?<right>.*)$")
var regex_blogical: RegEx = RegEx.create_from_string("^(?<left>.*?)\\s+(?<op>and|or|in)\\s+(?<right>.*)$")
var regex_blogical: RegEx = RegEx.create_from_string("^(?<left>.*?)\\s+(?<op>and|or|in|&&|\\|\\|)\\s+(?<right>.*)$")
var regex_ulogical: RegEx = RegEx.create_from_string("^\\s*(?<op>not)\\s+(?<right>.*)$")
var regex_paren: RegEx = RegEx.create_from_string("\\((?<paren>((?:[^\\(\\)]*)|(?:\\((?1)\\)))*?)\\)")
@ -186,9 +186,9 @@ func _get_dialogue_syntax_highlighting(start_index: int, text: String) -> Dictio
for goto_match in goto_matches:
colors[start_index + goto_match.get_start(0)] = {"color": text_edit.theme_overrides.jumps_color}
if "file" in goto_match.names:
colors[start_index + goto_match.get_start("file")] = {"color": text_edit.theme_overrides.members_color}
colors[start_index + goto_match.get_start("file")] = {"color": text_edit.theme_overrides.jumps_color}
colors[start_index + goto_match.get_end("file")] = {"color": text_edit.theme_overrides.symbols_color}
colors[start_index + goto_match.get_start("title")] = {"color": text_edit.theme_overrides.titles_color}
colors[start_index + goto_match.get_start("title")] = {"color": text_edit.theme_overrides.jumps_color}
colors[start_index + goto_match.get_end("title")] = {"color": text_edit.theme_overrides.jumps_color}
colors[start_index + goto_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color}

View File

@ -43,7 +43,7 @@ var TOKEN_DEFINITIONS: Dictionary = {
DialogueConstants.TOKEN_DOT: RegEx.create_from_string("^\\."),
DialogueConstants.TOKEN_STRING: RegEx.create_from_string("^(\".*?\"|\'.*?\')"),
DialogueConstants.TOKEN_NOT: RegEx.create_from_string("^(not( |$)|!)"),
DialogueConstants.TOKEN_AND_OR: RegEx.create_from_string("^(and|or)( |$)"),
DialogueConstants.TOKEN_AND_OR: RegEx.create_from_string("^(and|or|&&|\\|\\|)( |$)"),
DialogueConstants.TOKEN_VARIABLE: RegEx.create_from_string("^[a-zA-Z_][a-zA-Z_0-9]*"),
DialogueConstants.TOKEN_COMMENT: RegEx.create_from_string("^#.*"),
DialogueConstants.TOKEN_CONDITION: RegEx.create_from_string("^(if|elif|else)"),
@ -230,6 +230,7 @@ func parse(text: String, path: String) -> Error:
line["character"] = first_child.character
line["character_replacements"] = first_child.character_replacements
line["text"] = first_child.text
line["text_replacements"] = extract_dialogue_replacements(line.text, indent_size + 2)
line["translation_key"] = first_child.translation_key
parsed_lines[str(id) + ".2"] = first_child
line["next_id"] = str(id) + ".2"
@ -691,12 +692,7 @@ func get_line_after_line(id: int, indent_size: int, line: Dictionary) -> String:
var next_nonempty_line_id = get_next_nonempty_line_id(id)
if next_nonempty_line_id != DialogueConstants.ID_NULL \
and indent_size <= get_indent(raw_lines[next_nonempty_line_id.to_int()]):
# The next line is a title so we need the next nonempty line after that
if is_title_line(raw_lines[next_nonempty_line_id.to_int()]):
return get_next_nonempty_line_id(next_nonempty_line_id.to_int())
# Otherwise it's a normal line
else:
return next_nonempty_line_id
return next_nonempty_line_id
# Otherwise, we grab the ID from the parents next ID after children
elif line.has("parent_id") and parsed_lines.has(line.parent_id):
return parsed_lines[line.parent_id].next_id_after
@ -927,8 +923,8 @@ func find_next_line_after_responses(line_number: int) -> String:
if get_indent(line) <= expected_indent:
return str(line_number)
# EOF so must be end of conversation
return DialogueConstants.ID_END_CONVERSATION
# EOF so it's also the end of a block
return DialogueConstants.ID_END
## Get the names of any autoloads in the project
@ -1529,10 +1525,15 @@ func build_token_tree(tokens: Array[Dictionary], line_type: String, expected_clo
DialogueConstants.TOKEN_ASSIGNMENT, \
DialogueConstants.TOKEN_OPERATOR, \
DialogueConstants.TOKEN_AND_OR, \
DialogueConstants.TOKEN_VARIABLE: \
DialogueConstants.TOKEN_VARIABLE:
var value = token.value.strip_edges()
if value == "&&":
value = "and"
elif value == "||":
value = "or"
tree.append({
type = token.type,
value = token.value.strip_edges()
value = value
})
DialogueConstants.TOKEN_STRING:

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
@icon("./assets/responses_menu.svg")
## A VBoxContainer for dialogue responses provided by [b]Dialogue Manager[/b].
class_name DialogueResponsesMenu extends VBoxContainer
## A [Container] for dialogue responses provided by [b]Dialogue Manager[/b].
class_name DialogueResponsesMenu extends Container
## Emitted when a response is selected.
@ -14,8 +14,10 @@ signal response_selected(response)
## The action for accepting a response (is possibly overridden by parent dialogue balloon).
@export var next_action: StringName = &""
# The list of dialogue responses.
## The list of dialogue responses.
var responses: Array = []:
get:
return responses
set(value):
responses = value
@ -64,11 +66,25 @@ func _ready() -> void:
response_template.hide()
# This is deprecated.
## Get the selectable items in the menu.
func get_menu_items() -> Array:
var items: Array = []
for child in get_children():
if not child.visible: continue
if "Disallowed" in child.name: continue
items.append(child)
return items
## [b]DEPRECATED[/b]. Do not use.
func set_responses(next_responses: Array) -> void:
self.responses = next_responses
#region Internal
# Prepare the menu for keyboard and mouse navigation.
func _configure_focus() -> void:
var items = get_menu_items()
@ -100,18 +116,9 @@ func _configure_focus() -> void:
items[0].grab_focus()
## Get the selectable items in the menu.
func get_menu_items() -> Array:
var items: Array = []
for child in get_children():
if not child.visible: continue
if "Disallowed" in child.name: continue
items.append(child)
#endregion
return items
### Signals
#region Signals
func _on_response_mouse_entered(item: Control) -> void:
@ -129,3 +136,6 @@ func _on_response_gui_input(event: InputEvent, item: Control, response) -> void:
response_selected.emit(response)
elif event.is_action_pressed(&"ui_accept" if next_action.is_empty() else next_action) and item in get_menu_items():
response_selected.emit(response)
#endregion

View File

@ -105,6 +105,21 @@ namespace DialogueManagerRuntime
}
public override async void _Notification(int what)
{
// Detect a change of locale and update the current dialogue line to show the new language
if (what == NotificationTranslationChanged)
{
float visibleRatio = dialogueLabel.VisibleRatio;
DialogueLine = await DialogueManager.GetNextDialogueLine(resource, DialogueLine.Id, temporaryGameStates);
if (visibleRatio < 1.0f)
{
dialogueLabel.Call("skip_typing");
}
}
}
public async void Start(Resource dialogueResource, string title, Array<Variant> extraGameStates = null)
{
temporaryGameStates = extraGameStates ?? new Array<Variant>();

View File

@ -89,6 +89,15 @@ func _unhandled_input(_event: InputEvent) -> void:
get_viewport().set_input_as_handled()
func _notification(what: int) -> void:
# Detect a change of locale and update the current dialogue line to show the new language
if what == NOTIFICATION_TRANSLATION_CHANGED:
var visible_ratio = dialogue_label.visible_ratio
self.dialogue_line = await resource.get_next_dialogue_line(dialogue_line.id)
if visible_ratio < 1:
dialogue_label.skip_typing()
## Start some dialogue
func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void:
temporary_game_states = [self] + extra_game_states
@ -102,7 +111,7 @@ func next(next_id: String) -> void:
self.dialogue_line = await resource.get_next_dialogue_line(next_id, temporary_game_states)
### Signals
#region Signals
func _on_mutated(_mutation: Dictionary) -> void:
@ -139,3 +148,6 @@ func _on_balloon_gui_input(event: InputEvent) -> void:
func _on_responses_menu_response_selected(response: DialogueResponse) -> void:
next(response.next_id)
#endregion

View File

@ -8,7 +8,7 @@ signal compiled_resource(resource: Resource)
const DialogueResource = preload("./dialogue_resource.gd")
const DialogueManagerParseResult = preload("./components/parse_result.gd")
const compiler_version = 11
const compiler_version = 12
func _get_importer_name() -> String:

View File

@ -4,7 +4,7 @@ msgstr ""
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: penghao123456、憨憨羊の宇航鸽鸽\n"
"Language-Team: penghao123456、憨憨羊の宇航鸽鸽、ABShinri\n"
"Language: zh\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -29,6 +29,9 @@ msgstr "清空历史记录"
msgid "save_all_files"
msgstr "保存所有文件"
msgid "find_in_files"
msgstr "在文件中查找"
msgid "test_dialogue"
msgstr "测试对话"
@ -51,10 +54,10 @@ msgid "docs"
msgstr "文档"
msgid "insert.wave_bbcode"
msgstr "BBCode [lb]wave[rb]"
msgstr "波浪效果"
msgid "insert.shake_bbcode"
msgstr "BBCode [lb]wave[rb]"
msgstr "抖动效果"
msgid "insert.typing_pause"
msgstr "输入间隔"
@ -95,6 +98,9 @@ msgstr "结束对话"
msgid "generate_line_ids"
msgstr "生成行 ID"
msgid "save_characters_to_csv"
msgstr "保存角色到 CSV"
msgid "save_to_csv"
msgstr "生成 CSV"
@ -134,6 +140,9 @@ msgstr "在 Godot 侧边栏中显示"
msgid "settings.revert_to_default_test_scene"
msgstr "重置测试场景设定"
msgid "settings.default_balloon_hint"
msgstr "设置调用 \"DialogueManager.show_balloon()\" 时使用的对话框"
msgid "settings.autoload"
msgstr "Autoload"
@ -150,10 +159,10 @@ msgid "settings.missing_keys_hint"
msgstr "如果你使用静态键,这将会帮助你寻找未添加至翻译文件的键。"
msgid "settings.characters_translations"
msgstr "在翻译文件中导出角色名"
msgstr "在翻译文件中导出角色名"
msgid "settings.wrap_long_lines"
msgstr "自动折行"
msgstr "文本编辑器自动换行"
msgid "settings.include_failed_responses"
msgstr "在判断条件失败时仍显示回复选项"
@ -176,9 +185,39 @@ msgstr "当一个 Autoload 在这里被勾选,他的所有成员会被映射
msgid "settings.states_hint"
msgstr "比如当你开启对于“Foo”的映射时你可以将“Foo.bar”简写成“bar”。"
msgid "settings.recompile_warning"
msgstr "更改这些选项会强制重新编译所有的对话框,当你清楚在做什么的时候更改。"
msgid "settings.create_lines_for_responses_with_characters"
msgstr "回复项带角色名时(- char: response会自动生成为选择后的下一句对话"
msgid "settings.include_characters_in_translations"
msgstr "导出 CSV 时包括角色名"
msgid "settings.include_notes_in_translations"
msgstr "导出 CSV 时包括注释(## comments"
msgid "settings.check_for_updates"
msgstr "检查升级"
msgid "n_of_n"
msgstr "第{index}个,共{total}个"
msgid "search.find"
msgstr "查找:"
msgid "search.find_all"
msgstr "查找全部..."
msgid "search.placeholder"
msgstr "请输入查找的内容"
msgid "search.replace_placeholder"
msgstr "请输入替换的内容"
msgid "search.replace_selected"
msgstr "替换勾选"
msgid "search.previous"
msgstr "查找上一个"

View File

@ -4,7 +4,7 @@ msgstr ""
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: 憨憨羊の宇航鴿鴿\n"
"Language-Team: 憨憨羊の宇航鴿鴿、ABShinri\n"
"Language: zh_TW\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -29,6 +29,9 @@ msgstr "清空歷史記錄"
msgid "save_all_files"
msgstr "儲存所有檔案"
msgid "find_in_files"
msgstr "在檔案中查找"
msgid "test_dialogue"
msgstr "測試對話"
@ -51,10 +54,10 @@ msgid "docs"
msgstr "文檔"
msgid "insert.wave_bbcode"
msgstr "BBCode [lb]wave[rb]"
msgstr "波浪特效"
msgid "insert.shake_bbcode"
msgstr "BBCode [lb]wave[rb]"
msgstr "震動特效"
msgid "insert.typing_pause"
msgstr "輸入間隔"
@ -95,6 +98,9 @@ msgstr "結束對話"
msgid "generate_line_ids"
msgstr "生成行 ID"
msgid "save_characters_to_csv"
msgstr "保存角色到 CSV"
msgid "save_to_csv"
msgstr "生成 CSV"
@ -134,6 +140,9 @@ msgstr "在 Godot 側邊欄中顯示"
msgid "settings.revert_to_default_test_scene"
msgstr "重置測試場景設定"
msgid "settings.default_balloon_hint"
msgstr "設置使用 \"DialogueManager.show_balloon()\" 时的对话框"
msgid "settings.autoload"
msgstr "Autoload"
@ -176,9 +185,39 @@ msgstr "當一個 Autoload 在這裏被勾選,他的所有成員會被映射
msgid "settings.states_hint"
msgstr "比如當你開啓對於“Foo”的映射時你可以將“Foo.bar”簡寫成“bar”。"
msgid "settings.recompile_warning"
msgstr "更改這些選項會強制重新編譯所有的對話框,當你清楚在做什麼的時候更改。"
msgid "settings.create_lines_for_responses_with_characters"
msgstr "回覆項目帶角色名稱時(- char: response會自動產生為選擇後的下一句對話"
msgid "settings.include_characters_in_translations"
msgstr "匯出 CSV 時包含角色名"
msgid "settings.include_notes_in_translations"
msgstr "匯出 CSV 時包括註解(## comments"
msgid "settings.check_for_updates"
msgstr "檢查升級"
msgid "n_of_n"
msgstr "第{index}個,共{total}個"
msgid "search.find"
msgstr "搜尋:"
msgid "search.find_all"
msgstr "搜尋全部..."
msgid "search.placeholder"
msgstr "請輸入搜尋的內容"
msgid "search.replace_placeholder"
msgstr "請輸入替換的內容"
msgid "search.replace_selected"
msgstr "替換勾選"
msgid "search.previous"
msgstr "搜尋上一個"

View File

@ -3,5 +3,5 @@
name="Dialogue Manager"
description="A simple but powerful branching dialogue system"
author="Nathan Hoad"
version="2.38.0"
version="2.39.1"
script="plugin.gd"

View File

@ -145,6 +145,80 @@ func _build() -> bool:
return true
## Get the shortcuts used by the plugin
func get_editor_shortcuts() -> Dictionary:
var shortcuts: Dictionary = {
toggle_comment = [
_create_event("Ctrl+K"),
_create_event("Ctrl+Slash")
],
move_up = [
_create_event("Alt+Up")
],
move_down = [
_create_event("Alt+Down")
],
save = [
_create_event("Ctrl+Alt+S")
],
close_file = [
_create_event("Ctrl+W")
],
find_in_files = [
_create_event("Ctrl+Shift+F")
],
run_test_scene = [
_create_event("Ctrl+F5")
],
text_size_increase = [
_create_event("Ctrl+Equal")
],
text_size_decrease = [
_create_event("Ctrl+Minus")
],
text_size_reset = [
_create_event("Ctrl+0")
]
}
var paths = get_editor_interface().get_editor_paths()
var settings = load(paths.get_config_dir() + "/editor_settings-4.tres")
if not settings: return shortcuts
for s in settings.get("shortcuts"):
for key in shortcuts:
if s.name == "script_text_editor/%s" % key or s.name == "script_editor/%s" % key:
shortcuts[key] = []
for event in s.shortcuts:
if event is InputEventKey:
shortcuts[key].append(event)
return shortcuts
func _create_event(string: String) -> InputEventKey:
var event: InputEventKey = InputEventKey.new()
var bits = string.split("+")
event.keycode = OS.find_keycode_from_string(bits[bits.size() - 1])
event.shift_pressed = bits.has("Shift")
event.alt_pressed = bits.has("Alt")
if bits.has("Ctrl") or bits.has("Command"):
event.command_or_control_autoremap = true
return event
## Get the editor shortcut that matches an event
func get_editor_shortcut(event: InputEventKey) -> String:
var shortcuts: Dictionary = get_editor_shortcuts()
for key in shortcuts:
for shortcut in shortcuts.get(key, []):
if event.is_match(shortcut):
return key
return ""
## Get the current version
func get_version() -> String:
var config: ConfigFile = ConfigFile.new()

View File

@ -176,9 +176,5 @@ static func has_dotnet_solution() -> bool:
var has_dotnet_solution: bool = FileAccess.file_exists("res://%s/%s.sln" % [directory, file_name])
set_user_value("has_dotnet_solution", has_dotnet_solution)
return has_dotnet_solution
else:
var plugin_path: String = new().get_script().resource_path.get_base_dir()
if not ResourceLoader.exists(plugin_path + "/DialogueManager.cs"): return false
if load(plugin_path + "/DialogueManager.cs") == null: return false
return true
return false

View File

@ -11,7 +11,8 @@ const SUPPORTED_BUILTIN_TYPES = [
TYPE_DICTIONARY,
TYPE_QUATERNION,
TYPE_COLOR,
TYPE_SIGNAL
TYPE_SIGNAL,
TYPE_CALLABLE
]

View File

@ -181,19 +181,20 @@ func _unhandled_input(event: InputEvent) -> void:
if not visible: return
if event is InputEventKey and event.is_pressed():
match event.as_text():
"Ctrl+Alt+S", "Command+Alt+S":
get_viewport().set_input_as_handled()
save_file(current_file_path)
"Ctrl+W", "Command+W":
var shortcut: String = Engine.get_meta("DialogueManagerPlugin").get_editor_shortcut(event)
match shortcut:
"close_file":
get_viewport().set_input_as_handled()
close_file(current_file_path)
"Ctrl+F5", "Command+F5":
"save":
get_viewport().set_input_as_handled()
_on_test_button_pressed()
"Ctrl+Shift+F", "Command+Shift+F":
save_file(current_file_path)
"find_in_files":
get_viewport().set_input_as_handled()
_on_find_in_files_button_pressed()
"run_test_scene":
get_viewport().set_input_as_handled()
_on_test_button_pressed()
func apply_changes() -> void:
@ -1044,9 +1045,11 @@ func _on_files_list_file_middle_clicked(path: String):
func _on_files_popup_menu_about_to_popup() -> void:
files_popup_menu.clear()
files_popup_menu.add_item(DialogueConstants.translate(&"buffer.save"), ITEM_SAVE, KEY_MASK_CTRL | KEY_MASK_ALT | KEY_S)
var shortcuts: Dictionary = Engine.get_meta("DialogueManagerPlugin").get_editor_shortcuts()
files_popup_menu.add_item(DialogueConstants.translate(&"buffer.save"), ITEM_SAVE, OS.find_keycode_from_string(shortcuts.get("save")[0].as_text_keycode()))
files_popup_menu.add_item(DialogueConstants.translate(&"buffer.save_as"), ITEM_SAVE_AS)
files_popup_menu.add_item(DialogueConstants.translate(&"buffer.close"), ITEM_CLOSE, KEY_MASK_CTRL | KEY_W)
files_popup_menu.add_item(DialogueConstants.translate(&"buffer.close"), ITEM_CLOSE, OS.find_keycode_from_string(shortcuts.get("close_file")[0].as_text_keycode()))
files_popup_menu.add_item(DialogueConstants.translate(&"buffer.close_all"), ITEM_CLOSE_ALL)
files_popup_menu.add_item(DialogueConstants.translate(&"buffer.close_other_files"), ITEM_CLOSE_OTHERS)
files_popup_menu.add_separator()

@ -1 +1 @@
Subproject commit 36625398e3ad45306521926c5190141a7ac925b0
Subproject commit b265f5cebab07f3af55c92f642b458db9c973a7c

View File

@ -17,17 +17,16 @@ config/icon="res://icon.svg"
[autoload]
DialogueManager="*res://addons/dialogue_manager/dialogue_manager.gd"
GlobalState="*res://State/Global/GlobalState.cs"
EventBus="*res://Events/EventBus.cs"
BaseUI="*res://UI/Base.tscn"
World="*res://Scenes/Level.tscn"
AudioManager="*res://Audio/AudioManager.cs"
DebugUi="*res://UI/Debug/DebugUI.tscn"
DialogueManager="*res://addons/dialogue_manager/dialogue_manager.gd"
[dialogue_manager]
general/states=["GlobalState"]
general/states=["GlobalState", "World", "EventBus"]
general/wrap_lines=true
[display]
@ -201,7 +200,7 @@ cast={
[internationalization]
locale/translations_pot_files=PackedStringArray("res://Assets/Dialog/doc.dialogue", "res://Assets/Dialogue/doc.dialogue", "res://Assets/Dialogue/spellbook.dialogue", "res://Assets/Dialogue/books.dialogue", "res://Assets/Dialogue/clone-machine.dialogue")
locale/translations_pot_files=PackedStringArray("res://Assets/Dialogue/doc.dialogue", "res://Assets/Dialogue/books.dialogue", "res://Assets/Dialogue/clone-machine.dialogue", "res://Dialogue/snus-dealer.dialogue")
[layer_names]
@ -215,6 +214,10 @@ locale/translations_pot_files=PackedStringArray("res://Assets/Dialog/doc.dialogu
2d_physics/layer_6="Interaction Receiver"
2d_physics/layer_7="Interaction Trigger"
[navigation]
2d/default_cell_size=16.0
[physics]
2d/default_gravity=0.0
@ -222,7 +225,5 @@ locale/translations_pot_files=PackedStringArray("res://Assets/Dialog/doc.dialogu
[rendering]
textures/canvas_textures/default_texture_filter=0
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"
environment/defaults/default_clear_color=Color(0.301961, 0.301961, 0.301961, 1)
viewport/hdr_2d=true