Files
GameJamDungeon/Zennysoft.Game.Ma/src/game/Game.cs
2025-03-08 13:49:46 -08:00

441 lines
14 KiB
C#

namespace Zennysoft.Game.Ma;
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Chickensoft.SaveFileBuilder;
using Godot;
using System;
using System.IO.Abstractions;
using System.Text.Json;
using Zennysoft.Game.Abstractions;
using Zennysoft.Ma.Godot.Adapter;
using System.IO;
using SimpleInjector;
using System.Threading.Tasks;
[Meta(typeof(IAutoNode))]
public partial class Game : Node3D, IGame
{
public override void _Notification(int what) => this.Notify(what);
IGameRepo IProvide<IGameRepo>.Value() => GameRepo;
IGame IProvide<IGame>.Value() => this;
IPlayer IProvide<IPlayer>.Value() => Player;
IGameEventDepot IProvide<IGameEventDepot>.Value() => GameEventDepot;
private static SimpleInjector.Container _container;
public IInstantiator Instantiator { get; set; } = default!;
public IGameLogic GameLogic { get; set; } = default!;
public IGameEventDepot GameEventDepot { get; set; } = default!;
public IGameRepo GameRepo { get; set; } = default!;
public GameLogic.IBinding GameBinding { get; set; } = default!;
[Signal]
public delegate void StatRaisedAlertEventHandler(string statRaisedAlert);
[Dependency] public IAppRepo AppRepo => this.DependOn<IAppRepo>();
#region Nodes
[Node] private IMap Map { get; set; } = default!;
[Node] private InGameUI InGameUI { get; set; } = default!;
[Node] private IFloorClearMenu FloorClearMenu { get; set; } = default!;
[Node] private DeathMenu DeathMenu { get; set; } = default!;
[Node] private IPauseMenu PauseMenu { get; set; } = default!;
[Node] private InGameAudio InGameAudio { get; set; } = default!;
[Node] private Timer DoubleEXPTimer { get; set; } = default!;
[Node] private IPlayer Player { get; set; } = default!;
#endregion
#region Save
public JsonSerializerOptions JsonOptions { get; set; } = default!;
public ISaveFile<GameData> SaveFile { get; set; } = default!;
public ISaveChunk<GameData> GameChunk { get; set; } = default!;
ISaveChunk<GameData> IProvide<ISaveChunk<GameData>>.Value() => GameChunk;
[Signal]
public delegate void SaveFileLoadedEventHandler();
#endregion
public RescuedItemDatabase RescuedItems { get; set; } = default!;
private EffectService _effectService;
public void Setup()
{
_container = new SimpleInjector.Container();
_container.Register<IFileSystem, FileSystem>();
_container.Register<ISaveFileManager<GameData>, SaveFileManager<GameData>>();
_container.Register<IGameRepo, GameRepo>(Lifestyle.Singleton);
_container.Register<IGameLogic, GameLogic>(Lifestyle.Singleton);
_container.Verify();
GameRepo = _container.GetInstance<IGameRepo>();
GameLogic = _container.GetInstance<IGameLogic>();
GameEventDepot = new GameEventDepot();
GameLogic.Set(GameRepo);
GameLogic.Set(AppRepo);
GameLogic.Set(GameEventDepot);
GameLogic.Set(Player);
GameLogic.Set(Map);
GameLogic.Set(InGameUI);
Instantiator = new Instantiator(GetTree());
RescuedItems = new RescuedItemDatabase();
GameChunk = new SaveChunk<GameData>(
(chunk) =>
{
var gameData = new GameData()
{
PlayerData = new PlayerData()
{
PlayerStats = new PlayerStats()
{
CurrentHP = Player.Stats.CurrentHP.Value,
MaximumHP = Player.Stats.MaximumHP.Value,
CurrentVT = Player.Stats.CurrentVT.Value,
MaximumVT = Player.Stats.MaximumVT.Value,
CurrentAttack = Player.Stats.CurrentAttack.Value,
BonusAttack = Player.Stats.BonusAttack.Value,
MaxAttack = Player.Stats.MaxAttack.Value,
CurrentDefense = Player.Stats.CurrentDefense.Value,
BonusDefense = Player.Stats.BonusDefense.Value,
MaxDefense = Player.Stats.MaxDefense.Value,
CurrentExp = Player.Stats.CurrentExp.Value,
CurrentLevel = Player.Stats.CurrentLevel.Value,
ExpToNextLevel = Player.Stats.ExpToNextLevel.Value,
Luck = Player.Stats.Luck.Value
},
Inventory = Player.Inventory
},
MapData = new MapData()
{
FloorScenes = Map.FloorScenes
},
RescuedItems = new RescuedItemDatabase() { Items = RescuedItems.Items }
};
return gameData;
},
onLoad: (chunk, data) =>
{
RescuedItems = data.RescuedItems;
chunk.LoadChunkSaveData(data.PlayerData);
chunk.LoadChunkSaveData(data.MapData);
}
);
}
public void OnResolved()
{
var saveFileManager = _container.GetInstance<ISaveFileManager<GameData>>();
SaveFile = new SaveFile<GameData>(
root: GameChunk,
onSave: saveFileManager.WriteToFile,
onLoad: async () =>
{
try
{
var gameData = await saveFileManager.ReadFromFile();
return gameData;
}
catch (FileNotFoundException)
{
GD.Print("No save file found.");
}
return null;
}
);
GameBinding = GameLogic.Bind();
GameBinding
.Handle((in GameLogic.Output.StartGame _) =>
{
GameRepo.Resume();
InGameUI.Show();
})
.Handle((in GameLogic.Output.GoToOverworld _) =>
{
GameEventDepot.OnOverworldEntered();
})
.Handle((in GameLogic.Output.SetPauseMode output) => CallDeferred(nameof(SetPauseMode), output.IsPaused))
.Handle((in GameLogic.Output.ShowPauseMenu _) =>
{
PauseMenu.Show();
PauseMenu.FadeIn();
Input.MouseMode = Input.MouseModeEnum.Visible;
PauseMenu.SetProcessUnhandledInput(true);
})
.Handle((in GameLogic.Output.HidePauseMenu _) => { PauseMenu.Hide(); })
.Handle((in GameLogic.Output.ExitPauseMenu _) => { PauseMenu.FadeOut(); Input.MouseMode = Input.MouseModeEnum.Visible; PauseMenu.SetProcessUnhandledInput(false); })
.Handle((in GameLogic.Output.LoadNextFloor _) => { Map.SpawnNextFloor(); })
.Handle((in GameLogic.Output.LoadMap _) => { Map.LoadMap(); })
.Handle((in GameLogic.Output.ShowFloorClearMenu _) => { FloorClearMenu.Show(); FloorClearMenu.FadeIn(); })
.Handle((in GameLogic.Output.ExitFloorClearMenu _) => { FloorClearMenu.FadeOut(); })
.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.DoubleExpTimeStart output) => { DoubleEXPTimer.WaitTime = output.lengthOfTimeInSeconds; DoubleEXPTimer.Start(); })
.Handle((in GameLogic.Output.SaveGame _) =>
{
SaveFile.Save();
AppRepo.OnExitGame();
GetTree().Quit();
// Back to title screen
});
GameLogic.Start();
GameLogic.Input(new GameLogic.Input.Initialize());
this.Provide();
PauseMenu.TransitionCompleted += OnPauseMenuTransitioned;
PauseMenu.UnpauseButtonPressed += PauseMenu_UnpauseButtonPressed;
InGameUI.UseTeleportPrompt.TeleportToNextFloor += UseTeleportPrompt_TeleportToNextFloor;
InGameUI.UseTeleportPrompt.CloseTeleportPrompt += UseTeleportPrompt_CloseTeleportPrompt;
FloorClearMenu.GoToNextFloor += FloorClearMenu_GoToNextFloor;
FloorClearMenu.SaveAndExit += FloorClearMenu_SaveAndExit;
FloorClearMenu.TransitionCompleted += FloorClearMenu_TransitionCompleted;
GameEventDepot.RestorativePickedUp += GameEventDepot_RestorativePickedUp;
DoubleEXPTimer.Timeout += DoubleEXPTimer_Timeout;
_effectService = new EffectService(this, Player);
}
public void LoadExistingGame()
{
SaveFile.Load()
.ContinueWith((_) => CallDeferred(nameof(FinishedLoadingSaveFile)));
}
public void TogglePause()
{
if (GameLogic.Value is GameLogic.State.Paused)
GameLogic.Input(new GameLogic.Input.UnpauseGame());
else
GameLogic.Input(new GameLogic.Input.PauseGame());
}
public void FloorExitReached()
{
GameLogic.Input(new GameLogic.Input.AskForTeleport());
GameEventDepot.OnTeleportEntered();
}
public async Task UseItem(IInventoryItem item)
{
if (item is ConsumableItem consumableItem)
{
if (Player.Stats.CurrentHP == Player.Stats.MaximumHP && consumableItem.RaiseHPAmount > 0)
Player.RaiseHP(consumableItem.RaiseHPAmount);
if (Player.Stats.CurrentVT == Player.Stats.MaximumVT && consumableItem.RaiseVTAmount > 0)
Player.RaiseVT(consumableItem.RaiseVTAmount);
if (consumableItem.HealHPAmount > 0 && Player.Stats.CurrentHP != Player.Stats.MaximumHP)
Player.HealHP(consumableItem.HealHPAmount);
if (consumableItem.HealVTAmount > 0 && Player.Stats.CurrentVT != Player.Stats.MaximumVT)
Player.HealVT(consumableItem.HealVTAmount);
}
if (item is EffectItem effectItem)
{
switch (effectItem.UsableItemTag)
{
case UsableItemTag.TeleportAllEnemiesToRoom:
_effectService.TeleportEnemiesToCurrentRoom();
break;
case UsableItemTag.KillHalfEnemiesInRoom:
_effectService.KillHalfEnemiesInRoom();
break;
case UsableItemTag.TurnAllEnemiesIntoHealingItem:
_effectService.TurnAllEnemiesInRoomIntoHealingItem();
break;
case UsableItemTag.HealsAllInRoomToMaxHP:
_effectService.HealAllEnemiesAndPlayerInRoomToFull();
break;
case UsableItemTag.AbsorbHPFromAllEnemiesInRoom:
_effectService.AbsorbHPFromAllEnemiesInRoom();
break;
case UsableItemTag.DealElementalDamageToAllEnemiesInRoom:
_effectService.DealElementalDamageToAllEnemiesInRoom(ElementType.Hydric);
break;
case UsableItemTag.SwapHPAndVT:
_effectService.SwapHPandVT();
break;
case UsableItemTag.RaiseCurrentWeaponAttack:
_effectService.RaiseCurrentWeaponAttack();
break;
case UsableItemTag.RaiseCurrentDefenseArmor:
_effectService.RaiseCurrentArmorDefense();
break;
case UsableItemTag.RaiseLevel:
_effectService.RaiseLevel();
break;
case UsableItemTag.RandomEffect:
_effectService.RandomEffect(effectItem);
break;
}
}
if (item is ThrowableItem throwableItem)
{
switch (throwableItem.ThrowableItemTag)
{
case ThrowableItemTag.DoubleExp:
GameRepo.StartDoubleEXP(TimeSpan.FromSeconds(30));
break;
case ThrowableItemTag.TeleportToRandomLocation:
_effectService.TeleportToRandomRoom(Player);
InGameUI.CloseInventory();
break;
case ThrowableItemTag.CanChangeAffinity:
_effectService.ChangeAffinity(throwableItem);
break;
case ThrowableItemTag.WarpToExitIfFound:
_effectService.WarpToExit(Player);
InGameUI.CloseInventory();
break;
}
if (throwableItem.HealHPAmount > 0)
Player.HealHP(throwableItem.HealHPAmount);
if (throwableItem.HealVTAmount > 0)
Player.HealVT(throwableItem.HealVTAmount);
}
await ToSignal(GetTree().CreateTimer(1f), "timeout");
GameRepo.RemoveItemFromInventory(item);
}
public void DropItem(IInventoryItem item)
{
var droppedScene = GD.Load<PackedScene>("res://src/items/dropped/DroppedItem.tscn");
var dropped = droppedScene.Instantiate<DroppedItem>();
dropped.Item = item;
AddChild(dropped);
dropped.Drop();
}
public void ThrowItem(IInventoryItem item)
{
var thrownScene = GD.Load<PackedScene>("res://src/items/thrown/ThrownItem.tscn");
var thrown = thrownScene.Instantiate<ThrownItem>();
thrown.ItemThatIsThrown = item;
AddChild(thrown);
thrown.Position += new Vector3(0, 1.5f, 0);
thrown.Throw(_effectService);
}
public IDungeonFloor CurrentFloor => Map.CurrentFloor;
public void EnemyDefeated(Vector3 defeatedLocation, EnemyStatResource resource)
{
Player.GainExp(resource.ExpFromDefeat * GameRepo.ExpRate);
}
private void DropRestorative(Vector3 vector)
{
var restorativeScene = GD.Load<PackedScene>("res://src/items/restorative/Restorative.tscn");
var restorative = restorativeScene.Instantiate<Restorative>();
AddChild(restorative);
restorative.GlobalPosition = vector;
}
private void UseTeleportPrompt_CloseTeleportPrompt()
{
GameLogic.Input(new GameLogic.Input.HideAskForTeleport());
}
private void UseTeleportPrompt_TeleportToNextFloor()
{
GameLogic.Input(new GameLogic.Input.FloorExitReached());
GameEventDepot.OnDungeonAThemeAreaEntered();
}
private void PauseMenu_UnpauseButtonPressed()
{
GameLogic.Input(new GameLogic.Input.UnpauseGame());
}
private void Player_PauseButtonPressed()
{
GameLogic.Input(new GameLogic.Input.PauseGame());
}
private void FloorClearMenu_TransitionCompleted()
{
GameRepo.Resume();
if (Player.EquippedWeapon.Value.ItemTag == ItemTag.BreaksOnChange)
Player.Unequip(Player.EquippedWeapon.Value);
if (Player.EquippedArmor.Value.ItemTag == ItemTag.BreaksOnChange)
Player.Unequip(Player.EquippedArmor.Value);
if (Player.EquippedAccessory.Value.ItemTag == ItemTag.BreaksOnChange)
Player.Unequip(Player.EquippedAccessory.Value);
}
private void FloorClearMenu_GoToNextFloor()
{
GameLogic.Input(new GameLogic.Input.GoToNextFloor());
}
private void FloorClearMenu_SaveAndExit()
{
// Save
GameLogic.Input(new GameLogic.Input.SaveGame());
}
private void GameEventDepot_RestorativePickedUp(Restorative obj)
=> Player.Stats.SetCurrentVT(Player.Stats.CurrentVT.Value + obj.VTRestoreAmount);
private void SetPauseMode(bool isPaused)
{
if (GetTree() != null)
GetTree().Paused = isPaused;
}
private void DoubleEXPTimer_Timeout()
{
GameRepo.EndDoubleExp();
}
public void NextFloorLoaded()
{
GameLogic.Input(new GameLogic.Input.HideFloorClearMenu());
}
private void OnPauseMenuTransitioned()
{
GameLogic.Input(new GameLogic.Input.PauseMenuTransitioned());
}
public void OnStart() =>
GameLogic.Input(new GameLogic.Input.StartGame());
private void FinishedLoadingSaveFile()
{
EmitSignal(SignalName.SaveFileLoaded);
}
}