Files
GameJamDungeon/Zennysoft.Game.Ma/src/game/Game.cs
2026-02-13 15:59:10 -08:00

620 lines
19 KiB
C#

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<IGame>.Value() => this;
IGameRepo IProvide<IGameRepo>.Value() => GameRepo;
IPlayer IProvide<IPlayer>.Value() => _player;
IMap IProvide<IMap>.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<GameData> SaveFile { get; set; } = default!;
public ISaveChunk<GameData> GameChunk { get; set; } = default!;
ISaveChunk<GameData> IProvide<ISaveChunk<GameData>>.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<IGameRepo>();
GameState = _container.GetInstance<IGameState>();
QuestData = new QuestData();
RescuedItems = new RescuedItemDatabase();
ItemDatabase = ItemDatabase.Instance;
GameChunk = new SaveChunk<GameData>(
(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<IMaSaveFileManager>();
SaveFile = new SaveFile<GameData>(
root: GameChunk,
onSave: saveFileManager.Save,
onLoad: async () =>
{
try
{
var gameData = await saveFileManager.Load<GameData>();
return gameData;
}
catch (FileNotFoundException)
{
GD.Print("No save file found.");
}
return null;
}
);
}
public void Setup()
{
_instantiator = new Instantiator(GetTree());
_player = _instantiator.LoadAndInstantiate<Player>("res://src/player/Player.tscn");
_map = _instantiator.LoadAndInstantiate<Map>("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<PackedScene>("res://src/items/dropped/DroppedItem.tscn");
var dropped = droppedScene.Instantiate<DroppedItem>();
dropped.Item = item;
_map.AddChild(dropped);
dropped.Drop();
_player.Inventory.Remove(item);
}
public void SetItem(IBaseInventoryItem item)
{
var setScene = GD.Load<PackedScene>("res://src/items/misc/SetItem.tscn");
var setItem = setScene.Instantiate<SetItem>();
_map.AddChild(setItem);
setItem.Set();
_player.Inventory.Remove(item);
}
public void ThrowItem(IBaseInventoryItem item)
{
var thrownScene = GD.Load<PackedScene>("res://src/items/thrown/ThrownItem.tscn");
var thrown = thrownScene.Instantiate<ThrownItem>();
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<PackedScene>("res://src/items/restorative/Restorative.tscn");
var restorative = restorativeScene.Instantiate<Restorative>();
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<Accessory>());
break;
case ItemTag.ContainsArmor:
_player.Inventory.TryAdd(_effectService.GetRandomItemOfType<Armor>());
break;
case ItemTag.ContainsWeapon:
_player.Inventory.TryAdd(_effectService.GetRandomItemOfType<Weapon>());
break;
case ItemTag.ContainsBox:
_player.Inventory.TryAdd(_effectService.GetRandomItemOfType<BoxItem>());
break;
case ItemTag.ContainsRestorative:
_player.Inventory.TryAdd(_effectService.GetRandomItemOfType<ConsumableItem>());
break;
case ItemTag.DropTo1HPAndGainRareItem:
_effectService.DropTo1HPAndGainRareItem<IBaseInventoryItem>();
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<IBaseInventoryItem>();
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;
}
}