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 { // 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) { Die(); } } } /// /// 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() { //⠀⠀⠀⠀⢠⣤⣤⣤⢠⣤⣤⣤⣤⣄⢀⣠⣤⣤⣄⠀⠀⠀⢀⣠⣤⣤⣄⠀⣤⣤⠀⠀⣠⣤⣤⣤⣤⣤⡄⢠⣤⣤⣤⣄⠀⠀ //⠀⠀⠀⠀⠈⢹⣿⠉⠈⠉⣿⣿⠉⠉⢾⣿⣉⣉⠙⠀⠀⢀⣾⡟⠉⠉⣿⣧⢸⣿⡄⢠⣿⠏⣿⣿⣉⣉⡁⢸⣿⡏⢉⣿⡷⠀ //⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⣿⣿⠀⠀⠈⠿⠿⣿⣿⡀⠀⠸⣿⡇⠀⠀⣾⣿⠀⢿⣿⣸⡿⠀⣿⣿⠿⠿⠇⢸⣿⣿⣿⣿⠀⠀ //⠀⠀⠀⠀⢠⣼⣿⣤⠀⠀⣿⣿⠀⠀⢷⣦⣤⣼⡿⠁⠀⠀⠹⣿⣤⣴⡿⠋⠀⠘⣿⣿⠃⠀⣿⣿⣤⣤⡄⢸⣿⡇⠙⢿⣦⡀ //⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠀⠀⠀⠀⠀⠀⠀⠀⢀⣰⣶⣶⣶⣿⣿⣿⣿⣷⣶⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⠿⠛⠛⠻⢿⣿⣿⣿⣿⣿⣿⣿⣶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠀⠀⠀⠀⠀⢀⢾⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠈⠉⠉⠉⠻⢿⢿⣿⣿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠀⠀⠀⠀⢠⠏⢸⣿⣿⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠿⢻⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠀⠀⠀⢀⠇⠀⠈⠿⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠀⠀⢀⡞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠀⠀⡸⠀⠀⠀⠀⠀⠀⠀⠀⡼⠛⠳⣄⡀⠀⠐⢿⣦⡀⠀⠀⠀⢠⠃⠀⣸⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠀⢠⠇⠀⠀⠀⠀⠀⠀⠀⠀⠉⠀⠀⠀⠉⣳⠟⠒⠻⣿⣦⡀⠀⡘⠀⢰⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⢀⠞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⠃⢠⣄⡀⠈⠙⢿⡌⠁⠀⡞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⣈⢻⡿⠃⢰⠟⠲⣼⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠀⠀⠀⡰⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⡶⢴⠋⠀⠀⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠀⠀⡴⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠞⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡴⢟⠒⠀⠀⠀⠀⢰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠏⠀⠀⠈⠉⣿⠇⠀⢀⡎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠠⣤⣤⣀⢰⠏⠉⠙⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣀⣠⠴⠢⠦⠽⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⣿⣿⣿⣷⡄⣀⡀⠈⠉⠋⢹⠋⠁⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ //⠿⠿⠿⠿⠿⠦⠈⠀⠀⠀⠸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ string deathMessage; if (MapData.Player == this) { deathMessage = "Você morreu!"; } else { deathMessage = $"{DisplayName} morreu!"; } MessageLogData.Instance.AddMessage(deathMessage); Texture = definition.deathTexture; BlocksMovement = false; Type = EntityType.CORPSE; DisplayName = $"Restos mortais de {DisplayName}"; MapData.UnregisterBlockingEntity(this); EmitSignal(SignalName.Died); } 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 }