527 lines
16 KiB
C#
527 lines
16 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;
|
|
|
|
IGame IProvide<IGame>.Value() => this;
|
|
|
|
IPlayer IProvide<IPlayer>.Value() => Player;
|
|
|
|
IMap IProvide<IMap>.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<IAppRepo>();
|
|
|
|
#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<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;
|
|
|
|
public void Setup()
|
|
{
|
|
_container = new SimpleInjector.Container();
|
|
Module.Bootstrap(_container);
|
|
|
|
GameRepo = _container.GetInstance<IGameRepo>();
|
|
GameState = _container.GetInstance<IGameState>();
|
|
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<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 _) =>
|
|
{
|
|
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<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());
|
|
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();
|
|
}
|
|
}
|