Refactoring game logic

This commit is contained in:
2025-03-07 12:05:20 -08:00
parent e7ef669c29
commit c3bfab5f53
13 changed files with 127 additions and 42 deletions

View File

@@ -6,5 +6,11 @@ public interface ISaveFileManager<T>
{
public Task WriteToFile(T gameData);
public Task<T> ReadFromFile();
public Task WriteToFile(T gameData, string filePath);
public Task WriteToFile(T gameData, string filePath, JsonSerializerOptions jsonOptions);
public Task<T?> ReadFromFile();
public Task<T?> ReadFromFile(string filePath);
}

View File

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

View File

@@ -14,14 +14,30 @@ public partial class GameLogic
{
var gameRepo = Get<IGameRepo>();
gameRepo.IsPaused.Sync += OnIsPaused;
gameRepo.OpenInventory += OnOpenInventory;
gameRepo.CloseInventory += OnCloseInventory;
gameRepo.AnnounceMessage += OnAnnounceMessage;
gameRepo.DoubleExpTimeStart += OnDoubleExpTimeStart;
});
OnDetach(() =>
{
var gameRepo = Get<IGameRepo>();
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));
}
}

View File

@@ -5,27 +5,51 @@ namespace Zennysoft.Game.Ma.Implementation;
public interface IGameRepo : IDisposable
{
event Action? Ended;
event Action? OpenInventory;
event Action? CloseInventory;
event Action<string>? AnnounceMessage;
event Action<int>? DoubleExpTimeStart;
event Action? DoubleExpTimeEnd;
void Pause();
void Resume();
IAutoProp<bool> 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<string>? AnnounceMessage;
public event Action<int>? DoubleExpTimeStart;
public event Action? DoubleExpTimeEnd;
public IAutoProp<bool> IsPaused => _isPaused;
private readonly AutoProp<bool> _isPaused;
public int MaxItemSize => 20;
public double ExpRate { get; private set; }
private bool _disposedValue;
public GameRepo()
{
_isPaused = new AutoProp<bool>(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();

View File

@@ -13,7 +13,7 @@ public partial class GameLogic
public InventoryOpened()
{
this.OnEnter(() => { Get<IGameRepo>().Pause(); Output(new Output.OpenInventory()); });
this.OnExit(() => { Get<IGameRepo>().Resume(); Output(new Output.HideInventory()); });
this.OnExit(() => { Get<IGameRepo>().Resume(); Output(new Output.CloseInventory()); });
}
public Transition On(in Input.CloseInventory input)

View File

@@ -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 = "<Pending>", Scope = "namespace", Target = "~N:Zennysoft.Game.Ma.Implementation")]

View File

@@ -3,6 +3,7 @@
public enum ThrowableItemTag
{
None,
DoubleExp,
LowerTargetTo1HP,
CanChangeAffinity,
TeleportToRandomLocation,

View File

@@ -3,7 +3,6 @@
public enum UsableItemTag
{
None,
DoubleEXP,
IdentifyAllItemsCostHP,
BriefImmunity,
SwapHPAndVT,

View File

@@ -12,15 +12,14 @@ namespace Zennysoft.Game.Ma.Implementation;
public class SaveFileManager<T> : ISaveFileManager<T>
{
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<T> : ISaveFileManager<T>
var upgradeDependencies = new Blackboard();
JsonOptions = new JsonSerializerOptions
_jsonOptions = new JsonSerializerOptions
{
Converters = {
new SerializableTypeConverter(upgradeDependencies)
@@ -39,18 +38,36 @@ public class SaveFileManager<T> : ISaveFileManager<T>
}
public async Task<T> ReadFromFile()
public Task<T?> ReadFromFile()
{
if (!_fileSystem.File.Exists(SaveFilePath))
if (!_fileSystem.File.Exists(_defaultSaveLocation))
throw new FileNotFoundException();
return ReadFromFile(_defaultSaveLocation);
}
public async Task<T?> ReadFromFile(string filePath)
{
if (!_fileSystem.File.Exists(filePath))
throw new FileNotFoundException();
var json = await _fileSystem.File.ReadAllTextAsync(SaveFilePath);
return JsonSerializer.Deserialize<T>(json, JsonOptions);
var json = await _fileSystem.File.ReadAllTextAsync(filePath);
return JsonSerializer.Deserialize<T?>(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);
}
}

View File

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

View File

@@ -26,8 +26,6 @@ public interface IGame : IProvide<IGameRepo>, IProvide<IGameEventDepot>, IProvid
public void ThrowItem(InventoryItem item);
public void DoubleEXP(TimeSpan lengthOfEffect);
public void ToggleInventory();
public void ToggleMinimap();

View File

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

View File

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