namespace Zennysoft.Game.Ma; using Chickensoft.AutoInject; using Chickensoft.Introspection; using Chickensoft.SaveFileBuilder; using Godot; using System; using System.Text.Json; using Zennysoft.Ma.Adapter; using System.IO; using System.Threading.Tasks; using Zennysoft.Game.Implementation; using Zennysoft.Ma.Adapter.Entity; [Meta(typeof(IAutoNode))] public partial class Game : Node3D, IGame { public override void _Notification(int what) => this.Notify(what); IGame IProvide.Value() => this; IGameRepo IProvide.Value() => GameRepo; IPlayer IProvide.Value() => _player; IMap IProvide.Value() => _map; private static SimpleInjector.Container _container; public IGameState GameState { get; set; } = default!; public IGameRepo GameRepo { get; set; } = default!; public GameState.IBinding GameBinding { get; set; } = default!; #region Nodes [Node] private Node PauseContainer { get; set; } = default!; [Node] private InGameUI InGameUI { get; set; } = default!; [Node] private IFloorClearMenu LoadNextLevel { get; set; } = default!; [Node] private IGameOverMenu GameOverMenu { get; set; } = default!; [Node] private IPauseMenu PauseMenu { 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(); public event Action GameExitRequested; public event Action GameLoaded; #endregion public RescuedItemDatabase RescuedItems { get; set; } = default!; public ItemDatabase ItemDatabase { get; private set; } public QuestData QuestData { get; private set; } public ItemRescueMenu ItemRescueMenu { get => InGameUI.ItemRescueMenu; } private EffectService _effectService; private IInstantiator _instantiator; private IPlayer _player; private IMap _map; [Signal] private delegate void OnLoadLevelRequestEventHandler(); public Game() { _container = new SimpleInjector.Container(); Module.Bootstrap(_container); GameRepo = _container.GetInstance(); GameState = _container.GetInstance(); QuestData = new QuestData(); RescuedItems = new RescuedItemDatabase(); ItemDatabase = ItemDatabase.Instance; GameChunk = new SaveChunk( (chunk) => { var gameData = new GameData() { RescuedItems = new RescuedItemDatabase() { Items = RescuedItems.Items }, QuestData = new QuestData() { DeathCount = QuestData.DeathCount, QuestMarker1 = QuestData.QuestMarker1 } }; return gameData; }, onLoad: (chunk, data) => { RescuedItems = data.RescuedItems; QuestData = data.QuestData; } ); 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; } ); } public void Setup() { _instantiator = new Instantiator(GetTree()); _player = _instantiator.LoadAndInstantiate("res://src/player/Player.tscn"); _map = _instantiator.LoadAndInstantiate("res://src/map/Map.tscn"); _map.SpawnPointCreated += MovePlayer; PauseContainer.AddChild((Player)_player); PauseContainer.AddChild((Map)_map); } public async void OnResolved() { LoadExistingGame(); await InitializeGame(); GameState.Set(GameRepo); GameState.Set(_player); GameState.Set(_map); GameState.Set(InGameUI); GameRepo.Resume(); InGameUI.Show(); HandleGameLogic(); GameState.Start(); this.Provide(); InGameUI.UseTeleportPrompt.TeleportToNextFloor += UseTeleportPrompt_TeleportToNextFloor; InGameUI.UseTeleportPrompt.CloseTeleportPrompt += UseTeleportPrompt_CloseTeleportPrompt; LoadNextLevel.GoToNextFloor += FloorClearMenu_GoToNextFloor; LoadNextLevel.Exit += FloorClearMenu_Exit; LoadNextLevel.TransitionCompleted += FloorClearMenu_TransitionCompleted; OnLoadLevelRequest += LoadLevel; GameRepo.CloseInventoryEvent += ExitInventoryAction; GameRepo.EnemyDied += GameRepo_EnemyDied; _player.Inventory.BroadcastMessage += BroadcastMessage; _map.FloorLoaded += OnFloorLoadFinished; _player.PlayerDied += GameOver; GameOverMenu.NewGame += OnNewGame; GameOverMenu.QuitGame += OnQuit; PauseMenu.ExitGamePressed += OnQuit; GameRepo.IsPaused.Sync += IsPaused_Sync; InGameUI.PlayerInfoUI.Activate(); } private void GameRepo_EnemyDied(IEnemy obj) { DropRestorative(obj.GlobalPosition); } private void BroadcastMessage(string obj) { InGameUI.InventoryMessageUI.DisplayMessage(obj); } public void LoadExistingGame() => SaveFile.Load().ContinueWith((_) => CallDeferred(nameof(FinishedLoadingSaveFile))); public async Task InitializeGame() { _player.ResetPlayerData(); _map.InitializeMapData(); _effectService = new EffectService(this, _player, _map); _player.Activate(); await _map.LoadFloor(); GameLoaded?.Invoke(); } public async Task Save() => await SaveFile.Save(); public void FloorExitReached() => GameState.Input(new GameState.Input.FloorExitEntered()); public async Task UseItem(IBaseInventoryItem item) { if (item.ItemTag == ItemTag.MysteryItem) _effectService.RerollItem(item); switch (item) { case BoxItem boxItem: EnactBoxItemEffects(boxItem); break; case ConsumableItem consumableItem: EnactConsumableItemEffects(consumableItem); break; case EffectItem effectItem: EnactEffectItemEffects(effectItem); break; } await ToSignal(GetTree().CreateTimer(0.3f), "timeout"); RemoveItemOrSubtractFromItemCount(item); } public void DropItem(IBaseInventoryItem item) { var droppedScene = GD.Load("res://src/items/dropped/DroppedItem.tscn"); var dropped = droppedScene.Instantiate(); dropped.Item = item; _map.AddChild(dropped); dropped.Drop(); _player.Inventory.Remove(item); } public void SetItem(IBaseInventoryItem item) { var setScene = GD.Load("res://src/items/misc/SetItem.tscn"); var setItem = setScene.Instantiate(); _map.AddChild(setItem); setItem.Set(); _player.Inventory.Remove(item); } public void ThrowItem(IBaseInventoryItem item) { var thrownScene = GD.Load("res://src/items/thrown/ThrownItem.tscn"); var thrown = thrownScene.Instantiate(); thrown.ItemThatIsThrown = item; _map.AddChild(thrown); thrown.Throw(_effectService); RemoveItemOrSubtractFromItemCount(item); } public IDungeonFloor CurrentFloor => _map.CurrentFloor; public async void GameOver() { QuestData.DeathCount++; await Save(); _player.Deactivate(); GameState.Input(new GameState.Input.GameOver()); } public override void _Input(InputEvent @event) { if (@event.IsActionPressed(GameInputs.Debug)) GameState.Input(new GameState.Input.DebugButtonPressed()); if (@event.IsActionPressed(GameInputs.Pause)) GameState.Input(new GameState.Input.PauseButtonPressed()); if (Input.IsActionJustPressed(GameInputs.Inventory)) GameState.Input(new GameState.Input.InventoryButtonPressed()); if (Input.IsActionJustPressed(GameInputs.Interact)) GameState.Input(new GameState.Input.InteractButtonPressed()); } private void HandleGameLogic() { GameBinding = GameState.Bind(); GameBinding .Handle((in GameState.Output.InitializeGame _) => { LoadExistingGame(); InitializeGame(); }) .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 _) => { GameRepo.Pause(); InGameUI.InventoryMenu.Show(); InGameUI.InventoryMenu.SetProcessInput(true); }) .Handle((in GameState.Output.CloseInventoryMenu _) => { CloseInventory(); }) .Handle((in GameState.Output.OpenDebugMenu _) => { InGameUI.DebugMenu.Show(); InGameUI.PlayerInfoUI.Hide(); _player.Deactivate(); }) .Handle((in GameState.Output.CloseDebugMenu _) => { InGameUI.DebugMenu.Hide(); InGameUI.PlayerInfoUI.Show(); _player.Activate(); }) .Handle((in GameState.Output.OpenTeleportScreen _) => { InGameUI.UseTeleportPrompt.Show(); InGameUI.UseTeleportPrompt.FadeIn(); }) .Handle((in GameState.Output.OpenFloorExitScreen _) => { InGameUI.UseTeleportPrompt.FadeOut(); LoadNextLevel.Show(); LoadNextLevel.FadeIn(); }) .Handle((in GameState.Output.LoadNextFloor _) => { LoadNextLevel.FadeOut(); EmitSignal(SignalName.OnLoadLevelRequest); Task.Run(() => Save()); if (_player.EquipmentComponent.EquippedWeapon.Value.ItemTag == ItemTag.BreaksOnChange) { var itemToDestroy = _player.EquipmentComponent.EquippedWeapon.Value; _player.Unequip(itemToDestroy); _player.Inventory.Remove(itemToDestroy); } if (_player.EquipmentComponent.EquippedArmor.Value.ItemTag == ItemTag.BreaksOnChange) { var itemToDestroy = _player.EquipmentComponent.EquippedArmor.Value; _player.Unequip(itemToDestroy); _player.Inventory.Remove(itemToDestroy); } if (_player.EquipmentComponent.EquippedAccessory.Value.ItemTag == ItemTag.BreaksOnChange) { var itemToDestroy = _player.EquipmentComponent.EquippedAccessory.Value; _player.Unequip(itemToDestroy); _player.Inventory.Remove(itemToDestroy); } LoadNextLevel.FadeOut(); }) .Handle((in GameState.Output.ExitGame _) => { OnQuit(); }) .Handle((in GameState.Output.GameOver _) => { GameRepo.Pause(); GameOverMenu.FadeIn(); }); } private void FloorClearMenu_Exit() { _player.Deactivate(); InGameUI.Hide(); } private void ExitInventoryAction() => GameState.Input(new GameState.Input.CloseInventory()); private void CloseInventory() { GameRepo.Resume(); InGameUI.InventoryMenu.Hide(); InGameUI.InventoryMenu.SetProcessInput(false); } private async void LoadLevel() => await _map.LoadFloor(); private void FloorClearMenu_GoToNextFloor() => GameState.Input(new GameState.Input.LoadNextFloor()); private void DropRestorative(Vector3 vector) { var restorativeScene = GD.Load("res://src/items/restorative/Restorative.tscn"); var restorative = restorativeScene.Instantiate(); AddChild(restorative); restorative.GlobalPosition = new Vector3(vector.X, 2f, vector.Z) + (-_player.GetGlobalBasis().Z); } private void UseTeleportPrompt_CloseTeleportPrompt() { GameState.Input(new GameState.Input.CloseTeleport()); InGameUI.UseTeleportPrompt.FadeOut(); GameRepo.Resume(); } private void UseTeleportPrompt_TeleportToNextFloor() { //_player.LookUp(); GameState.Input(new GameState.Input.UseTeleport()); } private void PointUpFinished() => GameState.Input(new GameState.Input.UseTeleport()); private void FloorClearMenu_TransitionCompleted() { GameRepo.Resume(); } private void IsPaused_Sync(bool isPaused) => GetTree().Paused = isPaused; private void FinishedLoadingSaveFile() => EmitSignal(SignalName.SaveFileLoaded); private void EnactBoxItemEffects(BoxItem boxItem) { switch (boxItem.ItemTag) { case ItemTag.DamagesPlayer: _effectService.DamagesPlayer(boxItem.Stats.DamageToPlayer); GameRepo.CloseInventory(); break; case ItemTag.ContainsAccessory: _player.Inventory.TryAdd(_effectService.GetRandomItemOfType()); break; case ItemTag.ContainsArmor: _player.Inventory.TryAdd(_effectService.GetRandomItemOfType()); break; case ItemTag.ContainsWeapon: _player.Inventory.TryAdd(_effectService.GetRandomItemOfType()); break; case ItemTag.ContainsBox: _player.Inventory.TryAdd(_effectService.GetRandomItemOfType()); break; case ItemTag.ContainsRestorative: _player.Inventory.TryAdd(_effectService.GetRandomItemOfType()); break; case ItemTag.DropTo1HPAndGainRareItem: _effectService.DropTo1HPAndGainRareItem(); break; case ItemTag.TradeAllRandomItems: var newInventory = _effectService.TradeAllRandomItems(boxItem); _player.Inventory.Items.Clear(); _player.Inventory.TryAdd(boxItem); foreach (var item in newInventory) _player.Inventory.TryAdd(item); break; case ItemTag.ContainsUnobtainedItem: _effectService.GetUnobtainedItem(); break; case ItemTag.ContainsBasicItem: _effectService.GetBasicItem(); break; case ItemTag.UnequipAllItems: _player.EquipmentComponent.Unequip(_player.EquipmentComponent.EquippedWeapon.Value); _player.EquipmentComponent.Unequip(_player.EquipmentComponent.EquippedArmor.Value); _player.EquipmentComponent.Unequip(_player.EquipmentComponent.EquippedAccessory.Value); break; } } private void EnactConsumableItemEffects(ConsumableItem consumableItem) { if (_player.HealthComponent.AtFullHealth && consumableItem.RaiseHPAmount > 0) { _player.HealthComponent.RaiseMaximumHP(consumableItem.RaiseHPAmount); SfxDatabase.Instance.Play(SoundEffect.IncreaseStat); } if (_player.VTComponent.AtFullVT && consumableItem.RaiseVTAmount > 0) { _player.VTComponent.RaiseMaximumVT(consumableItem.RaiseVTAmount); SfxDatabase.Instance.Play(SoundEffect.IncreaseStat); } if (consumableItem.HealHPAmount > 0) { _player.HealthComponent.Heal(consumableItem.HealHPAmount); SfxDatabase.Instance.Play(SoundEffect.HealHP); } if (consumableItem.HealVTAmount > 0) { _player.VTComponent.Restore(consumableItem.HealVTAmount); SfxDatabase.Instance.Play(SoundEffect.HealVT); } } private void EnactEffectItemEffects(EffectItem effectItem) { switch (effectItem.UsableItemTag) { case UsableItemTag.TeleportAllEnemiesToRoom: _effectService.TeleportEnemiesToCurrentRoom(); GameRepo.CloseInventory(); SfxDatabase.Instance.Play(SoundEffect.RecallEnemies); 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; case UsableItemTag.DoubleExp: GameRepo.StartDoubleEXP(TimeSpan.FromSeconds(30)); GameRepo.CloseInventory(); break; case UsableItemTag.TeleportToRandomLocation: _effectService.TeleportToRandomRoom(_player); GameRepo.CloseInventory(); break; case UsableItemTag.WarpToExitIfFound: _effectService.WarpToExit(); GameRepo.CloseInventory(); break; } } private void RemoveItemOrSubtractFromItemCount(IBaseInventoryItem item) { if (item is IStackable stackableItem && stackableItem.Count.Value > 1) stackableItem.SetCount(stackableItem.Count.Value - 1); else _player.Inventory.Remove(item); } public void ShowDebugInfo(bool show) => InGameUI.DebugInfo.Visible = show; private void MovePlayer((Vector3 Rotation, Vector3 Position) spawnPoint) => _player.TeleportPlayer(spawnPoint); private void OnNewGame() { SaveFile.Load(); GameState.Input(new GameState.Input.NewGame()); GameRepo.Resume(); } private void OnFloorLoadFinished() { LoadNextLevel.Hide(); } private void OnQuit() => GameExitRequested?.Invoke(); public void OnExitTree() { InGameUI.UseTeleportPrompt.TeleportToNextFloor -= UseTeleportPrompt_TeleportToNextFloor; InGameUI.UseTeleportPrompt.CloseTeleportPrompt -= UseTeleportPrompt_CloseTeleportPrompt; LoadNextLevel.GoToNextFloor -= FloorClearMenu_GoToNextFloor; LoadNextLevel.Exit -= FloorClearMenu_Exit; LoadNextLevel.TransitionCompleted -= FloorClearMenu_TransitionCompleted; OnLoadLevelRequest -= LoadLevel; GameRepo.CloseInventoryEvent -= ExitInventoryAction; _player.Inventory.BroadcastMessage -= BroadcastMessage; _map.FloorLoaded -= OnFloorLoadFinished; _player.PlayerDied -= GameOver; GameOverMenu.NewGame -= OnNewGame; GameOverMenu.QuitGame -= OnQuit; PauseMenu.ExitGamePressed -= OnQuit; GameRepo.IsPaused.Sync -= IsPaused_Sync; } }