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; using System.Linq; [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 SarcoData SarcoData { get; private set; } public NpcData NpcData { get; private set; } public StatData StatData { get; private set; } public ItemRescueMenu ItemRescueMenu { get => InGameUI.ItemRescueMenu; } private EffectService _effectService; private IInstantiator _instantiator; private IPlayer _player; private IMap _map; private Timer _doubleExpTimer; private Timer _rustTimer; private Timer _rustDurationTimer; [Signal] private delegate void OnLoadLevelRequestEventHandler(); public event Action InventoryEventNotification; public Game() { _container = new SimpleInjector.Container(); Module.Bootstrap(_container); GameRepo = _container.GetInstance(); GameState = _container.GetInstance(); QuestData = new QuestData(); RescuedItems = new RescuedItemDatabase(20); SarcoData = new SarcoData(); NpcData = new NpcData() { SteleDiscovered = [] }; ItemDatabase = ItemDatabase.Instance; GameChunk = new SaveChunk( (chunk) => { var gameData = new GameData() { RescuedItems = new RescuedItemDatabase(RescuedItems.GetItems(), 20), QuestData = new QuestData() { DeathCount = QuestData.DeathCount, QuestMarker1 = QuestData.QuestMarker1 }, SarcoData = new SarcoData() { AeolicSarcoAcquired = SarcoData.AeolicSarcoAcquired, IgneousSarcoAcquired = SarcoData.IgneousSarcoAcquired, TelluricSarcoAcquired = SarcoData.TelluricSarcoAcquired, HydricSarcoAcquired = SarcoData.HydricSarcoAcquired, FerrumSarcoAcquired = SarcoData.FerrumSarcoAcquired, SanktaSarcoAcquired = SarcoData.SanktaSarcoAcquired, ShuraSarcoAcquired = SarcoData.ShuraSarcoAcquired, }, NpcData = new NpcData() { SteleDiscovered = NpcData.SteleDiscovered }, StatData = new StatData() { SproingyDefeated = StatData.SproingyDefeated } }; return gameData; }, onLoad: (chunk, data) => { RescuedItems = data.RescuedItems ?? new RescuedItemDatabase(); QuestData = data.QuestData ?? new QuestData(); SarcoData = data.SarcoData ?? new SarcoData(); NpcData = data.NpcData ?? new NpcData() { SteleDiscovered = [] }; StatData = data.StatData ?? new StatData(); } ); var saveFileManager = new MaSaveFileManager(); 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); 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; _doubleExpTimer = new Timer(); _doubleExpTimer.WaitTime = 30; _doubleExpTimer.Timeout += EndDoubleExpTimer; AddChild(_doubleExpTimer); _player.StatusEffectComponent.Rust.Changed += RustStatusChanged; _rustTimer = new Timer(); _rustTimer.WaitTime = 2f; _rustTimer.Timeout += RustTimeout; _rustDurationTimer = new Timer(); _rustDurationTimer.WaitTime = _player.StatusEffectComponent.RustDuration; _rustDurationTimer.Timeout += RustWoreOff; AddChild(_rustTimer); AddChild(_rustDurationTimer); GameRepo.IsPaused.Sync += IsPaused_Sync; InGameUI.PlayerInfoUI.Activate(); InGameUI.Show(); GameRepo.Resume(); } public void LoadExistingGame() => SaveFile.Load().ContinueWith((_) => CallDeferred(nameof(FinishedLoadingSaveFile))); public async Task InitializeGame() { _player.ResetPlayerData(); _map.InitializeMapData(); _effectService = new EffectService(this, _player, _map); await _map.LoadFloor(); } public async Task Save() => await SaveFile.Save(); public void FloorExitReached() => GameState.Input(new GameState.Input.FloorExitEntered()); public void ShowMinimap(bool isVisible) { if (_map.CurrentFloor is SpecialFloor) InGameUI.ShowMinimap(false); else InGameUI.ShowMinimap(true); } public async Task UseItem(IBaseInventoryItem item) { if (item.ItemTag == ItemTag.MysteryItem) _effectService.RerollItem(item); switch (item) { case BoxItem boxItem: await EnactBoxItemEffects(boxItem); break; case ConsumableItem consumableItem: EnactConsumableItemEffects(consumableItem); break; case EffectItem effectItem: await EnactEffectItemEffects(effectItem); break; } 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 void DoubleExp() { GameRepo.AnnounceMessageOnMainScreen("Experience points temporarily doubled."); if (_doubleExpTimer.TimeLeft == 0) _player.ExperiencePointsComponent.ModifyExpGainRate(_player.ExperiencePointsComponent.ExpGainRate.Value * 2); _doubleExpTimer.Start(); } public IDungeonFloor CurrentFloor => _map.CurrentFloor; public async void GameOver() { QuestData.DeathCount++; await Save(); _player.Deactivate(); GameRepo.Pause(); 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); SfxDatabase.Instance.Play(SoundEffect.OpenInventory); }) .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.BreaksOnFloorExit) { var itemToDestroy = _player.EquipmentComponent.EquippedWeapon.Value; _player.Unequip(itemToDestroy); _player.Inventory.Remove(itemToDestroy); } if (_player.EquipmentComponent.EquippedArmor.Value.ItemTag == ItemTag.BreaksOnFloorExit) { var itemToDestroy = _player.EquipmentComponent.EquippedArmor.Value; _player.Unequip(itemToDestroy); _player.Inventory.Remove(itemToDestroy); } if (_player.EquipmentComponent.EquippedAccessory.Value.ItemTag == ItemTag.BreaksOnFloorExit) { 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 _) => { GameOverMenu.FadeIn(); var enemies = GetTree().GetNodesInGroup("enemy").OfType(); foreach (var enemy in enemies) enemy.CallDeferred(MethodName.QueueFree, []); }); } public void SetAffinity(ElementType elementType) => InGameUI.SetAffinity(elementType); 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 DropItem(Vector3 vector) { var randomItem = ItemDatabase.Instance.PickItem() as Node3D; var duplicated = randomItem.Duplicate((int)DuplicateFlags.UseInstantiation) as Node3D; AddChild(duplicated); duplicated.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() { 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 async Task EnactBoxItemEffects(BoxItem boxItem) { switch (boxItem.ItemTag) { case ItemTag.DamagesPlayer: _effectService.DamagesPlayer(boxItem.Stats.DamageToPlayer); InventoryEventNotification.Invoke($"{boxItem.Stats.DamageToPlayer} damage done to self."); await ToSignal(GetTree().CreateTimer(0.5f), "timeout"); GameRepo.CloseInventory(); break; case ItemTag.ContainsAccessory: var accessory = _effectService.GetRandomItemOfType(); _player.Inventory.TryAdd(accessory); InventoryEventNotification.Invoke($"{boxItem.ItemName} contained {accessory.ItemName}."); break; case ItemTag.ContainsArmor: var armor = _effectService.GetRandomItemOfType(); _player.Inventory.TryAdd(armor); InventoryEventNotification.Invoke($"{boxItem.ItemName} contained {armor.ItemName}."); break; case ItemTag.ContainsWeapon: var weapon = _effectService.GetRandomItemOfType(); _player.Inventory.TryAdd(weapon); InventoryEventNotification.Invoke($"{boxItem.ItemName} contained {weapon.ItemName}."); break; case ItemTag.ContainsBox: var box = _effectService.GetRandomItemOfType(); _player.Inventory.TryAdd(box); InventoryEventNotification.Invoke($"{boxItem.ItemName} contained {box.ItemName}."); break; case ItemTag.RandomSpell: var effectItem = _effectService.GetRandomItemOfType(); _player.Inventory.TryAdd(effectItem); InventoryEventNotification.Invoke($"{boxItem.ItemName} contained {effectItem.ItemName}."); break; case ItemTag.ContainsRestorative: var restorative = _effectService.GetRandomItemOfType(); _player.Inventory.TryAdd(restorative); InventoryEventNotification.Invoke($"{boxItem.ItemName} contained {restorative.ItemName}."); break; case ItemTag.DropTo1HPAndGainRareItem: var rareItem = _effectService.DropTo1HPAndGainRareItem(); _player.Inventory.TryAdd(rareItem); InventoryEventNotification.Invoke($"{boxItem.ItemName} contained {rareItem.ItemName} but cost dear life."); break; case ItemTag.TradeOneRandomItem: var itemsWithoutBox = _player.Inventory.Items.Where(x => x != boxItem).ToList(); var rng = new RandomNumberGenerator(); rng.Randomize(); var index = rng.RandiRange(0, itemsWithoutBox.Count - 1); var randomItem = itemsWithoutBox[index]; _player.Inventory.Remove(randomItem); var newItem = _effectService.GetRandomItemOfType(); _player.Inventory.TryAdd(newItem); InventoryEventNotification.Invoke($"{boxItem.ItemName} contained {newItem.ItemName}."); 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); InventoryEventNotification.Invoke($"All items replaced."); break; case ItemTag.ContainsUnobtainedItem: var unobtainedItem = _effectService.GetUnobtainedItem(); _player.Inventory.TryAdd(unobtainedItem); InventoryEventNotification.Invoke($"{boxItem.ItemName} contained {unobtainedItem.ItemName}."); break; case ItemTag.ContainsBasicItem: var basicItem = _effectService.GetBasicItem(); InventoryEventNotification.Invoke($"{boxItem.ItemName} contained {basicItem.ItemName}."); break; case ItemTag.UnequipAllItems: _player.Unequip(_player.EquipmentComponent.EquippedWeapon.Value); _player.Unequip(_player.EquipmentComponent.EquippedArmor.Value); _player.Unequip(_player.EquipmentComponent.EquippedAccessory.Value); InventoryEventNotification.Invoke($"Equipment status reset."); break; case ItemTag.RestrictUnequip: _effectService.GlueAllEquipment(_player); InventoryEventNotification.Invoke($"Currently equipped items have become unmovable."); break; case ItemTag.EjectAllItems: _player.EquipmentComponent.EquippedWeapon.Value.Glued = false; _player.EquipmentComponent.EquippedArmor.Value.Glued = false; _player.EquipmentComponent.EquippedAccessory.Value.Glued = false; _player.Unequip(_player.EquipmentComponent.EquippedWeapon.Value); _player.Unequip(_player.EquipmentComponent.EquippedArmor.Value); _player.Unequip(_player.EquipmentComponent.EquippedAccessory.Value); _player.Inventory.Items.Remove(boxItem); foreach (var item in _player.Inventory.Items.ToList()) ThrowItem(item); _player.Inventory.Items.Clear(); GameRepo.CloseInventory(); BroadcastMessage($"All items have been ejected from inventory."); break; } } private void EnactConsumableItemEffects(ConsumableItem consumableItem) { if (consumableItem.HealHPAmount > 0 && consumableItem.HealVTAmount > 0) { SfxDatabase.Instance.Play(SoundEffect.Eucharistia); if (_player.HealthComponent.AtFullHealth && _player.VTComponent.AtFullVT) { _player.HealthComponent.RaiseMaximumHP(consumableItem.RaiseHPAmount, true); _player.VTComponent.RaiseMaximumVT(consumableItem.RaiseVTAmount, true); InventoryEventNotification.Invoke($"Raised maximum HP by {consumableItem.RaiseHPAmount}." + System.Environment.NewLine + $"Raised maximum VT by {consumableItem.RaiseVTAmount}."); } else { _player.HealthComponent.Heal(consumableItem.HealHPAmount); _player.VTComponent.Restore(consumableItem.HealVTAmount); InventoryEventNotification.Invoke($"Restored {consumableItem.RaiseHPAmount}HP." + System.Environment.NewLine + $"Restored {consumableItem.RaiseVTAmount}VT."); } if (_player.StatusEffectComponent.Rust.Value) { _player.StatusEffectComponent.Reset(); InventoryEventNotification.Invoke($"All status afflictments have faded."); } return; } if (_player.HealthComponent.AtFullHealth && consumableItem.RaiseHPAmount > 0) { _player.HealthComponent.RaiseMaximumHP(consumableItem.RaiseHPAmount, true); SfxDatabase.Instance.Play(SoundEffect.IncreaseStat); InventoryEventNotification.Invoke($"Raised maximum HP by {consumableItem.RaiseHPAmount}."); } else if (_player.VTComponent.AtFullVT && consumableItem.RaiseVTAmount > 0) { _player.VTComponent.RaiseMaximumVT(consumableItem.RaiseVTAmount, true); SfxDatabase.Instance.Play(SoundEffect.IncreaseStat); InventoryEventNotification.Invoke($"Raised maximum VT by {consumableItem.RaiseVTAmount}."); } else if (consumableItem.HealHPAmount > 0) { var currentHP = _player.HealthComponent.CurrentHP; _player.HealthComponent.Heal(consumableItem.HealHPAmount); SfxDatabase.Instance.Play(SoundEffect.HealHP); InventoryEventNotification.Invoke($"Restored {consumableItem.HealHPAmount}HP."); } else if (consumableItem.HealVTAmount > 0) { _player.VTComponent.Restore(consumableItem.HealVTAmount); SfxDatabase.Instance.Play(SoundEffect.HealVT); InventoryEventNotification.Invoke($"Restored {consumableItem.HealVTAmount}VT."); } if (consumableItem.Stats.HealsStatusAilments) { _player.StatusEffectComponent.Reset(); InventoryEventNotification.Invoke($"All status afflictments have faded."); } } private async Task EnactEffectItemEffects(EffectItem effectItem) { switch (effectItem.UsableItemTag) { case UsableItemTag.BriefImmunity: _player.EnactBriefImmunity(); break; case UsableItemTag.TeleportAllEnemiesToRoom: _effectService.TeleportEnemiesToCurrentRoom([.. GetTree().GetNodesInGroup("enemy").OfType()]); SfxDatabase.Instance.Play(SoundEffect.RecallEnemies); GameRepo.CloseInventory(); BroadcastMessage($"All entities have been summoned."); _player.PlaySpellFX(SpellFXEnum.DivinityRecall); break; case UsableItemTag.KillHalfEnemiesInRoom: _effectService.KillHalfEnemiesInRoom(); GameRepo.CloseInventory(); BroadcastMessage($"The balance has been achieved."); break; case UsableItemTag.TurnAllEnemiesIntoHealingItem: _effectService.TurnAllEnemiesInRoomIntoHealingItem(); GameRepo.CloseInventory(); BroadcastMessage($"Entities in current room have been converted."); break; case UsableItemTag.HealsAllInRoomToMaxHP: _effectService.HealAllEnemiesAndPlayerInRoomToFull(); GameRepo.CloseInventory(); BroadcastMessage($"All present have been renewed."); break; case UsableItemTag.AbsorbHPFromAllEnemiesInRoom: var hpAbsorbed = _effectService.AbsorbHPFromAllEnemiesInRoom(); if (hpAbsorbed == 0) { GameRepo.CloseInventory(); BroadcastMessage($"No entities present to absorb from or invalid location."); return; } _player.HealthComponent.Heal(hpAbsorbed); GameRepo.CloseInventory(); BroadcastMessage($"Entities have surrendered {hpAbsorbed}HP to you."); _player.PlaySpellFX(SpellFXEnum.Kyuuketsuki); break; case UsableItemTag.DealElementalDamageToAllEnemiesInRoom: _effectService.DealElementalDamageToAllEnemiesInRoom(effectItem.Stats.ElementalDamageType); GameRepo.CloseInventory(); BroadcastMessage($"All entities present have taken {effectItem.Stats.ElementalDamageType} to you."); break; case UsableItemTag.SwapHPAndVT: _effectService.SwapHPandVT(); InventoryEventNotification.Invoke($"HP and VT have been traded."); await ToSignal(GetTree().CreateTimer(0.5f), "timeout"); break; case UsableItemTag.RaiseCurrentWeaponAttack: if (string.IsNullOrEmpty(_player.EquipmentComponent.EquippedWeapon.Value.ItemName)) return; _effectService.RaiseCurrentWeaponAttack(); InventoryEventNotification.Invoke($"{_player.EquipmentComponent.EquippedWeapon.Value}'s attack has risen by 1."); await ToSignal(GetTree().CreateTimer(0.5f), "timeout"); break; case UsableItemTag.RaiseCurrentDefenseArmor: if (string.IsNullOrEmpty(_player.EquipmentComponent.EquippedArmor.Value.ItemName)) return; _effectService.RaiseCurrentArmorDefense(); InventoryEventNotification.Invoke($"{_player.EquipmentComponent.EquippedArmor.Value}'s defense has risen by 1."); await ToSignal(GetTree().CreateTimer(0.5f), "timeout"); break; case UsableItemTag.RaiseLevel: _effectService.RaiseLevel(); InventoryEventNotification.Invoke($"Level increased to {_player.ExperiencePointsComponent.Level.Value}"); await ToSignal(GetTree().CreateTimer(0.5f), "timeout"); break; case UsableItemTag.LowerLevel: _effectService.LowerLevel(); InventoryEventNotification.Invoke($"Level decreased to {_player.ExperiencePointsComponent.Level.Value}"); await ToSignal(GetTree().CreateTimer(0.5f), "timeout"); break; case UsableItemTag.LowerCurrentDefenseArmor: if (string.IsNullOrEmpty(_player.EquipmentComponent.EquippedArmor.Value.ItemName)) return; _effectService.LowerCurrentArmorDefense(); InventoryEventNotification.Invoke($"{_player.EquipmentComponent.EquippedArmor.Value}'s defense has been lowered by 1."); break; case UsableItemTag.RandomEffect: _effectService.RandomEffect(); break; case UsableItemTag.DoubleExp: _effectService.DoubleExp(); InventoryEventNotification.Invoke($"EXP Rate has been doubled."); GameRepo.CloseInventory(); _player.PlaySpellFX(SpellFXEnum.AnBradan); break; case UsableItemTag.TeleportToRandomLocation: _effectService.TeleportToRandomRoom(_player); InventoryEventNotification.Invoke($"Moving to a different room."); GameRepo.CloseInventory(); break; case UsableItemTag.WarpToExitIfFound: var warpedToExit = _effectService.WarpToExit(); if (warpedToExit) InventoryEventNotification.Invoke($"Moved to exit room."); else InventoryEventNotification.Invoke($"Unable to locate exit room."); await ToSignal(GetTree().CreateTimer(0.5f), "timeout"); GameRepo.CloseInventory(); break; case UsableItemTag.IncreaseAttack: _player.AttackComponent.RaiseMaximumAttack(effectItem.Stats.BonusAttack); SfxDatabase.Instance.Play(SoundEffect.IncreaseStat); InventoryEventNotification.Invoke($"Attack has permanently risen to {_player.AttackComponent.CurrentAttack.Value}."); break; case UsableItemTag.IncreaseDefense: _player.DefenseComponent.RaiseMaximumDefense(effectItem.Stats.BonusDefense); SfxDatabase.Instance.Play(SoundEffect.IncreaseStat); InventoryEventNotification.Invoke($"Defense has permanently risen to {_player.DefenseComponent.CurrentDefense.Value}."); break; case UsableItemTag.IncreaseLuck: _player.LuckComponent.IncreaseLuck(effectItem.Stats.BonusLuck); SfxDatabase.Instance.Play(SoundEffect.IncreaseStat); InventoryEventNotification.Invoke($"Luck increased."); break; case UsableItemTag.DecreaseAttack: _player.AttackComponent.Reduce(effectItem.Stats.BonusAttack); SfxDatabase.Instance.Play(SoundEffect.DecreaseStat); InventoryEventNotification.Invoke($"Attack has been lowered to {_player.AttackComponent.CurrentAttack.Value}."); break; case UsableItemTag.DecreaseDefense: _player.DefenseComponent.Reduce(effectItem.Stats.BonusDefense); SfxDatabase.Instance.Play(SoundEffect.DecreaseStat); InventoryEventNotification.Invoke($"Defense has been lowered to {_player.DefenseComponent.CurrentDefense.Value}."); break; case UsableItemTag.DecreaseLuck: _player.LuckComponent.DecreaseLuck(effectItem.Stats.BonusLuck); SfxDatabase.Instance.Play(SoundEffect.DecreaseStat); InventoryEventNotification.Invoke($"Luck decreased."); break; case UsableItemTag.DecreaseAllStats: _player.AttackComponent.Reduce(effectItem.Stats.BonusAttack); _player.DefenseComponent.Reduce(effectItem.Stats.BonusDefense); _player.LuckComponent.DecreaseLuck(effectItem.Stats.BonusLuck); SfxDatabase.Instance.Play(SoundEffect.DecreaseStat); InventoryEventNotification.Invoke($"All stats have been lowered."); break; case UsableItemTag.MeltAllEquipment: _effectService.MeltAllEquipment(_player); SfxDatabase.Instance.Play(SoundEffect.DecreaseStat); InventoryEventNotification.Invoke($"All equipped items have dissolved."); break; case UsableItemTag.GlueAllEquipment: _effectService.GlueAllEquipment(_player); SfxDatabase.Instance.Play(SoundEffect.DecreaseStat); InventoryEventNotification.Invoke($"All equipped items have become sticky."); break; case UsableItemTag.RestoreStats: _effectService.RestoreParameters(_player); SfxDatabase.Instance.Play(SoundEffect.IncreaseStat); InventoryEventNotification.Invoke($"All stats have been restored."); break; case UsableItemTag.LowerTargetTo1HP: _player.HealthComponent.SetCurrentHealth(1); SfxDatabase.Instance.Play(SoundEffect.DecreaseStat); InventoryEventNotification.Invoke($"HP has been reduced to 1."); break; case UsableItemTag.DoubleStackedItems: var stackableItems = _player.Inventory.Items.OfType().Except([effectItem]).ToList(); stackableItems.ForEach(x => x.Count.OnNext(x.Count.Value * 2)); InventoryEventNotification.Invoke($"Items with inventory count have been doubled."); break; case UsableItemTag.IdentifyRandomItem: var unidentifiedItems = _player.Inventory.Items.Where(x => x.ItemTag == ItemTag.MysteryItem).ToList(); if (!unidentifiedItems.Any()) return; var rng = new RandomNumberGenerator(); rng.Randomize(); var index = rng.RandiRange(0, unidentifiedItems.Count - 1); var itemToIdentify = unidentifiedItems[index]; var identifiedItem = _player.IdentifyItem(itemToIdentify); InventoryEventNotification.Invoke($"{itemToIdentify} identified to be {identifiedItem.ItemName}."); break; case UsableItemTag.IdentifyAllItemsCostHP: var unidentifiedItemsAll = _player.Inventory.Items.Where(x => x.ItemTag == ItemTag.MysteryItem).ToList(); if (!unidentifiedItemsAll.Any()) return; foreach (var item in unidentifiedItemsAll) _player.IdentifyItem(item); InventoryEventNotification.Invoke($"All items identified."); break; } _player.EquipmentComponent.UpdateEquipment(_player.EquipmentComponent.EquippedWeapon.Value); _player.EquipmentComponent.UpdateEquipment(_player.EquipmentComponent.EquippedArmor.Value); _player.EquipmentComponent.UpdateEquipment(_player.EquipmentComponent.EquippedAccessory.Value); } 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 EndDoubleExpTimer() { GameRepo.AnnounceMessageOnMainScreen("Experience points effect wore off."); SfxDatabase.Instance.Play(SoundEffect.MoveUI); _player.ExperiencePointsComponent.ModifyExpGainRate(_player.ExperiencePointsComponent.ExpGainRate.Value / 2); _doubleExpTimer.Stop(); } private void GameRepo_EnemyDied(IEnemy obj) { var rng = new RandomNumberGenerator(); rng.Randomize(); if (rng.Randf() < 0.15f) DropItem(obj.GlobalPosition); else DropRestorative(obj.GlobalPosition); } private void BroadcastMessage(string obj) { InGameUI.InventoryMessageUI.DisplayMessage(obj); } 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(); GameLoaded?.Invoke(); _map.FadeIn(); if (GameOverMenu.Visible) GameOverMenu.FadeOut(); GameRepo.Resume(); _player.Activate(); } private void RustStatusChanged(bool rustStatus) { if (rustStatus) { _rustTimer.Start(); _rustDurationTimer.Start(); GameRepo.AnnounceMessageOnMainScreen("Afflicted with Rust."); } else { _rustTimer.Stop(); GameRepo.AnnounceMessageOnMainScreen("Rust affliction has faded."); } } private void RustTimeout() { _player.HealthComponent.Damage(3, ElementType.Ferrum); } private void RustWoreOff() => _player.StatusEffectComponent.Rust.OnNext(false); public void NotifyInventory(string message) => InventoryEventNotification?.Invoke(message); 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; } }