From bc85f4e44fa4dd5594f3b7bd78a56e1630a4545d Mon Sep 17 00:00:00 2001 From: HumanoidSandvichDispenser Date: Thu, 3 Aug 2023 16:17:34 -0700 Subject: [PATCH] global scene manager (world) --- Characters/Boss.cs | 5 +- Characters/Doc.cs | 3 +- Extensions/AudioStreamPlayer2D.cs | 2 +- Extensions/Node.cs | 5 ++ Scenes/Map.cs | 6 ++ Scenes/Maps/Arena.tscn | 6 +- Scenes/Maps/ArenaExterior.tscn | 2 +- State/Global/GlobalState.cs | 2 +- State/NPC/Doc/DocAttackState.cs | 7 ++- State/NPC/Doc/DocTelegraphState.cs | 4 +- Utils/CacheItem.cs | 13 ++++ Utils/CacheStore.cs | 89 ++++++++++++++++++++++++++++ Utils/World.cs | 95 ++++++++++++++++++------------ project.godot | 3 +- 14 files changed, 187 insertions(+), 55 deletions(-) create mode 100644 Utils/CacheItem.cs create mode 100644 Utils/CacheStore.cs diff --git a/Characters/Boss.cs b/Characters/Boss.cs index cd86f72..8733a16 100644 --- a/Characters/Boss.cs +++ b/Characters/Boss.cs @@ -1,5 +1,5 @@ using Godot; -using GodotUtilities; +using SupaLidlGame.Extensions; namespace SupaLidlGame.Characters; @@ -27,8 +27,7 @@ public abstract partial class Boss : Enemy _isActive = value; // register or deregister ourselves when we are active/inactive - this.GetAncestor() - .RegisterBoss(_isActive ? this : null); + this.GetWorld().RegisterBoss(_isActive ? this : null); } } diff --git a/Characters/Doc.cs b/Characters/Doc.cs index 1bde845..bd4bba4 100644 --- a/Characters/Doc.cs +++ b/Characters/Doc.cs @@ -1,5 +1,4 @@ using Godot; -using GodotUtilities; using SupaLidlGame.Extensions; using SupaLidlGame.State.Character; @@ -86,7 +85,7 @@ public partial class Doc : Boss .Interaction += () => { //DialogueManager.ShowExampleDialogueBalloon(dialog, "duel"); - this.GetAncestor().DialogueBalloon + this.GetWorld().DialogueBalloon .Start(dialog, "duel"); //.Call("start", dialog, "duel"); }; diff --git a/Extensions/AudioStreamPlayer2D.cs b/Extensions/AudioStreamPlayer2D.cs index 0258eb3..ff7de64 100644 --- a/Extensions/AudioStreamPlayer2D.cs +++ b/Extensions/AudioStreamPlayer2D.cs @@ -30,7 +30,7 @@ public static class AudioStreamPlayer2DExtensions public static AudioStreamPlayer2D OnWorld( this AudioStreamPlayer2D audio) { - var world = audio.GetTree().Root.GetNode("World/TileMap"); + var world = audio.GetWorld().CurrentMap; if (world is null) { throw new NullReferenceException("World does not exist"); diff --git a/Extensions/Node.cs b/Extensions/Node.cs index 48ea606..d167a2c 100644 --- a/Extensions/Node.cs +++ b/Extensions/Node.cs @@ -61,4 +61,9 @@ public static class NodeExtensions { return node.GetNode("/root/EventBus"); } + + public static Utils.World GetWorld(this Node node) + { + return node.GetNode("/root/World"); + } } diff --git a/Scenes/Map.cs b/Scenes/Map.cs index 50fdcae..0999847 100644 --- a/Scenes/Map.cs +++ b/Scenes/Map.cs @@ -1,4 +1,5 @@ using Godot; +using SupaLidlGame.Extensions; namespace SupaLidlGame.Scenes; @@ -45,6 +46,11 @@ public partial class Map : TileMap public override void _Ready() { + var world = this.GetWorld(); + if (world.CurrentMap is null) + { + world.LoadScene(this); + } Active = true; } diff --git a/Scenes/Maps/Arena.tscn b/Scenes/Maps/Arena.tscn index 10207b4..4515805 100644 --- a/Scenes/Maps/Arena.tscn +++ b/Scenes/Maps/Arena.tscn @@ -11,7 +11,7 @@ [ext_resource type="PackedScene" uid="uid://dldnp8eunxj3q" path="res://BoundingBoxes/InteractionTrigger.tscn" id="9_3401j"] [ext_resource type="Script" path="res://BoundingBoxes/ConnectorBox.cs" id="9_fg062"] -[sub_resource type="ShaderMaterial" id="ShaderMaterial_bk1di"] +[sub_resource type="ShaderMaterial" id="ShaderMaterial_keqip"] resource_local_to_scene = true shader = ExtResource("5_h8k5p") shader_parameter/color = Quaternion(1, 1, 1, 1) @@ -24,7 +24,7 @@ size = Vector2(256, 256) [sub_resource type="RectangleShape2D" id="RectangleShape2D_2xagi"] size = Vector2(40, 20) -[node name="TileMap" instance=ExtResource("1_ifiic")] +[node name="Arena" instance=ExtResource("1_ifiic")] tile_set = ExtResource("2_x0mh7") layer_0/tile_data = PackedInt32Array(-524281, 458752, 0, -524284, 458752, 0, -262152, 458752, 0, -327673, 458752, 0, -131065, 458752, 0, -65544, 458752, 0, 131064, 458752, 0, 65543, 458752, 0, 262151, 458752, 0, 327672, 458752, 0, 524280, 458752, 0, 524283, 458752, 0, 524286, 458752, 0, 458753, 458752, 0, 458756, 458752, 0, 458759, 458752, 0, -458760, 458752, 0, -458757, 458752, 0, -524287, 458752, 0, -458754, 458752, 0, -524292, 458752, 3, -589821, 458752, 2, -589822, 393216, 3, -524291, 393216, 2, -589817, 393216, 0) layer_2/tile_data = PackedInt32Array(1376242, 262144, 2, 1441778, 196608, 3, 1376243, 131072, 2, 1441779, 131072, 1, 1376244, 131072, 2, 1441780, 131072, 1, 1376245, 131072, 2, 1441781, 196608, 1, 1376246, 131072, 2, 1441782, 196608, 1, 1376247, 131072, 2, 1441783, 131072, 1, 1376248, 131072, 2, 1441784, 131072, 1, 1376249, 131072, 2, 1441785, 131072, 1, 1376250, 327680, 2, 1441786, 0, 5, 1441787, 131072, 2, 1441788, 131072, 2, 1441789, 131072, 2, 655358, 196608, 3, 720894, 196608, 3, 786430, 196608, 3, 851966, 196608, 3, 917502, 196608, 3, 983038, 196608, 3, 1048574, 196608, 3, 1114110, 196608, 3, 1179646, 196608, 3, 1245182, 196608, 3, 1310718, 196608, 3, 1376254, 196608, 3, 1441790, 65536, 5, 1507322, 131072, 1, 1507323, 131072, 1, 1507324, 196608, 1, 1507325, 131072, 1, 1507326, 131072, 1, 1507314, 196608, 3, 1572850, 65536, 5, 1638386, 131072, 1, 1900530, 65536, 4, 1966066, 196608, 3, 2031602, 262144, 3, 2031603, 196608, 2, 2031604, 196608, 2, 2031605, 196608, 2, 2031606, 196608, 2, 2031607, 196608, 2, 2031608, 196608, 2, 2031609, 196608, 2, 1703930, 0, 4, 1769466, 131072, 3, 1835002, 131072, 3, 1900538, 131072, 3, 1966074, 131072, 3, 2031610, 327680, 3, 1703931, 196608, 2, 1703932, 196608, 2, 1703933, 196608, 2, 589822, 65536, 4, 1703934, 196608, 2, 1703935, 196608, 2, 1638400, 196608, 2, 524289, 0, 4, 589825, 131072, 3, 655361, 131072, 3, 720897, 131072, 3, 786433, 131072, 3, 851969, 131072, 3, 917505, 131072, 3, 983041, 131072, 3, 1048577, 131072, 3, 1114113, 131072, 3, 1179649, 131072, 3, 1245185, 131072, 3, 1310721, 131072, 3, 1376257, 131072, 3, 1441793, 131072, 3, 1507329, 131072, 3, 1572865, 131072, 3, 1638401, 327680, 3) @@ -111,7 +111,7 @@ visible = false position = Vector2(120, -112) [node name="Doc" parent="Entities" index="0" instance=ExtResource("4_ej0f3")] -material = SubResource("ShaderMaterial_bk1di") +material = SubResource("ShaderMaterial_keqip") [node name="PointLight2D" type="PointLight2D" parent="Entities" index="1"] position = Vector2(168, -42) diff --git a/Scenes/Maps/ArenaExterior.tscn b/Scenes/Maps/ArenaExterior.tscn index 5e9e5e6..649ab15 100644 --- a/Scenes/Maps/ArenaExterior.tscn +++ b/Scenes/Maps/ArenaExterior.tscn @@ -152,7 +152,7 @@ size = Vector2(64, 97) [sub_resource type="RectangleShape2D" id="RectangleShape2D_5pcme"] size = Vector2(18, 6) -[node name="TileMap" instance=ExtResource("1_ci4ij")] +[node name="ArenaExterior" instance=ExtResource("1_ci4ij")] tile_set = ExtResource("2_m6h7j") layer_0/tile_data = PackedInt32Array(-1703963, 393216, 0, -1703954, 393216, 0, -1703953, 458752, 2, -393224, 196608, 4, -262152, 196608, 4, -131080, 196608, 4, 196600, 196608, 4, 327672, 196608, 4, 458744, 196608, 4, -458759, 131072, 4, 589817, 458752, 4, -458757, 131072, 4, 589819, 458752, 4, -458755, 131072, 4, 589821, 458752, 4, -524286, 131072, 4, 524290, 458752, 4, -524284, 131072, 4, 524292, 458752, 4, -524282, 131072, 4, 524294, 458752, 4, -458744, 327680, 4, -327672, 327680, 4, -196600, 327680, 4, 131080, 327680, 4, 262152, 327680, 4, 393224, 327680, 4, -589819, 458752, 2, -524294, 458752, 2, -524292, 393216, 2, -589821, 393216, 2, 131070, 458752, 0, 131068, 458752, 0, 131066, 458752, 0, 262142, 458752, 0, 393214, 458752, 0, 327681, 458752, 0, 196609, 458752, 0, 65537, 458752, 0, 65539, 458752, 0, 65541, 458752, 0, -131067, 458752, 0, -131069, 458752, 0, -131071, 458752, 0, -262143, 458752, 0, -393215, 458752, 0, -327682, 458752, 0, -196610, 458752, 0, -65538, 458752, 0, -65540, 458752, 0, -65542, 458752, 0, -1638415, 131072, 4, -1638426, 131072, 4, -1507340, 131072, 4, -1572862, 131072, 4, -1507333, 131072, 4, -1703960, 393216, 2, 1900541, 131072, 4, 1835010, 131072, 4, 1835020, 131072, 4, 1835025, 131072, 4, 2883581, 458752, 4, 2818050, 458752, 4, 2818055, 458752, 4, 2818060, 458752, 4, 2818065, 458752, 4, 2097152, 458752, 0, 2097156, 458752, 0, 2097160, 458752, 0, 2097164, 458752, 0, 2490380, 458752, 0, 2490376, 458752, 0, 2490372, 458752, 0, 2490368, 458752, 0, 2097162, 458752, 0, 2097154, 458752, 0, 2490370, 458752, 0, 2490374, 458752, 0, 2097158, 458752, 0, 2490378, 458752, 0, 2097166, 458752, 0, 2228238, 458752, 0, 2359310, 458752, 0, 2490382, 458752, 0, 1835015, 131072, 4, 1835039, 786432, 0, 1835040, 851968, 0) layer_1/tile_data = PackedInt32Array(-1703953, 393216, 0, -1703952, 393216, 0, 1900577, 65537, 5, 1900576, 65537, 5, 1900578, 65537, 5, 1900575, 65537, 5, 1900574, 65537, 5, 1441823, 327680, 6, 1441832, 393216, 6, 1507368, 393216, 7, 1572904, 393216, 7, 1507359, 327680, 7, 1572895, 327680, 7, 1900581, 65537, 5, 1900582, 65537, 5, 1900583, 65537, 5, 1900584, 65537, 5, 1900585, 65537, 5) diff --git a/State/Global/GlobalState.cs b/State/Global/GlobalState.cs index 714f0dc..412fa9a 100644 --- a/State/Global/GlobalState.cs +++ b/State/Global/GlobalState.cs @@ -4,7 +4,7 @@ namespace SupaLidlGame.State.Global; public partial class GlobalState : Node { - public Utils.World World { get; set; } + //public Utils.World World { get; set; } public Progression Progression { get; set; } diff --git a/State/NPC/Doc/DocAttackState.cs b/State/NPC/Doc/DocAttackState.cs index e8e6428..20b7fdd 100644 --- a/State/NPC/Doc/DocAttackState.cs +++ b/State/NPC/Doc/DocAttackState.cs @@ -1,5 +1,5 @@ using Godot; -using GodotUtilities; +using SupaLidlGame.Extensions; namespace SupaLidlGame.State.NPC.Doc; @@ -26,8 +26,9 @@ public abstract partial class DocAttackState : NPCState public override NPCState Enter(IState previousState) { - _map = this.GetAncestor(); - _world = this.GetAncestor(); + //_map = this.GetAncestor(); + _world = this.GetWorld(); + _map = _world.CurrentMap; _currentDuration = Duration; _currentAttackDuration = AttackDuration; return null; diff --git a/State/NPC/Doc/DocTelegraphState.cs b/State/NPC/Doc/DocTelegraphState.cs index 21bbd07..1609c97 100644 --- a/State/NPC/Doc/DocTelegraphState.cs +++ b/State/NPC/Doc/DocTelegraphState.cs @@ -1,5 +1,5 @@ using Godot; -using GodotUtilities; +using SupaLidlGame.Extensions; namespace SupaLidlGame.State.NPC.Doc; @@ -28,7 +28,7 @@ public partial class DocTelegraphState : NPCState TelegraphAnimationPlayer.Play("enter_in"); NPC.ShouldMove = true; - var player = this.GetAncestor().CurrentPlayer; + var player = this.GetWorld().CurrentPlayer; Vector2 randVec; do diff --git a/Utils/CacheItem.cs b/Utils/CacheItem.cs new file mode 100644 index 0000000..da86400 --- /dev/null +++ b/Utils/CacheItem.cs @@ -0,0 +1,13 @@ +using Godot; + +public class CacheItem +{ + public ulong TimeToLiveTimestamp { get; set; } + + public T Value { get; set; } + + public bool HasExpired(ulong ttl) + { + return Time.GetTicksMsec() > TimeToLiveTimestamp + ttl; + } +} diff --git a/Utils/CacheStore.cs b/Utils/CacheStore.cs new file mode 100644 index 0000000..706af80 --- /dev/null +++ b/Utils/CacheStore.cs @@ -0,0 +1,89 @@ +using Godot; +using System.Collections.Generic; + +public class CacheStore +{ + // default TTL is 5 mins + public ulong TimeToLive { get; } = 3000; + + private Dictionary> _store = new(); + + public CacheItem this[TKey key] + { + get + { + if (_store.ContainsKey(key)) + { + return _store[key]; + } + return null; + } + set + { + if (_store.ContainsKey(key)) + { + _store[key] = value; + } + else + { + _store.Add(key, value); + } + } + } + + public TVal Retrieve(TKey key) + { + if (IsItemValid(key)) + { + return _store[key].Value; + } + return default; + } + + public void Update(TKey key, TVal value = default) + { + CacheItem cacheItem; + if (_store.ContainsKey(key)) + { + cacheItem = _store[key]; + } + else + { + cacheItem = new CacheItem(); + _store[key] = cacheItem; + } + cacheItem.TimeToLiveTimestamp = Time.GetTicksMsec(); + cacheItem.Value = value; + } + + public bool IsItemStale(TKey key) + { + if (_store.ContainsKey(key)) + { + return _store[key].HasExpired(TimeToLive); + } + return false; + } + + public bool IsItemValid(TKey key) + { + return !IsItemStale(key) && _store.ContainsKey(key); + } + + public bool ContainsKey(TKey key) + { + return _store.ContainsKey(key); + } + + public void Update(TKey key) + { + if (_store.ContainsKey(key)) + { + _store[key].TimeToLiveTimestamp = Time.GetTicksMsec(); + } + else + { + GD.PushWarning("Updating a non-existent item in a cache!"); + } + } +} diff --git a/Utils/World.cs b/Utils/World.cs index 922ef8d..720f5b3 100644 --- a/Utils/World.cs +++ b/Utils/World.cs @@ -7,7 +7,7 @@ using System.Linq; namespace SupaLidlGame.Utils; -public partial class World : Node2D +public partial class World : Node { [Export] public PackedScene StartingArea { get; set; } @@ -34,7 +34,7 @@ public partial class World : Node2D public Events.EventBus EventBus { get; set; } - private Dictionary _maps; + private CacheStore _maps = new(); private string _currentConnector; @@ -49,7 +49,6 @@ public partial class World : Node2D public World() { - _maps = new Dictionary(); _playerScene = ResourceLoader.Load(PLAYER_PATH); } @@ -58,39 +57,17 @@ public partial class World : Node2D // check if world already exists GlobalState = this.GetGlobalState(); - if (GlobalState.World is not null) - { - throw new System.InvalidOperationException(); - } - - GlobalState.World = this; Godot.RenderingServer.SetDefaultClearColor(Godot.Colors.Black); if (StartingArea is not null) { - LoadScene(StartingArea); + //LoadScene(StartingArea); } - // spawn the player in + // create a player (currently unparented) CreatePlayer(); - CurrentPlayer.Death += (Events.HealthChangedArgs args) => - { - // TODO: respawn the player at the last campfire. - GetTree().CreateTimer(3).Timeout += () => - { - SpawnPlayer(); - }; - }; - - CurrentPlayer.Hurt += (Events.HealthChangedArgs args) => - { - // TODO: move this to UI controller and add a setup method - var bar = UIController.GetNode("Top/Margin/HealthBar"); - bar.ProgressBar.Value = args.NewHealth; - }; - EventBus = this.GetEventBus(); EventBus.RequestMoveToArea += (Events.RequestAreaArgs args) => { @@ -123,11 +100,11 @@ public partial class World : Node2D if (CurrentMap is not null) { CurrentMap.Entities.RemoveChild(CurrentPlayer); - RemoveChild(CurrentMap); + GetTree().Root.RemoveChild(CurrentMap); CurrentMap.Active = false; } - AddChild(map); + GetTree().Root.AddChild(map); InitTilemap(map); CurrentMap = map; @@ -140,17 +117,36 @@ public partial class World : Node2D } } + public void LoadScene(Map map) + { + _maps.Update(map.SceneFilePath, map); + LoadMap(map); + } + public void LoadScene(PackedScene scene) { - Map map; - if (_maps.ContainsKey(scene.ResourcePath)) + if (CurrentMap is not null) { - map = _maps[scene.ResourcePath]; + _maps.Update(CurrentMap.SceneFilePath); + } + + Map map; + string path = scene.ResourcePath; + + if (_maps.IsItemValid(path)) + { + GD.Print($"{path} is cached"); + map = _maps.Retrieve(path); } else { + if (_maps.IsItemStale(path)) + { + _maps[path].Value.QueueFree(); + GD.Print("Freeing stale map " + path); + } map = scene.Instantiate(); - _maps.Add(scene.ResourcePath, map); + _maps.Update(path, map); } LoadMap(map); @@ -159,24 +155,47 @@ public partial class World : Node2D public void LoadScene(string path) { Map map; - if (_maps.ContainsKey(path)) + if (_maps.IsItemValid(path)) { - map = _maps[path]; + GD.Print($"{path} is cached"); + map = _maps.Retrieve(path); } else { + if (_maps.IsItemStale(path)) + { + _maps[path].Value.QueueFree(); + GD.Print("Freeing stale map " + path); + } var scene = ResourceLoader.Load(path); map = scene.Instantiate(); - _maps.Add(scene.ResourcePath, map); + _maps.Update(scene.ResourcePath, map); } LoadMap(map); } - public void CreatePlayer() + public Player CreatePlayer() { CurrentPlayer = _playerScene.Instantiate(); - CurrentMap.Entities.AddChild(CurrentPlayer); + + CurrentPlayer.Death += (Events.HealthChangedArgs args) => + { + // TODO: respawn the player at the last campfire. + GetTree().CreateTimer(3).Timeout += () => + { + SpawnPlayer(); + }; + }; + + CurrentPlayer.Hurt += (Events.HealthChangedArgs args) => + { + // TODO: move this to UI controller and add a setup method + var bar = UIController.GetNode("Top/Margin/HealthBar"); + bar.ProgressBar.Value = args.NewHealth; + }; + + return CurrentPlayer; } private void InitTilemap(Map map) diff --git a/project.godot b/project.godot index 9d95223..c9f3bf8 100644 --- a/project.godot +++ b/project.godot @@ -11,7 +11,7 @@ config_version=5 [application] config/name="SupaLidlGame" -run/main_scene="res://Scenes/Level.tscn" +run/main_scene="res://Scenes/Maps/ArenaExterior.tscn" config/features=PackedStringArray("4.1", "C#", "Forward Plus") config/icon="res://icon.svg" @@ -22,6 +22,7 @@ DialogueManager="*res://addons/dialogue_manager/dialogue_manager.gd" GlobalState="*res://State/Global/GlobalState.cs" EventBus="*res://Events/EventBus.cs" Panku="*res://addons/panku_console/console.tscn" +World="*res://Scenes/Level.tscn" [dialogue_manager]