From c3bfab5f53cb0e45a6ffbd91d55afc9a29b17989 Mon Sep 17 00:00:00 2001 From: Zenny Date: Fri, 7 Mar 2025 12:05:20 -0800 Subject: [PATCH] Refactoring game logic --- .../Save/ISaveFileManager.cs | 8 +++- .../Game/GameLogic.Output.cs | 8 +++- .../Game/GameLogic.State.cs | 16 +++++++ .../Game/GameRepo.cs | 40 ++++++++++++++++- .../states/GameLogic.State.InventoryOpened.cs | 2 +- .../GlobalSuppressions.cs | 8 ++++ .../Item/Tags/ThrowableItemTag.cs | 1 + .../Item/Tags/UsableItemTag.cs | 1 - .../Save/SaveFileManager.cs | 43 +++++++++++++------ Zennysoft.Game.Ma/src/game/Game.cs | 26 ++++------- Zennysoft.Game.Ma/src/game/IGame.cs | 2 - .../resources/SpellSignKnowledge.tres | 6 ++- .../src/map/dungeon/floors/Floor00.tscn | 8 +++- 13 files changed, 127 insertions(+), 42 deletions(-) create mode 100644 Zennysoft.Game.Ma.Implementation/GlobalSuppressions.cs diff --git a/Zennysoft.Game.Abstractions/Save/ISaveFileManager.cs b/Zennysoft.Game.Abstractions/Save/ISaveFileManager.cs index 65e235b6..0db34c41 100644 --- a/Zennysoft.Game.Abstractions/Save/ISaveFileManager.cs +++ b/Zennysoft.Game.Abstractions/Save/ISaveFileManager.cs @@ -6,5 +6,11 @@ public interface ISaveFileManager { public Task WriteToFile(T gameData); - public Task ReadFromFile(); + public Task WriteToFile(T gameData, string filePath); + + public Task WriteToFile(T gameData, string filePath, JsonSerializerOptions jsonOptions); + + public Task ReadFromFile(); + + public Task ReadFromFile(string filePath); } diff --git a/Zennysoft.Game.Ma.Implementation/Game/GameLogic.Output.cs b/Zennysoft.Game.Ma.Implementation/Game/GameLogic.Output.cs index c37f6c5f..84b9f296 100644 --- a/Zennysoft.Game.Ma.Implementation/Game/GameLogic.Output.cs +++ b/Zennysoft.Game.Ma.Implementation/Game/GameLogic.Output.cs @@ -12,9 +12,9 @@ public partial class GameLogic public readonly record struct ExitPauseMenu; - public readonly record struct OpenInventory(); + public readonly record struct OpenInventory; - public readonly record struct HideInventory; + public readonly record struct CloseInventory; public readonly record struct SetPauseMode(bool IsPaused); @@ -41,5 +41,9 @@ public partial class GameLogic public readonly record struct GoToOverworld; public readonly record struct SaveGame; + + public readonly record struct AnnounceMessage(string Message); + + public readonly record struct DoubleExpTimeStart(int lengthOfTimeInSeconds); } } diff --git a/Zennysoft.Game.Ma.Implementation/Game/GameLogic.State.cs b/Zennysoft.Game.Ma.Implementation/Game/GameLogic.State.cs index c2de3c8f..4c731af3 100644 --- a/Zennysoft.Game.Ma.Implementation/Game/GameLogic.State.cs +++ b/Zennysoft.Game.Ma.Implementation/Game/GameLogic.State.cs @@ -14,14 +14,30 @@ public partial class GameLogic { var gameRepo = Get(); gameRepo.IsPaused.Sync += OnIsPaused; + gameRepo.OpenInventory += OnOpenInventory; + gameRepo.CloseInventory += OnCloseInventory; + gameRepo.AnnounceMessage += OnAnnounceMessage; + gameRepo.DoubleExpTimeStart += OnDoubleExpTimeStart; }); OnDetach(() => { var gameRepo = Get(); gameRepo.IsPaused.Sync -= OnIsPaused; + gameRepo.OpenInventory -= OnOpenInventory; + gameRepo.CloseInventory -= OnCloseInventory; + gameRepo.AnnounceMessage -= OnAnnounceMessage; + gameRepo.DoubleExpTimeStart -= OnDoubleExpTimeStart; }); } + private void OnOpenInventory() => Output(new Output.OpenInventory()); + + private void OnCloseInventory() => Output(new Output.CloseInventory()); + + private void OnAnnounceMessage(string message) => Output(new Output.AnnounceMessage(message)); + + private void OnDoubleExpTimeStart(int lengthOfTimeInSeconds) => Output(new Output.DoubleExpTimeStart(lengthOfTimeInSeconds)); + public void OnIsPaused(bool isPaused) => Output(new Output.SetPauseMode(isPaused)); } } diff --git a/Zennysoft.Game.Ma.Implementation/Game/GameRepo.cs b/Zennysoft.Game.Ma.Implementation/Game/GameRepo.cs index 9e7f8435..10467191 100644 --- a/Zennysoft.Game.Ma.Implementation/Game/GameRepo.cs +++ b/Zennysoft.Game.Ma.Implementation/Game/GameRepo.cs @@ -5,27 +5,51 @@ namespace Zennysoft.Game.Ma.Implementation; public interface IGameRepo : IDisposable { + event Action? Ended; + + event Action? OpenInventory; + + event Action? CloseInventory; + + event Action? AnnounceMessage; + + event Action? DoubleExpTimeStart; + + event Action? DoubleExpTimeEnd; + void Pause(); void Resume(); IAutoProp IsPaused { get; } + + public void StartDoubleEXP(TimeSpan lengthOfEffect); + + public void EndDoubleExp(); + + public double ExpRate { get; } } public class GameRepo : IGameRepo { public event Action? Ended; + public event Action? OpenInventory; + public event Action? CloseInventory; + public event Action? AnnounceMessage; + public event Action? DoubleExpTimeStart; + public event Action? DoubleExpTimeEnd; public IAutoProp IsPaused => _isPaused; private readonly AutoProp _isPaused; - public int MaxItemSize => 20; + public double ExpRate { get; private set; } private bool _disposedValue; public GameRepo() { _isPaused = new AutoProp(true); + ExpRate = 1; } public void Pause() @@ -40,6 +64,20 @@ public class GameRepo : IGameRepo GD.Print("Resume"); } + public void StartDoubleEXP(TimeSpan lengthOfEffect) + { + CloseInventory?.Invoke(); + AnnounceMessage?.Invoke("Experience points temporarily doubled."); + DoubleExpTimeStart?.Invoke(lengthOfEffect.Seconds); + ExpRate = 2; + } + + public void EndDoubleExp() + { + AnnounceMessage?.Invoke("Experience points effect wore off."); + ExpRate = 1; + } + public void OnGameEnded() { Pause(); diff --git a/Zennysoft.Game.Ma.Implementation/Game/state/states/GameLogic.State.InventoryOpened.cs b/Zennysoft.Game.Ma.Implementation/Game/state/states/GameLogic.State.InventoryOpened.cs index e945a3f3..1e526bd2 100644 --- a/Zennysoft.Game.Ma.Implementation/Game/state/states/GameLogic.State.InventoryOpened.cs +++ b/Zennysoft.Game.Ma.Implementation/Game/state/states/GameLogic.State.InventoryOpened.cs @@ -13,7 +13,7 @@ public partial class GameLogic public InventoryOpened() { this.OnEnter(() => { Get().Pause(); Output(new Output.OpenInventory()); }); - this.OnExit(() => { Get().Resume(); Output(new Output.HideInventory()); }); + this.OnExit(() => { Get().Resume(); Output(new Output.CloseInventory()); }); } public Transition On(in Input.CloseInventory input) diff --git a/Zennysoft.Game.Ma.Implementation/GlobalSuppressions.cs b/Zennysoft.Game.Ma.Implementation/GlobalSuppressions.cs new file mode 100644 index 00000000..8727e868 --- /dev/null +++ b/Zennysoft.Game.Ma.Implementation/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "", Scope = "namespace", Target = "~N:Zennysoft.Game.Ma.Implementation")] diff --git a/Zennysoft.Game.Ma.Implementation/Item/Tags/ThrowableItemTag.cs b/Zennysoft.Game.Ma.Implementation/Item/Tags/ThrowableItemTag.cs index 397ab19a..afca029a 100644 --- a/Zennysoft.Game.Ma.Implementation/Item/Tags/ThrowableItemTag.cs +++ b/Zennysoft.Game.Ma.Implementation/Item/Tags/ThrowableItemTag.cs @@ -3,6 +3,7 @@ public enum ThrowableItemTag { None, + DoubleExp, LowerTargetTo1HP, CanChangeAffinity, TeleportToRandomLocation, diff --git a/Zennysoft.Game.Ma.Implementation/Item/Tags/UsableItemTag.cs b/Zennysoft.Game.Ma.Implementation/Item/Tags/UsableItemTag.cs index 495ebc7c..0b20fe6e 100644 --- a/Zennysoft.Game.Ma.Implementation/Item/Tags/UsableItemTag.cs +++ b/Zennysoft.Game.Ma.Implementation/Item/Tags/UsableItemTag.cs @@ -3,7 +3,6 @@ public enum UsableItemTag { None, - DoubleEXP, IdentifyAllItemsCostHP, BriefImmunity, SwapHPAndVT, diff --git a/Zennysoft.Game.Ma.Implementation/Save/SaveFileManager.cs b/Zennysoft.Game.Ma.Implementation/Save/SaveFileManager.cs index adcfdd87..77baffc7 100644 --- a/Zennysoft.Game.Ma.Implementation/Save/SaveFileManager.cs +++ b/Zennysoft.Game.Ma.Implementation/Save/SaveFileManager.cs @@ -12,15 +12,14 @@ namespace Zennysoft.Game.Ma.Implementation; public class SaveFileManager : ISaveFileManager { private readonly IFileSystem _fileSystem; - public const string SAVE_FILE_NAME = "game.json"; - public JsonSerializerOptions JsonOptions { get; set; } = default!; - - public string SaveFilePath { get; set; } = default!; + private JsonSerializerOptions _jsonOptions; + private string _defaultSaveLocation; + public const string DEFAULT_SAVE_FILE_NAME = "game.json"; public SaveFileManager(IFileSystem fileSystem) { _fileSystem = fileSystem; - SaveFilePath = _fileSystem.Path.Join(OS.GetUserDataDir(), SAVE_FILE_NAME); + _defaultSaveLocation = _fileSystem.Path.Join(OS.GetUserDataDir(), DEFAULT_SAVE_FILE_NAME); var resolver = new SerializableTypeResolver(); GodotSerialization.Setup(); @@ -28,7 +27,7 @@ public class SaveFileManager : ISaveFileManager var upgradeDependencies = new Blackboard(); - JsonOptions = new JsonSerializerOptions + _jsonOptions = new JsonSerializerOptions { Converters = { new SerializableTypeConverter(upgradeDependencies) @@ -39,18 +38,36 @@ public class SaveFileManager : ISaveFileManager } - public async Task ReadFromFile() + public Task ReadFromFile() { - if (!_fileSystem.File.Exists(SaveFilePath)) + if (!_fileSystem.File.Exists(_defaultSaveLocation)) + throw new FileNotFoundException(); + return ReadFromFile(_defaultSaveLocation); + } + + public async Task ReadFromFile(string filePath) + { + if (!_fileSystem.File.Exists(filePath)) throw new FileNotFoundException(); - var json = await _fileSystem.File.ReadAllTextAsync(SaveFilePath); - return JsonSerializer.Deserialize(json, JsonOptions); + var json = await _fileSystem.File.ReadAllTextAsync(filePath); + + return JsonSerializer.Deserialize(json, _jsonOptions); } - public async Task WriteToFile(T gameData) + public Task WriteToFile(T gameData) { - var json = JsonSerializer.Serialize(gameData, JsonOptions); - await _fileSystem.File.WriteAllTextAsync(SaveFilePath, json); + return WriteToFile(gameData, _defaultSaveLocation, _jsonOptions); + } + + public Task WriteToFile(T gameData, string filePath) + { + return WriteToFile(gameData, filePath, _jsonOptions); + } + + public async Task WriteToFile(T gameData, string filePath, JsonSerializerOptions jsonOptions) + { + var json = JsonSerializer.Serialize(gameData, jsonOptions); + await _fileSystem.File.WriteAllTextAsync(filePath, json); } } diff --git a/Zennysoft.Game.Ma/src/game/Game.cs b/Zennysoft.Game.Ma/src/game/Game.cs index b8c3818e..ccfee439 100644 --- a/Zennysoft.Game.Ma/src/game/Game.cs +++ b/Zennysoft.Game.Ma/src/game/Game.cs @@ -80,8 +80,6 @@ public partial class Game : Node3D, IGame private EffectService _effectService; - private double _expRate = 1; - public void Setup() { _container = new SimpleInjector.Container(); @@ -194,13 +192,15 @@ public partial class Game : Node3D, IGame .Handle((in GameLogic.Output.ShowFloorClearMenu _) => { FloorClearMenu.Show(); FloorClearMenu.FadeIn(); }) .Handle((in GameLogic.Output.ExitFloorClearMenu _) => { FloorClearMenu.FadeOut(); }) .Handle((in GameLogic.Output.OpenInventory _) => { InGameUI.ShowInventoryScreen(); InGameUI.InventoryMenu.SetProcessInput(true); }) - .Handle((in GameLogic.Output.HideInventory _) => { InGameUI.HideInventoryScreen(); InGameUI.InventoryMenu.SetProcessInput(false); }) + .Handle((in GameLogic.Output.CloseInventory _) => { InGameUI.HideInventoryScreen(); InGameUI.InventoryMenu.SetProcessInput(false); }) .Handle((in GameLogic.Output.ShowMiniMap _) => { InGameUI.ShowMiniMap(); }) .Handle((in GameLogic.Output.HideMiniMap _) => { InGameUI.HideMiniMap(); }) .Handle((in GameLogic.Output.ShowAskForTeleport _) => { GameRepo.Pause(); InGameUI.UseTeleportPrompt.FadeIn(); InGameUI.SetProcessInput(true); }) .Handle((in GameLogic.Output.HideAskForTeleport _) => { GameRepo.Resume(); InGameUI.UseTeleportPrompt.FadeOut(); InGameUI.SetProcessInput(false); }) .Handle((in GameLogic.Output.ShowLostScreen _) => { DeathMenu.Show(); DeathMenu.FadeIn(); }) .Handle((in GameLogic.Output.ExitLostScreen _) => { DeathMenu.FadeOut(); }) + .Handle((in GameLogic.Output.AnnounceMessage output) => { AnnounceMessageOnMainScreen(output.Message); }) + .Handle((in GameLogic.Output.DoubleExpTimeStart output) => { DoubleEXPTimer.WaitTime = output.lengthOfTimeInSeconds; DoubleEXPTimer.Start(); }) .Handle((in GameLogic.Output.SaveGame _) => { SaveFile.Save(); @@ -284,9 +284,6 @@ public partial class Game : Node3D, IGame { switch (effectItem.UsableItemTag) { - case UsableItemTag.DoubleEXP: - DoubleEXP(TimeSpan.FromSeconds(30)); - break; case UsableItemTag.TeleportAllEnemiesToRoom: _effectService.TeleportEnemiesToCurrentRoom(); break; @@ -324,6 +321,9 @@ public partial class Game : Node3D, IGame } if (item is ThrowableItem throwableItem) { + if (throwableItem.ThrowableItemTag == ThrowableItemTag.DoubleExp) + GameRepo.StartDoubleEXP(TimeSpan.FromSeconds(30)); + if (throwableItem.HealHPAmount > 0) Player.HealHP(throwableItem.HealHPAmount); if (throwableItem.HealVTAmount > 0) @@ -373,7 +373,7 @@ public partial class Game : Node3D, IGame public void EnemyDefeated(Vector3 defeatedLocation, EnemyStatResource resource) { - Player.GainExp(resource.ExpFromDefeat * _expRate); + Player.GainExp(resource.ExpFromDefeat * GameRepo.ExpRate); } private void DropRestorative(Vector3 vector) @@ -441,19 +441,9 @@ public partial class Game : Node3D, IGame GetTree().Paused = isPaused; } - public async void DoubleEXP(TimeSpan lengthOfEffect) - { - ToggleInventory(); - AnnounceMessageOnMainScreen("Experience points temporarily doubled."); - DoubleEXPTimer.Start(lengthOfEffect.Seconds); - _expRate = 2; - } - private void DoubleEXPTimer_Timeout() { - DoubleEXPTimer.Stop(); - _expRate = 1; - AnnounceMessageOnMainScreen("Experience points effect wore off."); + GameRepo.EndDoubleExp(); } private void InventoryMenu_CloseInventory() => GameLogic.Input(new GameLogic.Input.CloseInventory()); diff --git a/Zennysoft.Game.Ma/src/game/IGame.cs b/Zennysoft.Game.Ma/src/game/IGame.cs index e8bb4236..0564b886 100644 --- a/Zennysoft.Game.Ma/src/game/IGame.cs +++ b/Zennysoft.Game.Ma/src/game/IGame.cs @@ -26,8 +26,6 @@ public interface IGame : IProvide, IProvide, IProvid public void ThrowItem(InventoryItem item); - public void DoubleEXP(TimeSpan lengthOfEffect); - public void ToggleInventory(); public void ToggleMinimap(); diff --git a/Zennysoft.Game.Ma/src/items/throwable/resources/SpellSignKnowledge.tres b/Zennysoft.Game.Ma/src/items/throwable/resources/SpellSignKnowledge.tres index 3ed0f442..f8fd65c6 100644 --- a/Zennysoft.Game.Ma/src/items/throwable/resources/SpellSignKnowledge.tres +++ b/Zennysoft.Game.Ma/src/items/throwable/resources/SpellSignKnowledge.tres @@ -5,8 +5,9 @@ [resource] script = ExtResource("1_s3pq7") -ThrowableItemTags = Array[int]([]) -UsableItemTags = Array[int]([0]) +ThrowableItemTag = 1 +ElementType = 0 +UsableItemTag = 0 Name = "Spell Sign: Knowledge" Description = "Doubles experience points earned. Effect is temporary." Texture = ExtResource("1_3605p") @@ -15,3 +16,4 @@ ThrowSpeed = 12.0 HealHPAmount = 0 HealVTAmount = 0 ThrowDamage = 5 +ItemTag = 0 diff --git a/Zennysoft.Game.Ma/src/map/dungeon/floors/Floor00.tscn b/Zennysoft.Game.Ma/src/map/dungeon/floors/Floor00.tscn index 5cf05065..fbe2f1ec 100644 --- a/Zennysoft.Game.Ma/src/map/dungeon/floors/Floor00.tscn +++ b/Zennysoft.Game.Ma/src/map/dungeon/floors/Floor00.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=61 format=4 uid="uid://dl6h1djc27ddl"] +[gd_scene load_steps=63 format=4 uid="uid://dl6h1djc27ddl"] [ext_resource type="Script" uid="uid://c1nhqlem1ew3m" path="res://src/map/dungeon/code/Floor0.cs" id="1_db2o3"] [ext_resource type="Texture2D" uid="uid://b27ksiyfefb33" path="res://src/map/dungeon/models/Set A/02. Altar/02_ALTAR_FLOOR_ZER0_VER_outside_desert.png" id="2_xh2ej"] @@ -15,6 +15,8 @@ [ext_resource type="Texture2D" uid="uid://dyufabjcwlago" path="res://src/map/dungeon/models/Set A/02. Altar/02_ALTAR_FLOOR_ZER0_VER_HAND_CYCLE_MOTIF.png" id="13_1i307"] [ext_resource type="Texture2D" uid="uid://4k6vtn4oip5f" path="res://src/map/dungeon/models/Set A/02. Altar/02_ALTAR_FLOOR_ZER0_VER_TILE4.png" id="14_qqc7i"] [ext_resource type="Texture2D" uid="uid://cururtxtgylxf" path="res://src/map/dungeon/models/Set A/02. Altar/02_ALTAR_FLOOR_ZER0_VER_COLUMN.jpg" id="15_ojbcg"] +[ext_resource type="PackedScene" uid="uid://1fl6s352e2ej" path="res://src/items/throwable/ThrowableItem.tscn" id="16_db2o3"] +[ext_resource type="Resource" uid="uid://qqg0gdcb8fwg" path="res://src/items/throwable/resources/SpellSignKnowledge.tres" id="17_ntxe5"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3ubi4"] shading_mode = 0 @@ -896,3 +898,7 @@ shape = SubResource("BoxShape3D_db2o3") [node name="Camera3D" type="Camera3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.00384, 1.80761, 11.3571) + +[node name="ThrowableItem" parent="." instance=ExtResource("16_db2o3")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.78811, -3.32789, 0) +_throwableItemStats = ExtResource("17_ntxe5")