Files
GameJamDungeon/Zennysoft.Game.Ma/src/game/Game.cs
2025-09-25 02:50:55 -07:00

519 lines
15 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.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<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 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!;
#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();
#endregion
public RescuedItemDatabase RescuedItems { get; set; } = default!;
private EffectService _effectService;
private IInstantiator _instantiator;
private Player _player;
private Map _map;
public void Setup()
{
_container = new SimpleInjector.Container();
Module.Bootstrap(_container);
_instantiator = new Instantiator(GetTree());
_player = _instantiator.LoadAndInstantiate<Player>("res://src/player/Player.tscn");
PauseContainer.AddChild(_player);
_map = _instantiator.LoadAndInstantiate<Map>("res://src/map/Map.tscn");
PauseContainer.AddChild(_map);
GameRepo = _container.GetInstance<IGameRepo>();
GameState = _container.GetInstance<IGameState>();
GameState.Set(GameRepo);
GameState.Set(_player);
GameState.Set(_map);
GameState.Set(InGameUI);
RescuedItems = new RescuedItemDatabase();
GameChunk = new SaveChunk<GameData>(
(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<IMaSaveFileManager<GameData>>();
SaveFile = new SaveFile<GameData>(
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 _) =>
{
})
.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;
DeathMenu.NewGame += OnContinueGame;
DeathMenu.QuitGame += OnQuit;
GameRepo.IsPaused.Sync += IsPaused_Sync;
_effectService = new EffectService(this, _player, _map);
}
public void OnReady()
{
InitializeGame();
_map.LoadMap();
GameRepo.Resume();
InGameUI.Show();
InGameUI.PlayerInfoUI.Activate();
_player.TeleportPlayer(_map.CurrentFloor.GetPlayerSpawnPoint());
_player.Activate();
}
private void FloorClearMenu_SaveAndExit()
{
//SaveFile.Save();
_player.Deactivate();
GameState.Input(new GameState.Input.ReturnToMainMenu());
InGameUI.Hide();
}
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<PackedScene>("res://src/items/dropped/DroppedItem.tscn");
var dropped = droppedScene.Instantiate<DroppedItem>();
dropped.Item = item;
AddChild(dropped);
dropped.Drop();
}
public void ThrowItem(InventoryItem item)
{
var thrownScene = GD.Load<PackedScene>("res://src/items/thrown/ThrownItem.tscn");
var thrown = thrownScene.Instantiate<ThrownItem>();
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<PackedScene>("res://src/items/restorative/Restorative.tscn");
var restorative = restorativeScene.Instantiate<Restorative>();
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());
}
private void OnContinueGame()
{
GameState.Input(new GameState.Input.ContinueGame());
}
private void OnLoadGame()
{
GameState.Input(new GameState.Input.LoadGame());
}
private void OnQuit()
{
}
}