using Godot;
using Godot.Collections;
using TheLegendOfGustav.Magic;
using TheLegendOfGustav.Map;
using TheLegendOfGustav.Utils;
namespace TheLegendOfGustav.Entities.Actors;
///
/// A classe de ator define um personagem no jogo.
///
[GlobalClass]
public partial class Actor : Entity, ISaveable
{
#region Fields
private int mp;
private int hp;
private int energy;
///
/// A definição do ator possui caracterísitcas padrões que definem
/// o ator em questão.
///
private ActorDefinition definition;
#endregion
#region Constructors
public Actor(Vector2I initialPosition, MapData map, ActorDefinition definition) : base(initialPosition, map, definition)
{
SetDefinition(definition);
}
public Actor(Vector2I initialPosition, MapData map) : base(initialPosition, map)
{
}
#endregion
#region Signals
///
/// Sinal emitido toda vez que o HP mudar.
///
/// Novo HP
/// Quantidade máxima de HP.
[Signal]
public delegate void HealthChangedEventHandler(int hp, int maxHp);
///
/// Sinal emitido toda vez que a mana mudar.
///
/// Nova mana
/// Quantidade máxima de mana
[Signal]
public delegate void ManaChangedEventHandler(int mp, int maxMp);
///
/// Sinal emitido se o ator morrer.
///
[Signal]
public delegate void DiedEventHandler();
#endregion
#region Properties
///
/// Se o ator está vivo.
///
public bool IsAlive { get => Hp > 0; }
///
/// Utilizado no sistema de turnos.
/// Enquanto o ator tiver energia, ele poderá realizar turnos.
///
public int Energy
{
get => energy;
set
{
if (value > Speed)
{
energy = Speed;
}
else
{
energy = value;
}
}
}
///
/// Taxa de recarga de energia.
///
public int Speed { get => definition.Speed; }
///
/// HP máximo do ator.
///
public int MaxHp { get; private set; }
///
/// HP atual do ator.
///
public int Hp
{
get => hp;
set
{
if (MapData != null && MapData.Player == this && hp > value) {
Stats.Instance.DamageTaken += (hp - value);
}
// Esta propriedade impede que o HP seja maior que o máximo.
hp = int.Clamp(value, 0, MaxHp);
EmitSignal(SignalName.HealthChanged, Hp, MaxHp);
if (hp <= 0)
{
bool inLoading = true;
// Se o ator morrer, porém não estiver em um SceneTree,
// Quer dizer que este ator já estava morto e estamos carregando
// um save.
if (IsInsideTree())
{
inLoading = false;
}
Die(inLoading);
}
}
}
///
/// Máximo de mana do ator.
///
public int MaxMp { get; private set; }
///
/// Mana atual do ator.
///
public int Mp
{
get => mp;
set
{
mp = int.Clamp(value, 0, MaxMp);
EmitSignal(SignalName.ManaChanged, Mp, MaxMp);
}
}
///
/// Estatística de ataque
///
public int Atk { get; private set; }
///
/// Estatística de defesa.
///
public int Def { get; private set; }
///
/// Estatística mental.
///
public int Men { get; private set; }
///
/// Quantos turnos para recarregar a mana.
///
public int MpRegenRate { get; private set; } = 2;
///
/// Quanto de mana para recarregar.
///
public int MpRegenPerTurn { get; private set; } = 5;
public SpellBook SpellBook { get; private set; } = new();
#endregion
#region Methods
///
/// Recarrega a energia do ator.
///
private void RechargeEnergy()
{
Energy += Speed;
}
///
/// Executado uma vez por
///
/// Número do turno.
public void OnTurnStart(int turn)
{
RechargeEnergy();
if (turn % MpRegenRate == 0 && Mp < MaxMp)
{
Mp += MpRegenPerTurn;
}
}
///
/// Move o ator para uma localização. Veja MovementAction.
///
/// Vetor que parte da posição do ator até o seu destino.
public void Walk(Vector2I offset)
{
// Cada ator tem um peso no sistema de pathfinding.
// Sempre que ele se mover, removemos seu peso da posição antiga
MapData.UnregisterBlockingEntity(this);
GridPosition += offset;
// E colocamos na próxima.
MapData.RegisterBlockingEntity(this);
// Este peso influencia o algoritmo de pathfinding.
// Atores evitam caminhos bloqueados. por outros atores.
}
///
/// Recupera uma quantidade de HP do ator.
///
/// HP para recuperar
/// Quanto HP foi realmente recuperado.
public int Heal(int amount)
{
int neoHp = Hp + amount;
if (neoHp > MaxHp) neoHp = MaxHp;
int recovered = neoHp - Hp;
Hp = neoHp;
return recovered;
}
///
/// Aplica uma definição de NPC para o ator.
/// Se o ator for um boneco de barro, este método é como um
/// sopro de vida.
///
/// A definição do ator.
public virtual void SetDefinition(ActorDefinition definition)
{
base.SetDefinition(definition);
this.definition = definition;
Type = definition.Type;
MaxHp = definition.Hp;
Hp = definition.Hp;
MaxMp = definition.Mp;
Mp = definition.Mp;
Atk = definition.Atk;
Def = definition.Def;
Men = definition.Men;
}
public virtual void Die(bool inLoading)
{
//⠀⠀⠀⠀⢠⣤⣤⣤⢠⣤⣤⣤⣤⣄⢀⣠⣤⣤⣄⠀⠀⠀⢀⣠⣤⣤⣄⠀⣤⣤⠀⠀⣠⣤⣤⣤⣤⣤⡄⢠⣤⣤⣤⣄⠀⠀
//⠀⠀⠀⠀⠈⢹⣿⠉⠈⠉⣿⣿⠉⠉⢾⣿⣉⣉⠙⠀⠀⢀⣾⡟⠉⠉⣿⣧⢸⣿⡄⢠⣿⠏⣿⣿⣉⣉⡁⢸⣿⡏⢉⣿⡷⠀
//⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⣿⣿⠀⠀⠈⠿⠿⣿⣿⡀⠀⠸⣿⡇⠀⠀⣾⣿⠀⢿⣿⣸⡿⠀⣿⣿⠿⠿⠇⢸⣿⣿⣿⣿⠀⠀
//⠀⠀⠀⠀⢠⣼⣿⣤⠀⠀⣿⣿⠀⠀⢷⣦⣤⣼⡿⠁⠀⠀⠹⣿⣤⣴⡿⠋⠀⠘⣿⣿⠃⠀⣿⣿⣤⣤⡄⢸⣿⡇⠙⢿⣦⡀
//⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠀⠀⢀⣰⣶⣶⣶⣿⣿⣿⣿⣷⣶⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⠿⠛⠛⠻⢿⣿⣿⣿⣿⣿⣿⣿⣶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⢀⢾⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠈⠉⠉⠉⠻⢿⢿⣿⣿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⢠⠏⢸⣿⣿⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠿⢻⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⢀⠇⠀⠈⠿⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⢀⡞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⡸⠀⠀⠀⠀⠀⠀⠀⠀⡼⠛⠳⣄⡀⠀⠐⢿⣦⡀⠀⠀⠀⢠⠃⠀⣸⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⢠⠇⠀⠀⠀⠀⠀⠀⠀⠀⠉⠀⠀⠀⠉⣳⠟⠒⠻⣿⣦⡀⠀⡘⠀⢰⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⢀⠞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⠃⢠⣄⡀⠈⠙⢿⡌⠁⠀⡞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⣈⢻⡿⠃⢰⠟⠲⣼⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⡰⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⡶⢴⠋⠀⠀⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⡴⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠞⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡴⢟⠒⠀⠀⠀⠀⢰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠏⠀⠀⠈⠉⣿⠇⠀⢀⡎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠠⣤⣤⣀⢰⠏⠉⠙⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣀⣠⠴⠢⠦⠽⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⣿⣿⣿⣷⡄⣀⡀⠈⠉⠋⢹⠋⠁⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
//⠿⠿⠿⠿⠿⠦⠈⠀⠀⠀⠸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
string deathMessage;
if (MapData.Player == this)
{
deathMessage = "Você morreu!";
}
else
{
deathMessage = $"{DisplayName} morreu!";
if (!inLoading) {
Stats.Instance.EnemiesKilled++;
}
}
if (!inLoading)
{
MessageLogData.Instance.AddMessage(deathMessage);
DisplayName = $"Restos mortais de {DisplayName}";
EmitSignal(SignalName.Died);
}
Texture = definition.deathTexture;
BlocksMovement = false;
Type = EntityType.CORPSE;
MapData.UnregisterBlockingEntity(this);
}
public new Dictionary GetSaveData()
{
Dictionary baseData = base.GetSaveData();
baseData.Add("energy", Energy);
baseData.Add("max_hp", MaxHp);
baseData.Add("hp", Hp);
baseData.Add("max_mp", MaxMp);
baseData.Add("mp", MaxMp);
baseData.Add("atk", Atk);
baseData.Add("def", Def);
baseData.Add("men", Men);
baseData.Add("mp_regen_rate", MpRegenRate);
baseData.Add("mp_regen_per_turn", MpRegenPerTurn);
baseData.Add("spell_book", SpellBook.GetSaveData());
return baseData;
}
public new bool LoadSaveData(Dictionary saveData)
{
if (!base.LoadSaveData(saveData))
{
return false;
}
if (!SpellBook.LoadSaveData((Dictionary)saveData["spell_book"]))
{
return false;
}
Energy = (int)saveData["energy"];
MaxHp = (int)saveData["max_hp"];
Hp = (int)saveData["hp"];
MaxMp = (int)saveData["max_mp"];
Mp = (int)saveData["max_mp"];
Atk = (int)saveData["atk"];
Def = (int)saveData["def"];
Men = (int)saveData["men"];
MpRegenRate = (int)saveData["mp_regen_rate"];
MpRegenPerTurn = (int)saveData["mp_regen_per_turn"];
return true;
}
#endregion
}