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.Value() => GameRepo; IGame IProvide.Value() => this; IPlayer IProvide.Value() => Player; IGameEventDepot IProvide.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(); #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 SaveFile { get; set; } = default!; public ISaveChunk GameChunk { get; set; } = default!; ISaveChunk IProvide>.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(); _container.Register, SaveFileManager>(); _container.Register(Lifestyle.Singleton); _container.Register(Lifestyle.Singleton); _container.Verify(); GameRepo = _container.GetInstance(); GameLogic = _container.GetInstance(); 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( (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>(); SaveFile = new SaveFile( 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("res://src/items/dropped/DroppedItem.tscn"); var dropped = droppedScene.Instantiate(); dropped.Item = item; AddChild(dropped); dropped.Drop(); } public void ThrowItem(IInventoryItem item) { var thrownScene = GD.Load("res://src/items/thrown/ThrownItem.tscn"); var thrown = thrownScene.Instantiate(); 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("res://src/items/restorative/Restorative.tscn"); var restorative = restorativeScene.Instantiate(); 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); } }