namespace Zennysoft.Game.Ma; using Chickensoft.AutoInject; using Chickensoft.Introspection; using Chickensoft.SaveFileBuilder; using Godot; using System; using System.Text.Json; using Zennysoft.Game.Abstractions; using Zennysoft.Ma.Adapter; using System.IO; 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; IMap IProvide.Value() => Map; private static SimpleInjector.Container _container; public IInstantiator Instantiator { get; set; } = default!; public IGameState GameState { get; set; } = default!; public IGameRepo GameRepo { get; set; } = default!; public GameState.IBinding GameBinding { get; set; } = default!; [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 Timer DoubleEXPTimer { get; set; } = default!; [Node] private IPlayer Player { get; set; } = default!; [Node] private MainMenu MainMenu { 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(); Module.Bootstrap(_container); GameRepo = _container.GetInstance(); GameState = _container.GetInstance(); GameState.Set(GameRepo); GameState.Set(AppRepo); GameState.Set(Player); GameState.Set(Map); GameState.Set(InGameUI); Instantiator = new Instantiator(GetTree()); RescuedItems = new RescuedItemDatabase(); GameChunk = new SaveChunk( (chunk) => { var gameData = new GameData() { PlayerData = new PlayerData() { PlayerStats = Player.Stats, 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.Save, onLoad: async () => { try { var gameData = await saveFileManager.Load(); return gameData; } catch (FileNotFoundException) { GD.Print("No save file found."); } return null; } ); GameBinding = GameState.Bind(); GameBinding .Handle((in GameState.Output.InitializeGame _) => { InitializeGame(); Map.LoadMap(); GameRepo.Resume(); InGameUI.Show(); InGameUI.PlayerInfoUI.Activate(); Player.TeleportPlayer(Map.CurrentFloor.GetPlayerSpawnPoint()); Player.Activate(); Autoload.BgmPlayer.Play(BackgroundMusic.CrossingTheGate); }) .Handle((in GameState.Output.LoadGameFromFile _) => { LoadExistingGame(); }) .Handle((in GameState.Output.OpenPauseScreen _) => { GameRepo.Pause(); PauseMenu.Show(); PauseMenu.FadeIn(); Input.MouseMode = Input.MouseModeEnum.Visible; PauseMenu.SetProcessUnhandledInput(true); }) .Handle((in GameState.Output.ClosePauseScreen _) => { PauseMenu.FadeOut(); Input.MouseMode = Input.MouseModeEnum.Visible; PauseMenu.SetProcessUnhandledInput(false); GameRepo.Resume(); }) .Handle((in GameState.Output.OpenInventoryMenu _) => { InGameUI.InventoryMenu.RefreshInventoryScreen(); InGameUI.InventoryMenu.Show(); InGameUI.InventoryMenu.SetProcessInput(true); }) .Handle((in GameState.Output.CloseInventoryMenu _) => { InGameUI.InventoryMenu.Hide(); InGameUI.InventoryMenu.SetProcessInput(false); }) .Handle((in GameState.Output.OpenDebugMenu _) => { InGameUI.DebugMenu.Show(); InGameUI.PlayerInfoUI.Hide(); GameRepo.Pause(); }) .Handle((in GameState.Output.CloseDebugMenu _) => { InGameUI.DebugMenu.Hide(); InGameUI.PlayerInfoUI.Show(); GameRepo.Resume(); }) .Handle((in GameState.Output.OpenTeleportScreen _) => { InGameUI.UseTeleportPrompt.Show(); InGameUI.UseTeleportPrompt.FadeIn(); }) .Handle((in GameState.Output.OpenFloorExitScreen _) => { InGameUI.UseTeleportPrompt.FadeOut(); FloorClearMenu.Show(); FloorClearMenu.FadeIn(); }) .Handle((in GameState.Output.LoadNextFloor _) => { FloorClearMenu.FadeOut(); Map.SpawnNextFloor(); if (Player.EquippedWeapon.Value.ItemTag == ItemTag.BreaksOnChange) { var itemToDestroy = Player.EquippedWeapon.Value; Player.Unequip(itemToDestroy); Player.Inventory.Remove(itemToDestroy); } if (Player.EquippedArmor.Value.ItemTag == ItemTag.BreaksOnChange) { var itemToDestroy = Player.EquippedArmor.Value; Player.Unequip(itemToDestroy); Player.Inventory.Remove(itemToDestroy); } if (Player.EquippedAccessory.Value.ItemTag == ItemTag.BreaksOnChange) { var itemToDestroy = Player.EquippedAccessory.Value; Player.Unequip(itemToDestroy); Player.Inventory.Remove(itemToDestroy); } FloorClearMenu.FadeOut(); }) .Handle((in GameState.Output.GameOver _) => { GameRepo.Pause(); DeathMenu.FadeIn(); }); GameState.Start(); this.Provide(); InGameUI.UseTeleportPrompt.TeleportToNextFloor += UseTeleportPrompt_TeleportToNextFloor; InGameUI.UseTeleportPrompt.CloseTeleportPrompt += UseTeleportPrompt_CloseTeleportPrompt; FloorClearMenu.GoToNextFloor += FloorClearMenu_GoToNextFloor; FloorClearMenu.SaveAndExit += FloorClearMenu_SaveAndExit; FloorClearMenu.TransitionCompleted += FloorClearMenu_TransitionCompleted; GameRepo.RestorativePickedUp += GameEventDepot_RestorativePickedUp; DoubleEXPTimer.Timeout += DoubleEXPTimer_Timeout; MainMenu.NewGame += OnNewGame; MainMenu.LoadGame += OnLoadGame; MainMenu.Quit += OnQuit; DeathMenu.NewGame += OnContinueGame; DeathMenu.QuitGame += OnQuit; GameRepo.IsPaused.Sync += IsPaused_Sync; _effectService = new EffectService(this, Player, Map); MainMenu.Show(); } private void FloorClearMenu_SaveAndExit() { //SaveFile.Save(); Player.Deactivate(); GameState.Input(new GameState.Input.ReturnToMainMenu()); InGameUI.Hide(); MainMenu.FadeIn(); } private void FloorClearMenu_GoToNextFloor() => GameState.Input(new GameState.Input.LoadNextFloor()); public void LoadExistingGame() => SaveFile.Load().ContinueWith((_) => CallDeferred(nameof(FinishedLoadingSaveFile))); public void InitializeGame() { Map.InitializeMapData(); Player.InitializePlayerState(); } public void FloorExitReached() { GameState.Input(new GameState.Input.FloorExitEntered()); } public async Task UseItem(InventoryItem item) { if (item.ItemTag == ItemTag.MysteryItem) item = RerollItem(item); switch (item) { case ConsumableItem consumableItem: EnactConsumableItemEffects(consumableItem); break; case EffectItem effectItem: EnactEffectItemEffects(effectItem); break; case ThrowableItem throwableItem: EnactThrowableItemEffects(throwableItem); break; } await ToSignal(GetTree().CreateTimer(0.3f), "timeout"); RemoveItemOrSubtractFromItemCount(item); } public void DropItem(InventoryItem 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(InventoryItem 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); DropRestorative(defeatedLocation); } public InventoryItem RerollItem(InventoryItem itemToReroll, bool insertIntoInventory = true) { var itemDb = new ItemDatabase(); var currentIndex = Player.Inventory.Items.IndexOf(itemToReroll); if (insertIntoInventory) Player.Inventory.Remove(itemToReroll); InventoryItem rolledItem = null; if (itemToReroll is Weapon weapon) rolledItem = itemDb.PickItem(weapon); if (itemToReroll is Armor armor) rolledItem = itemDb.PickItem(armor); if (itemToReroll is Accessory accessory) rolledItem = itemDb.PickItem(accessory); if (itemToReroll is ThrowableItem throwableItem) rolledItem = itemDb.PickItem(throwableItem); if (itemToReroll is EffectItem effectItem) rolledItem = itemDb.PickItem(effectItem); if (itemToReroll is ConsumableItem consumableItem) rolledItem = itemDb.PickItem(consumableItem); if (insertIntoInventory) Player.Inventory.TryInsert(rolledItem, currentIndex); return rolledItem; } public void GameOver() { GameState.Input(new GameState.Input.GameOver()); } public override void _Input(InputEvent @event) { if (@event.IsActionPressed(GameInputs.Debug)) GameState.Input(new GameState.Input.DebugButtonPressed()); } 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() { GameState.Input(new GameState.Input.CloseTeleport()); InGameUI.UseTeleportPrompt.FadeOut(); GameRepo.Resume(); } private void UseTeleportPrompt_TeleportToNextFloor() => GameState.Input(new GameState.Input.UseTeleport()); private void FloorClearMenu_TransitionCompleted() { GameRepo.Resume(); } private void GameEventDepot_RestorativePickedUp(IHealthPack obj) => Player.Stats.SetCurrentVT(Player.Stats.CurrentVT.Value + (int)obj.RestoreAmount); private void IsPaused_Sync(bool isPaused) => GetTree().Paused = isPaused; private void DoubleEXPTimer_Timeout() => GameRepo.EndDoubleExp(); private void FinishedLoadingSaveFile() => EmitSignal(SignalName.SaveFileLoaded); private void EnactConsumableItemEffects(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); } private void EnactEffectItemEffects(EffectItem effectItem) { switch (effectItem.UsableItemTag) { case UsableItemTag.TeleportAllEnemiesToRoom: _effectService.TeleportEnemiesToCurrentRoom(); GameRepo.CloseInventory(); break; case UsableItemTag.KillHalfEnemiesInRoom: _effectService.KillHalfEnemiesInRoom(); GameRepo.CloseInventory(); break; case UsableItemTag.TurnAllEnemiesIntoHealingItem: _effectService.TurnAllEnemiesInRoomIntoHealingItem(); GameRepo.CloseInventory(); break; case UsableItemTag.HealsAllInRoomToMaxHP: _effectService.HealAllEnemiesAndPlayerInRoomToFull(); GameRepo.CloseInventory(); break; case UsableItemTag.AbsorbHPFromAllEnemiesInRoom: _effectService.AbsorbHPFromAllEnemiesInRoom(); GameRepo.CloseInventory(); break; case UsableItemTag.DealElementalDamageToAllEnemiesInRoom: _effectService.DealElementalDamageToAllEnemiesInRoom(effectItem.Stats.ElementalDamageType); GameRepo.CloseInventory(); break; case UsableItemTag.SwapHPAndVT: _effectService.SwapHPandVT(); GameRepo.CloseInventory(); 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; } } private void EnactThrowableItemEffects(ThrowableItem throwableItem) { switch (throwableItem.ThrowableItemTag) { case ThrowableItemTag.DoubleExp: GameRepo.StartDoubleEXP(TimeSpan.FromSeconds(30)); GameRepo.CloseInventory(); break; case ThrowableItemTag.TeleportToRandomLocation: _effectService.TeleportToRandomRoom(Player); GameRepo.CloseInventory(); break; case ThrowableItemTag.CanChangeAffinity: _effectService.ChangeAffinity(throwableItem); break; case ThrowableItemTag.WarpToExitIfFound: _effectService.WarpToExit(Player); GameRepo.CloseInventory(); break; } if (throwableItem.HealHPAmount > 0) Player.HealHP(throwableItem.HealHPAmount); if (throwableItem.HealVTAmount > 0) Player.HealVT(throwableItem.HealVTAmount); } private void RemoveItemOrSubtractFromItemCount(InventoryItem item) { if (item is IStackable stackableItem && stackableItem.Count > 1) stackableItem.SetCount(stackableItem.Count - 1); else GameRepo.RemoveItemFromInventory(item); } private void OnNewGame() { GameState.Input(new GameState.Input.NewGame()); MainMenu.Hide(); } private void OnContinueGame() { GameState.Input(new GameState.Input.ContinueGame()); } private void OnLoadGame() { GameState.Input(new GameState.Input.LoadGame()); MainMenu.Hide(); } private void OnQuit() { MainMenu.Hide(); } }