summaryrefslogtreecommitdiff
path: root/scripts/Entities
diff options
context:
space:
mode:
authorMatheus <matheus.guedes.mg.m@gmail.com>2025-09-09 19:09:34 -0300
committerMatheus <matheus.guedes.mg.m@gmail.com>2025-09-09 19:09:34 -0300
commitc6bbb834f7758027c0df338f1520f34fad3befea (patch)
tree1818cd23c24be16fbe19b16dd0a510874d440d83 /scripts/Entities
parentf1b51bed52ffbd90b5b7cc8dcfc6f0484bbbeb3c (diff)
Organização
Diffstat (limited to 'scripts/Entities')
-rw-r--r--scripts/Entities/Actions/Action.cs69
-rw-r--r--scripts/Entities/Actions/Action.cs.uid1
-rw-r--r--scripts/Entities/Actions/BumpAction.cs38
-rw-r--r--scripts/Entities/Actions/BumpAction.cs.uid1
-rw-r--r--scripts/Entities/Actions/DirectionalAction.cs37
-rw-r--r--scripts/Entities/Actions/DirectionalAction.cs.uid1
-rw-r--r--scripts/Entities/Actions/DropAction.cs17
-rw-r--r--scripts/Entities/Actions/DropAction.cs.uid1
-rw-r--r--scripts/Entities/Actions/ItemAction.cs39
-rw-r--r--scripts/Entities/Actions/ItemAction.cs.uid1
-rw-r--r--scripts/Entities/Actions/MeleeAction.cs55
-rw-r--r--scripts/Entities/Actions/MeleeAction.cs.uid1
-rw-r--r--scripts/Entities/Actions/MovementAction.cs30
-rw-r--r--scripts/Entities/Actions/MovementAction.cs.uid1
-rw-r--r--scripts/Entities/Actions/PickUpAction.cs51
-rw-r--r--scripts/Entities/Actions/PickUpAction.cs.uid1
-rw-r--r--scripts/Entities/Actions/WaitAction.cs104
-rw-r--r--scripts/Entities/Actions/WaitAction.cs.uid1
-rw-r--r--scripts/Entities/Actors/AI/BaseAI.cs54
-rw-r--r--scripts/Entities/Actors/AI/BaseAI.cs.uid1
-rw-r--r--scripts/Entities/Actors/AI/HostileEnemyAI.cs84
-rw-r--r--scripts/Entities/Actors/AI/HostileEnemyAI.cs.uid1
-rw-r--r--scripts/Entities/Actors/Actor.cs251
-rw-r--r--scripts/Entities/Actors/Actor.cs.uid1
-rw-r--r--scripts/Entities/Actors/ActorDefinition.cs32
-rw-r--r--scripts/Entities/Actors/ActorDefinition.cs.uid1
-rw-r--r--scripts/Entities/Actors/Enemy.cs54
-rw-r--r--scripts/Entities/Actors/Enemy.cs.uid1
-rw-r--r--scripts/Entities/Actors/EnemyDefinition.cs15
-rw-r--r--scripts/Entities/Actors/EnemyDefinition.cs.uid1
-rw-r--r--scripts/Entities/Actors/Inventory.cs46
-rw-r--r--scripts/Entities/Actors/Inventory.cs.uid1
-rw-r--r--scripts/Entities/Actors/Player.cs27
-rw-r--r--scripts/Entities/Actors/Player.cs.uid1
-rw-r--r--scripts/Entities/Actors/PlayerDefinition.cs11
-rw-r--r--scripts/Entities/Actors/PlayerDefinition.cs.uid1
-rw-r--r--scripts/Entities/Entity.cs127
-rw-r--r--scripts/Entities/Entity.cs.uid1
-rw-r--r--scripts/Entities/EntityDefinition.cs23
-rw-r--r--scripts/Entities/EntityDefinition.cs.uid1
-rw-r--r--scripts/Entities/Items/ConsumableItem.cs40
-rw-r--r--scripts/Entities/Items/ConsumableItem.cs.uid1
-rw-r--r--scripts/Entities/Items/ConsumableItemDefinition.cs9
-rw-r--r--scripts/Entities/Items/ConsumableItemDefinition.cs.uid1
-rw-r--r--scripts/Entities/Items/HealingConsumable.cs30
-rw-r--r--scripts/Entities/Items/HealingConsumable.cs.uid1
-rw-r--r--scripts/Entities/Items/HealingConsumableDefinition.cs14
-rw-r--r--scripts/Entities/Items/HealingConsumableDefinition.cs.uid1
48 files changed, 1281 insertions, 0 deletions
diff --git a/scripts/Entities/Actions/Action.cs b/scripts/Entities/Actions/Action.cs
new file mode 100644
index 0000000..b2d6a4b
--- /dev/null
+++ b/scripts/Entities/Actions/Action.cs
@@ -0,0 +1,69 @@
+using Godot;
+using TheLegendOfGustav.Entities.Actors;
+using TheLegendOfGustav.Map;
+
+namespace TheLegendOfGustav.Entities.Actions;
+
+/// <summary>
+/// <c>Action</c> representa uma ação no jogo efetuada por um ator.
+/// Ações são geradas pelo jogador e pela IA, elas regem os atores do jogo.
+/// </summary>
+public abstract partial class Action : RefCounted
+{
+ private Actor actor;
+
+ private int cost;
+
+ public Action(Actor actor)
+ {
+ Actor = actor;
+ // Custo base, subclasses podem sobreescrever isto se quiserem.
+ Cost = 10;
+ }
+
+ /// <summary>
+ /// O ator que realiza a ação.
+ /// </summary>
+ public Actor Actor
+ {
+ get => actor;
+ private set
+ {
+ actor = value;
+ }
+ }
+
+ /// <summary>
+ /// O custo da ação.
+ /// </summary>
+ protected int Cost
+ {
+ get => cost;
+ set
+ {
+ cost = value;
+ }
+ }
+
+ /// <summary>
+ /// É conveniente ter acesso ao mapa dentro de uma ação.
+ /// </summary>
+ protected MapData MapData
+ {
+ get => actor.MapData;
+ }
+
+ /// <summary>
+ /// Método que executa a ação. Subclasses da ação devem implementar este método.
+ /// <example>
+ /// Exemplo:
+ /// <code>
+ /// Action action = new Action(actor);
+ /// /* . . . */
+ /// action.Perform();
+ /// </code>
+ /// </example>
+ /// </summary>
+ /// <returns>Se a ação foi executada ou não.</returns>
+ public abstract bool Perform();
+}
diff --git a/scripts/Entities/Actions/Action.cs.uid b/scripts/Entities/Actions/Action.cs.uid
new file mode 100644
index 0000000..9523b0a
--- /dev/null
+++ b/scripts/Entities/Actions/Action.cs.uid
@@ -0,0 +1 @@
+uid://dlejckfyro2ch
diff --git a/scripts/Entities/Actions/BumpAction.cs b/scripts/Entities/Actions/BumpAction.cs
new file mode 100644
index 0000000..f0a0047
--- /dev/null
+++ b/scripts/Entities/Actions/BumpAction.cs
@@ -0,0 +1,38 @@
+using Godot;
+using TheLegendOfGustav.Entities.Actors;
+
+namespace TheLegendOfGustav.Entities.Actions;
+
+/// <summary>
+/// Ação de "Esbarramento", utilizada principalmente pelo jogador.
+/// Esta ação direcionada tentará andar para o destino, se houver um
+/// ator no caminho, uma ação de ataque é gerada no lugar.
+/// </summary>
+public partial class BumpAction : DirectionalAction
+{
+ public BumpAction(Actor actor, Vector2I offset) : base(actor, offset)
+ {
+ }
+
+ // Como esta ação inevitavelmente gera outras ações,
+ // não faz sentido descontar a energia do ator.
+ public override bool Perform()
+ {
+ // Declaramos uma ação genérica.
+ Action action;
+
+ // Se houver um ator no destino, crie uma ação de ataque.
+ if (GetTarget() != null)
+ {
+ action = new MeleeAction(Actor, Offset);
+ }
+ else
+ {
+ // Mas se não houver, crie uma ação de movimento.
+ action = new MovementAction(Actor, Offset);
+ }
+
+ // Executa a ação.
+ return action.Perform();
+ }
+}
diff --git a/scripts/Entities/Actions/BumpAction.cs.uid b/scripts/Entities/Actions/BumpAction.cs.uid
new file mode 100644
index 0000000..f5ce3f8
--- /dev/null
+++ b/scripts/Entities/Actions/BumpAction.cs.uid
@@ -0,0 +1 @@
+uid://p6ij0dsuvv7y
diff --git a/scripts/Entities/Actions/DirectionalAction.cs b/scripts/Entities/Actions/DirectionalAction.cs
new file mode 100644
index 0000000..e32e9f2
--- /dev/null
+++ b/scripts/Entities/Actions/DirectionalAction.cs
@@ -0,0 +1,37 @@
+using Godot;
+using TheLegendOfGustav.Entities.Actors;
+using TheLegendOfGustav.Map;
+
+namespace TheLegendOfGustav.Entities.Actions;
+
+/// <summary>
+/// Ação direcionada. Esta ação é acompanhada com um vetor que representa uma
+/// distância tendo como ponto de partida o ator.
+/// </summary>
+public abstract partial class DirectionalAction : Action
+{
+ public DirectionalAction(Actor actor, Vector2I offset) : base(actor)
+ {
+ Offset = offset;
+ }
+
+ /// <summary>
+ /// Direção/distância do ator da ação.
+ /// Seu significado depende da ação que implementará esta classe.
+ /// </summary>
+ public Vector2I Offset { get; private set; }
+
+ /// <summary>
+ /// Coordenada do alvo da ação.
+ /// </summary>
+ public Vector2I Destination { get => Actor.GridPosition + Offset; }
+
+ /// <summary>
+ /// Função que obtém o alvo da ação, se houver.
+ /// </summary>
+ /// <returns>O ator alvo da ação, nulo se não houver.</returns>
+ protected Entity GetTarget()
+ {
+ return MapData.GetBlockingEntityAtPosition(Destination);
+ }
+}
diff --git a/scripts/Entities/Actions/DirectionalAction.cs.uid b/scripts/Entities/Actions/DirectionalAction.cs.uid
new file mode 100644
index 0000000..901756a
--- /dev/null
+++ b/scripts/Entities/Actions/DirectionalAction.cs.uid
@@ -0,0 +1 @@
+uid://cxotc2adk05j8
diff --git a/scripts/Entities/Actions/DropAction.cs b/scripts/Entities/Actions/DropAction.cs
new file mode 100644
index 0000000..00ddd7e
--- /dev/null
+++ b/scripts/Entities/Actions/DropAction.cs
@@ -0,0 +1,17 @@
+using TheLegendOfGustav.Entities.Actors;
+using TheLegendOfGustav.Entities.Items;
+
+namespace TheLegendOfGustav.Entities.Actions;
+
+public partial class DropAction : ItemAction
+{
+ public DropAction(Player player, ConsumableItem item) : base(player, item)
+ {
+ }
+
+ public override bool Perform()
+ {
+ Player.Inventory.Drop(Item);
+ return true;
+ }
+} \ No newline at end of file
diff --git a/scripts/Entities/Actions/DropAction.cs.uid b/scripts/Entities/Actions/DropAction.cs.uid
new file mode 100644
index 0000000..98ed82f
--- /dev/null
+++ b/scripts/Entities/Actions/DropAction.cs.uid
@@ -0,0 +1 @@
+uid://by48a3a3gbvfa
diff --git a/scripts/Entities/Actions/ItemAction.cs b/scripts/Entities/Actions/ItemAction.cs
new file mode 100644
index 0000000..14c5d93
--- /dev/null
+++ b/scripts/Entities/Actions/ItemAction.cs
@@ -0,0 +1,39 @@
+using TheLegendOfGustav.Entities.Actors;
+using TheLegendOfGustav.Entities.Items;
+
+namespace TheLegendOfGustav.Entities.Actions;
+
+public partial class ItemAction : Action
+{
+ private ConsumableItem item;
+ private Player player;
+
+ public ItemAction(Player player, ConsumableItem item) : base(player)
+ {
+ Item = item;
+ Player = player;
+ }
+
+ public Player Player
+ {
+ get => player;
+ private set
+ {
+ player = value;
+ }
+ }
+
+ protected ConsumableItem Item
+ {
+ get => item;
+ set
+ {
+ item = value;
+ }
+ }
+
+ public override bool Perform()
+ {
+ return item.Activate(this);
+ }
+} \ No newline at end of file
diff --git a/scripts/Entities/Actions/ItemAction.cs.uid b/scripts/Entities/Actions/ItemAction.cs.uid
new file mode 100644
index 0000000..c8c8e23
--- /dev/null
+++ b/scripts/Entities/Actions/ItemAction.cs.uid
@@ -0,0 +1 @@
+uid://f7ep4u4fwsyl
diff --git a/scripts/Entities/Actions/MeleeAction.cs b/scripts/Entities/Actions/MeleeAction.cs
new file mode 100644
index 0000000..fcd8368
--- /dev/null
+++ b/scripts/Entities/Actions/MeleeAction.cs
@@ -0,0 +1,55 @@
+using Godot;
+using TheLegendOfGustav.Entities.Actors;
+using TheLegendOfGustav.Utils;
+
+namespace TheLegendOfGustav.Entities.Actions;
+
+/// <summary>
+/// Ação de ataque físico. Uma ação direcionada que ataca um alvo.
+/// </summary>
+public partial class MeleeAction : DirectionalAction
+{
+ public MeleeAction(Actor actor, Vector2I offset) : base(actor, offset)
+ {
+ }
+
+ /// <summary>
+ /// Ataca o ator na direção da ação.
+ /// </summary>
+ public override bool Perform()
+ {
+ // Eu te disse que este método seria útil.
+ Entity potentialTarget = GetTarget();
+
+ // Só podemos atacar atores.
+ if (potentialTarget is not TheLegendOfGustav.Entities.Actors.Actor)
+ {
+ return false;
+ }
+
+ Actor target = (Actor)potentialTarget;
+
+ // Se não houver um ator na direção, não podemos continuar.
+ // Isto é uma ação gratuita.
+ if (target == null) return false;
+
+ // não podemos ter dano negativo.
+ int damage = Actor.Atk - target.Def;
+
+ string attackDesc = $"{Actor.DisplayName} ataca {target.DisplayName}";
+
+ if (damage > 0)
+ {
+ attackDesc += $" e remove {damage} de HP.";
+ target.Hp -= damage;
+ }
+ else
+ {
+ attackDesc += $" mas {target.DisplayName} tem músculos de aço.";
+ }
+
+ MessageLogData.Instance.AddMessage(attackDesc);
+ Actor.Energy -= Cost;
+ return true;
+ }
+}
diff --git a/scripts/Entities/Actions/MeleeAction.cs.uid b/scripts/Entities/Actions/MeleeAction.cs.uid
new file mode 100644
index 0000000..bc97619
--- /dev/null
+++ b/scripts/Entities/Actions/MeleeAction.cs.uid
@@ -0,0 +1 @@
+uid://vbptt0gl1ud0
diff --git a/scripts/Entities/Actions/MovementAction.cs b/scripts/Entities/Actions/MovementAction.cs
new file mode 100644
index 0000000..0ac842c
--- /dev/null
+++ b/scripts/Entities/Actions/MovementAction.cs
@@ -0,0 +1,30 @@
+using Godot;
+using TheLegendOfGustav.Entities.Actors;
+using TheLegendOfGustav.Map;
+
+namespace TheLegendOfGustav.Entities.Actions;
+
+/// <summary>
+/// Ação de movimento. Movimenta o ator para a direção de seu Offset.
+/// </summary>
+public partial class MovementAction : DirectionalAction
+{
+ public MovementAction(Actor actor, Vector2I offset) : base(actor, offset)
+ {
+ }
+
+ public override bool Perform()
+ {
+ // Não anda se o destino for um tile sólido.
+ if (!MapData.GetTile(Destination).IsWalkable) return true;
+
+ // Não anda se o destino for oculpado por um ator.
+ // Na maioria dos casos, essa condição nunca é verdadeira.
+ if (GetTarget() != null) return true;
+
+ Actor.Walk(Offset);
+ Actor.Energy -= Cost;
+
+ return true;
+ }
+}
diff --git a/scripts/Entities/Actions/MovementAction.cs.uid b/scripts/Entities/Actions/MovementAction.cs.uid
new file mode 100644
index 0000000..07569ef
--- /dev/null
+++ b/scripts/Entities/Actions/MovementAction.cs.uid
@@ -0,0 +1 @@
+uid://cdtpdaeg7hh6p
diff --git a/scripts/Entities/Actions/PickUpAction.cs b/scripts/Entities/Actions/PickUpAction.cs
new file mode 100644
index 0000000..0dbd672
--- /dev/null
+++ b/scripts/Entities/Actions/PickUpAction.cs
@@ -0,0 +1,51 @@
+using Godot;
+using TheLegendOfGustav.Entities.Actors;
+using TheLegendOfGustav.Utils;
+using TheLegendOfGustav.Entities.Items;
+using TheLegendOfGustav.Map;
+
+namespace TheLegendOfGustav.Entities.Actions;
+
+public partial class PickupAction : DirectionalAction
+{
+ private Player player;
+
+ public PickupAction(Player player, Vector2I offset) : base(player, offset)
+ {
+ Player = player;
+ // Pegar itens requer um tempo menor.
+ Cost = 2;
+ }
+
+ protected Player Player
+ {
+ get => player;
+ private set
+ {
+ player = value;
+ }
+ }
+
+ public override bool Perform()
+ {
+ ConsumableItem item = MapData.GetFirstItemAtPosition(Destination);
+
+ if (item == null)
+ {
+ MessageLogData.Instance.AddMessage("Não tem item aqui.");
+ return false;
+ }
+
+ if (player.Inventory.Items.Count >= player.Inventory.Capacity)
+ {
+ MessageLogData.Instance.AddMessage("Seu inventário está cheio");
+ return false;
+ }
+
+ MapData.RemoveEntity(item);
+ player.Inventory.Add(item);
+
+ player.Energy -= Cost;
+ return true;
+ }
+} \ No newline at end of file
diff --git a/scripts/Entities/Actions/PickUpAction.cs.uid b/scripts/Entities/Actions/PickUpAction.cs.uid
new file mode 100644
index 0000000..7ca9c72
--- /dev/null
+++ b/scripts/Entities/Actions/PickUpAction.cs.uid
@@ -0,0 +1 @@
+uid://dydpoqnwxwbq6
diff --git a/scripts/Entities/Actions/WaitAction.cs b/scripts/Entities/Actions/WaitAction.cs
new file mode 100644
index 0000000..011703b
--- /dev/null
+++ b/scripts/Entities/Actions/WaitAction.cs
@@ -0,0 +1,104 @@
+using TheLegendOfGustav.Entities.Actors;
+
+namespace TheLegendOfGustav.Entities.Actions;
+
+/// <summary>
+/// Ação da inação. Ação que realiza nada.
+/// </summary>
+public partial class WaitAction : Action
+{
+ public WaitAction(Actor actor) : base(actor)
+ {
+ }
+
+ public override bool Perform()
+ {
+//-=-==============================================-======++++*@@%*+#%%+*%%#*#%%@%**%%#=:+%@%=--.....::::@@@:................... #@----::@@@@@@@@@@@@@
+//--============--===========================================+@. :@ =% @ :* @#= @@*........@@@:................ .::::::@@@@@@@@@@@@
+//===========================================================+@+ * :+ @ :*%* @%+ @=.......:@@@:.......... ...... ..::.:@@@@@@@@@@@
+//===-===========---========--=========----==========--=======%% . . @ =%@* @%+ @@:......@@@:.......... ....... .....:*@@@@@@@@@@
+//=========-==========================--==============-=======#@ #@ #* @%+ @@:.. .@@@:. ........ ..... ..:*@@@@*@@@@@@@@@
+//============================--====---=======================*@ * @@ %@@* @%+ @=. @@@... . =@@@: #@@@@@@@@
+//===--==-========----==---=----==----===========-===-==#@@@@@@@- @ @@ :#%* #*+ *#*: .@@@: . .. .@@@@@@@@
+//-------==--==-==---::---======----===-----==--===---===*%@@@@@* @: @@ * = %= %@@. .. =@@@@@@@
+//--==---==-=--=====----======-=----==-===---=======--========@@@@@%*=+#%#*-=*%%%%**#%%%#*#%%%*=.%@@: @@@@@@@
+//===-=-----=--=--====--======--====-===-=--==----========--==@@@@@: #@@: . . :=*@@@@@@@
+//==--==-===-==-==@@+====--=----==-==-==---=------===========@@@@@@@@@- #@@: . .. :@@@@@@@+@@@@@@
+//==---=--=======@%@*@%-==-==-==-========--==----========-===@ .*@@@@+ #@@: . @@@@@@
+//========-====--==@@%@@+@%===-=--======--:-====-===========%+ .=@@@@%. #@@: . . =@@@@@
+//---=-=-==-----=====*@==@@*@@+==-----=-----====--===@@@*==*@ -@@@@@- :*##=*@@- . . .@@@@@
+//------------===---===%@*=@%=*@*==------------==-=--%@@@@@@: .. ..:#@@@@*:@@@@@@@@@% .@@@@@
+//---==-----===-====-==-==@#=#@@+@@===----==--=====--====%@* ..............+@@@@@@@@@@@@@@. ....:::::. --:+:%: @=:@@@. .::-====@@@@@
+//==-=----==----===--==-----=@-=@@+@#@%=-==-======---====#@ ....:::.::......@@@@@@@@@@@@@% . ..-@#.@:%@*@%.@+ %*:@@*@.@@#@*@=%@# %@@@@@@*@@@@@
+//===----=======-====--=--====-+*%@+@@@=@=--=====--=====+@ .. .+@@@@@@@@@@:...@@@@@@@@@@@@@% -@#@@-@- =@ @% *@.#@.@.@ *@*=@@@% @@@@:
+//====--==-===-========---=========@%*@@%%@@#====--====*@: .=@@@@@@%=+: @*::.@@@@@@@@@@@@@@: :@::@=:%@#:.-- :: .: : @@@.
+//===---===-==-------:-----=-=====---=@+@@***=========+@: =@@@ : @*=.:@@@@@@@@@@@@@@@ . =@:@@%
+//==----===-===-=------==----==--=--====*@@@*==*@@#==+@: . =@ - . #@@*.. +@@@@@@@= . ## -@
+//===---=-==-----=----===----============-======@@@@@@. ..:@= : =@@++== .@@+ . .. -@@@%#@=
+//=-=--==-===--------==-=---=---=----------=======*@@. *@@@-@@*@ #= * . . ..:@@@@:
+//=====-===-=======-==----====-----=======--==-===%@ .. .=@ = #. .. .. .@: %@@-
+//==-==-==-=--===-=====-------=----==--=====-=--=@* .. .* =. :* + .. . %:
+//==-==----=-------=------=----==-----==========@: ......+ #%*** @@%%+: . %@=
+//===--=-----------=--=--==---===---:=@@@*==-=@@. ....::@ .. %@*:%*-#%#*=: . :=:=%#@@@+
+//==---=-:-====---========-----====-:-=@@@@++@= ....@@%* @-@@+=%%. -@%: -@@@. :
+//==----========---------------==========@@@* %@@@:=%@@*@@@@==+@==- .@= .*@- #*
+//==-===--===-=---:-----------===========@% :%@=*@@@@@@@@@@@@===%=======+@#+=@#%@ .@@%=
+//------=====-------=-:----=#*=========@% :%-.@==@@@@@@@@@@@@++@*=======*@ *= *@@@=. =*=*@@:
+//===--=---===------------=*@@@======@% .%- .#%==#@@@@@@@@@@@%+#@@%=====*@ *+*-#@@@- .. . :. %:
+//--=======--=-=-----:---=--+@@@=-=@% :%.:==+@*==@@@@@@@@@@@@=---##====*@. *:=@ *@. . . :@@@%@
+//===---=---==---------------=@@@@= =@=======#%-@@@@@@@@@@@@=--*@====*@: @:+@@%.@- . . .@@@@@+ =@*=%=#-.
+//=====-----===@@*-----------=@@ *@* =@@=====@+--%@@@@@@@@@@@*-==@====+@= % :@@%@= =#. :. : -*%+#:#
+//-=--=--------@@@==---=--+@@@@@@ +@@@= =#%*===-@=--=@@@@@@@@@@@@+-=@=====@+: :@ :. :@@: %*
+//*@#==--------=@@@----*@@@@@@@@@@@+ -+=@*===%*--=@@@@@@@@@@@@@=-@*-===%*- : =+ =@%@:.: *%. %*
+//%@@+----------+@@@%@@@@@@@@@@@@@@: =*==@+-==@=--@@@@@@@@@@@@@--@*-===%*=:% #: -@@#-: .@@@= +@@@@@%
+//+@@#-------==+@@@@@@@@@@@@@@@@@@@@# :: =====@=--=@-=+@@@@@@@@@@@@@-%*====*#=+% @: .:%@@:. *@@%@@@%
+//=@@@=---=@@@@@@@@@@@@@@@@@@@@@@@@@@@. =@@= ======@=-=%+==@@@@@@@@@@@@@*=#=====@=%* % ..#@@@. -@@@@@
+//=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*=@@* ==-===#*---@*-%@@@@@@@@@@@@@=%=--==@#@=%%. :@ ::*@-%: -#. %@@@@@@.
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@* :+-====@=---%%=@@@@@@@@@@@@@%*=-====@*@= @: -@@+=- :@@* +@@@@@@@@@@@@=
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@: =@- ==.==--=@----*%%@@@@@@@@@@@@@*=-====#@==. @: %@=:@= =@@= =@@@@@@@@@@@
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. .@@% =:.---==%=---=%#@@@@@@@@@@@@@%==-====@+=. *: #%%@.: #@@@@@@@@@@@@@@:
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@::@@- =- --=--*%-----@@@@@@@@@@@@@@@==---===@=. -: :. .@# =@@@@@@@@@@@@@@@@. *@%
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%. @@:= :==---@=----=@@@@@@@@@@@@@@@=--====#* @: . *@@ #@@@@@@@@@@@@@@@@@@@@@-
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@: @@= % ------@=----#@@@@@@@@@@@@@@*-======%+ @ +@. -@@.:@@@@@@@@@@@@@@@@@@@@@@:
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%: @@= .% .-----=@+=---@@@@@@@@@@@@@@@=--==== *: *: :@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@ :=
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@= %.:------@=---*@@@@@@@@@@@@@@@==-==: *= * *@* =@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:@@@*
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%: =*:-----@----+@@@@@@@@@@@@@@@#-====. -@= @@=-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=.@*----%----+@@@@@@@@@@@@@@@@======. +* .+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -%
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=-:@----=@@@@@@@@@@@@@@@@@====== -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ :@@#
+//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%----@@@@@@@@@@@@@@@@@%====== :@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%
+//#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%----%@@@@@@@@@@@@@@@@@*=====- -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//++=+*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%----*@@@@@@@@@@@@@@@@@@+=-==== :@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=
+//=====++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%-----@@@@@@@@@@@@@@@@@@@=-=-==- -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//+===++@@%==*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%-----@@@@@@@@@@@@@@@@@@@@=---=-- :@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//++=++%@@+++=++%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%-----#@@@@@@@@@@@@@@@@@@@*======: :@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//===++@@==+++====*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-----*@@@@@@@@@@@@@@@@@@@@*--=---. .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//=+++=+++++++++++*@@%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=-==-=@@@@@@@@@@@@@@@@@@@@@===----: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//===++==+++++++=%@@+==*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+=====@%@@@@@@@@@@@@@@@@@@@@=-----=: %@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//++=====++++===*@%====+==@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*=====@*@@@@@@@@@@@@@@@@@@@@@-------: :@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//++===+++++====++++========@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*=====@+=@@@@@@@@@@@@@@@@@@@@@=------=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//=========+==++++=========+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%=====#@=*@@@@@@@@@@@@@@@@@@@@%%----=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//=+++++========+========+@@@*=+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%=====+@@@@@@@@@@@@@@@@@@@@@@@@=@+-=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//++++++====++=====*@@%==%@*=====#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%@@ %=====+% @@@@@@@@@@@@@@@@@@@@@@*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//==+==+===+++++==*@@=*@@#+=======+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@***%@@*=*@@%**%@@%**@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//=++==+==++++++++%@@=@*@@%=========*@@@@@@@ %* %+ @= :@+ * @ =@@ @@@@. *# @ %@ # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//=======++++++#@@+%@@@@@#=========%@@@@@@@@: :: #: :% @ @+ * @ =@@ -: @@@@ @= * @ % @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//===++==+++%@%*+%@@*+**+==+====+@@@%==%@@@@+ = %* @ %= * @ =@@ -= @@@@: .@: * @ % @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//=+=+====++@@@@@@@@@%=====+==+==#*====+*@@@@ @* @ %= * @ =@@ -= @@@@@ @@: * @ % @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//=======%@@*+@@%+++++++========+======+=+@@@ - @* @ %= * @ =@@ -= @.%@@: :@@: * @ % @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//==+=+@@**@@@+*@@==++==+===+=========+++++@@ @ @* % @+ + @ :%@ -. @ @@: :@@- + @ + -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//======%@@+*@@@++=++==++===++========+++++#@- @ :@@= -@@ %@ * +* @: :@@@ @@% @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//=+@@#@@@@@@=============++=+++=+==+=+%@@@@%@@@@@%=***+=%%@@@%*: -*%%%+:.=@@@*++*#***=. .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//@%+%@@%==*+============+*%@@%*+*%@@@@@@*%@@@@@@@@%#*=+#**#+%*%##*+=*#####+:@@######***#*=*#**@@@@@@@@@@@@@@@#*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//@%==+@@@===============*@ @%% @@+ @* @ *= =% % #+@ % @ -+ @@@= . @ -@ %+ @@@@@@@@@@@@@@@@@@@@@@@@
+//%@@*=================+=*@. @@ :. @ % =# * @. ** @#- =+*:**= =*% @ -* *#@@@@*. *%: @ @ %* *#@@@@@@@@@@@@@@@@@@@@@@@@
+//==@@#===+++======++=+==*@. @@ =: @ @ :# %= %. %@* %= =@ @@% @ =* @@@@@@@= @@: @ . %+ @@@@@@@@@@@@@@@@@@@@@@@@@@
+//%========+====+%@@%+=+=*@. @@ =: @ @ :% @: :@ - =@* %- -@ @@% -* @@@@@= @@: @ %+ @@@@@@@@@@@@@@@@@@@@@@@@
+//**@@@@@%*%@@@@@@*===++=*@. @@ =: @ @ :% =% -% : @* %: -@ @@@ @ =* @%- +@@= @@: @ + . %+ @@@@@@@@@@@@@@@@@@@@@@@@@@
+//@@@@@@@@@@@@@@*======+=*@. *# :. @ % -# * @-+* @# %: -@ @@@ @ =* +**-@@@= @@: @ @ :. %+ *#@@@@@@@@@@@@@@@@@@@@@@@@
+//@@@@@@@@@@@@*====+==++=*@. @ @@* .@* @ .*#. @ @* #: -@ @@% @ -+ =*%@@= @@: @ @ +. #+ *@@@@@@@@@@@@@@@@@@@@@@@
+//@@@@@@@@@@@======++====+*%@@@@%%%@@@@%*+#@@@@@%#%@@#=+**=. =**+=***==*#*: :%@@@@@@@@@**=+#*==*###*=. -=**+::@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+//@@@@@@@@@@+==+==++=====++=====++=+++++++++===+++++#% +@@@* :#%= =@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+ Actor.Energy -= Cost;
+ return true;
+ }
+} \ No newline at end of file
diff --git a/scripts/Entities/Actions/WaitAction.cs.uid b/scripts/Entities/Actions/WaitAction.cs.uid
new file mode 100644
index 0000000..120c8c1
--- /dev/null
+++ b/scripts/Entities/Actions/WaitAction.cs.uid
@@ -0,0 +1 @@
+uid://c24ebgrcsn6yi
diff --git a/scripts/Entities/Actors/AI/BaseAI.cs b/scripts/Entities/Actors/AI/BaseAI.cs
new file mode 100644
index 0000000..bdd1e61
--- /dev/null
+++ b/scripts/Entities/Actors/AI/BaseAI.cs
@@ -0,0 +1,54 @@
+using Godot;
+
+namespace TheLegendOfGustav.Entities.Actors.AI;
+
+/// <summary>
+/// Enum das diferentes IAs disponíveis.
+/// </summary>
+public enum AIType
+{
+ None,
+ DefaultHostile
+};
+
+/// <summary>
+/// base para as IAs do jogo.
+/// </summary>
+public abstract partial class BaseAI : Node
+{
+ /// <summary>
+ /// Corpo controlado pela IA.
+ /// O corpo é a marionete da alma.
+ /// </summary>
+ protected Actor Body { get; set; }
+
+ public override void _Ready()
+ {
+ base._Ready();
+ // Por padrão, a IA é filha do nó de seu corpo.
+ Body = GetParent<Actor>();
+ }
+
+ /// <summary>
+ /// Computa um único turno para o ator controlado.
+ /// Aviso: NPCs não possuem ações gratuitas.
+ /// A IA SEMPRE precisa executar uma ação que custe energia.
+ /// </summary>
+ public abstract void Perform();
+
+ /// <summary>
+ /// Utiliza o pathfinder do mapa para obter um caminho
+ /// da posição atual do ator para um destino qualquer.
+ /// </summary>
+ /// <param name="destination">Destino</param>
+ /// <returns>Vetor com vetores, passo a passo para chegar no destino.</returns>
+ public Godot.Collections.Array<Vector2> GetPathTo(Vector2I destination)
+ {
+ // Arrays do Godot são muito mais confortáveis de manipular, então
+ // eu converto o Array do C# em um array do Godot antes de retornar o caminho.
+ Godot.Collections.Array<Vector2> list = [];
+ Vector2[] path = Body.MapData.Pathfinder.GetPointPath(Body.GridPosition, destination);
+ list.AddRange(path);
+ return list;
+ }
+} \ No newline at end of file
diff --git a/scripts/Entities/Actors/AI/BaseAI.cs.uid b/scripts/Entities/Actors/AI/BaseAI.cs.uid
new file mode 100644
index 0000000..b23724c
--- /dev/null
+++ b/scripts/Entities/Actors/AI/BaseAI.cs.uid
@@ -0,0 +1 @@
+uid://jgm5qk02hism
diff --git a/scripts/Entities/Actors/AI/HostileEnemyAI.cs b/scripts/Entities/Actors/AI/HostileEnemyAI.cs
new file mode 100644
index 0000000..dbcf98d
--- /dev/null
+++ b/scripts/Entities/Actors/AI/HostileEnemyAI.cs
@@ -0,0 +1,84 @@
+using Godot;
+using TheLegendOfGustav.Entities.Actions;
+
+namespace TheLegendOfGustav.Entities.Actors.AI;
+
+/// <summary>
+/// Uma IA simples. Sempre tentará atacar o jogador com ataques corpo a corpo.
+/// </summary>
+public partial class HostileEnemyAI : BaseAI
+{
+ /// <summary>
+ /// Caminho até a última posição conhecida do jogador.
+ /// </summary>
+ private Godot.Collections.Array<Vector2> Path { get; set; } = [];
+
+ public override void Perform()
+ {
+ // O alvo da IA sempre é o jogador.
+ Player target = Body.MapData.Player;
+ // Vetor que parte do inimigo até o jogador.
+ Vector2I offset = target.GridPosition - Body.GridPosition;
+ // Distância entre o inimigo e o jogador. Leva em consideração somente
+ // um dos eixos.
+ int distance = int.Max(int.Abs(offset.X), int.Abs(offset.Y));
+
+ // A ação executada no turno pode ser de ataque ou de movimento.
+ Action action;
+
+ // Só faz sentido atacar o jogador se o inimigo estiver visível.
+ if (Body.MapData.GetTile(Body.GridPosition).IsInView)
+ {
+ // Se o inimigo consegue ver que o jogador está morto,
+ // IT'S OVER.
+ if (!target.IsAlive)
+ {
+ action = new WaitAction(Body);
+ action.Perform();
+ return;
+ }
+
+ // Se estiver do lado do jogador, ataque.
+ if (distance <= 1)
+ {
+ action = new MeleeAction(Body, offset);
+ action.Perform();
+ // Executada a ação, acabamos nosso turno aqui.
+ return;
+ }
+
+ // Se o inimigo estiver visível para o jogador,
+ // consideramos que ele também consiga ver o jogador.
+ // Logo, atualizamos o caminho para a posição atual do jogador.
+ Path = GetPathTo(target.GridPosition);
+ // O primeiro passo é a posição atual do inimigo, podemos remover.
+ Path.RemoveAt(0);
+ }
+
+ // Se existir um caminho conhecido para o jogador.
+ if (Path.Count > 0)
+ {
+ // Pegamos o próximo passo para o destino.
+ Vector2I destination = (Vector2I)Path[0];
+ // Se tiver o caminho estiver bloqueado, paramos o nosso turno aqui.
+ if (Body.MapData.GetBlockingEntityAtPosition(destination) != null)
+ {
+ action = new WaitAction(Body);
+ action.Perform();
+ return;
+ }
+
+ // Caso o contrário, criamos uma nova ação de movimentação e a executamos.
+ action = new MovementAction(Body, destination - Body.GridPosition);
+ action.Perform();
+ // Podemos remover o passo do caminho.
+ Path.RemoveAt(0);
+ return;
+ }
+
+ // Senão, espere.
+ action = new WaitAction(Body);
+ action.Perform();
+ return;
+ }
+} \ No newline at end of file
diff --git a/scripts/Entities/Actors/AI/HostileEnemyAI.cs.uid b/scripts/Entities/Actors/AI/HostileEnemyAI.cs.uid
new file mode 100644
index 0000000..0fa2c32
--- /dev/null
+++ b/scripts/Entities/Actors/AI/HostileEnemyAI.cs.uid
@@ -0,0 +1 @@
+uid://db28cxff4pl3t
diff --git a/scripts/Entities/Actors/Actor.cs b/scripts/Entities/Actors/Actor.cs
new file mode 100644
index 0000000..7e6685f
--- /dev/null
+++ b/scripts/Entities/Actors/Actor.cs
@@ -0,0 +1,251 @@
+using Godot;
+using TheLegendOfGustav.Map;
+using TheLegendOfGustav.Utils;
+
+namespace TheLegendOfGustav.Entities.Actors;
+
+/// <summary>
+/// A classe de ator define um personagem no jogo.
+/// </summary>
+[GlobalClass]
+public partial class Actor : Entity
+{
+ #region Fields
+ private int mp;
+ private int hp;
+
+ private int energy;
+ #endregion
+
+ #region Constructors
+ public Actor(Vector2I initialPosition, MapData map, ActorDefinition definition) : base(initialPosition, map, definition)
+ {
+ SetDefinition(definition);
+ }
+ #endregion
+
+ #region Signals
+ /// <summary>
+ /// Sinal emitido toda vez que o HP mudar.
+ /// </summary>
+ /// <param name="hp">Novo HP</param>
+ /// <param name="maxHp">Quantidade máxima de HP.</param>
+ [Signal]
+ public delegate void HealthChangedEventHandler(int hp, int maxHp);
+
+ /// <summary>
+ /// Sinal emitido se o ator morrer.
+ /// </summary>
+ [Signal]
+ public delegate void DiedEventHandler();
+ #endregion
+
+ #region Properties
+ /// <summary>
+ /// Se o ator está vivo.
+ /// </summary>
+ public bool IsAlive { get => Hp > 0; }
+
+ /// <summary>
+ /// Utilizado no sistema de turnos.
+ /// Enquanto o ator tiver energia, ele poderá realizar turnos.
+ /// </summary>
+ public int Energy
+ {
+ get => energy;
+ set
+ {
+ if (value > Speed)
+ {
+ energy = Speed;
+ }
+ else
+ {
+ energy = value;
+ }
+ }
+ }
+ /// <summary>
+ /// Taxa de recarga de energia.
+ /// </summary>
+ public int Speed { get => Definition.Speed; }
+
+ /// <summary>
+ /// HP máximo do ator.
+ /// </summary>
+ public int MaxHp { get; private set; }
+ /// <summary>
+ /// HP atual do ator.
+ /// </summary>
+ 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();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Máximo de mana do ator.
+ /// </summary>
+ public int MaxMp { get; private set; }
+ /// <summary>
+ /// Mana atual do ator.
+ /// </summary>
+ public int Mp
+ {
+ get => mp;
+ set
+ {
+ mp = int.Clamp(value, 0, MaxMp);
+ }
+ }
+
+ /// <summary>
+ /// Estatística de ataque
+ /// </summary>
+ public int Atk { get; private set; }
+
+ /// <summary>
+ /// Estatística de defesa.
+ /// </summary>
+ public int Def { get; private set; }
+
+ /// <summary>
+ /// Estatística mental.
+ /// </summary>
+ public int Men { get; private set; }
+
+ /// <summary>
+ /// A definição do ator possui caracterísitcas padrões que definem
+ /// o ator em questão.
+ /// </summary>
+ private ActorDefinition Definition
+ {
+ get;
+ set;
+ }
+ #endregion
+
+ #region Methods
+ /// <summary>
+ /// Executado uma vez por turno,
+ /// </summary>
+ public void RechargeEnergy()
+ {
+ Energy += Speed;
+ }
+
+ /// <summary>
+ /// Move o ator para uma localização. Veja MovementAction.
+ /// </summary>
+ /// <param name="offset">Vetor que parte da posição do ator até o seu destino.</param>
+ 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.
+ }
+
+
+ /// <summary>
+ /// Recupera uma quantidade de HP do ator.
+ /// </summary>
+ /// <param name="amount">HP para recuperar</param>
+ /// <returns>Quanto HP foi realmente recuperado.</returns>
+ public int Heal(int amount)
+ {
+ int neoHp = Hp + amount;
+
+ if (neoHp > MaxHp) neoHp = MaxHp;
+
+ int recovered = neoHp - Hp;
+ Hp = neoHp;
+ return recovered;
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="definition">A definição do ator.</param>
+ public virtual void SetDefinition(ActorDefinition definition)
+ {
+ base.SetDefinition(definition);
+ 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);
+ }
+ #endregion
+} \ No newline at end of file
diff --git a/scripts/Entities/Actors/Actor.cs.uid b/scripts/Entities/Actors/Actor.cs.uid
new file mode 100644
index 0000000..cf29b40
--- /dev/null
+++ b/scripts/Entities/Actors/Actor.cs.uid
@@ -0,0 +1 @@
+uid://c0cm4woy8lawl
diff --git a/scripts/Entities/Actors/ActorDefinition.cs b/scripts/Entities/Actors/ActorDefinition.cs
new file mode 100644
index 0000000..5bd8073
--- /dev/null
+++ b/scripts/Entities/Actors/ActorDefinition.cs
@@ -0,0 +1,32 @@
+using Godot;
+
+namespace TheLegendOfGustav.Entities.Actors;
+
+/// <summary>
+/// Define de forma genérica as características de um ator.
+/// </summary>
+[GlobalClass]
+public partial class ActorDefinition : EntityDefinition
+{
+ [ExportCategory("Visuals")]
+ // Sprite de morto
+ [Export]
+ public Texture2D deathTexture;
+
+ [ExportCategory("Mechanics")]
+ [Export]
+ public int Speed { get; set; } = 10;
+
+ // Estatísticas padrão do ator.
+ [ExportCategory("Stats")]
+ [Export]
+ public int Hp;
+ [Export]
+ public int Mp;
+ [Export]
+ public int Atk;
+ [Export]
+ public int Def;
+ [Export]
+ public int Men;
+}
diff --git a/scripts/Entities/Actors/ActorDefinition.cs.uid b/scripts/Entities/Actors/ActorDefinition.cs.uid
new file mode 100644
index 0000000..ddcfe02
--- /dev/null
+++ b/scripts/Entities/Actors/ActorDefinition.cs.uid
@@ -0,0 +1 @@
+uid://crxw1e37xlrrt
diff --git a/scripts/Entities/Actors/Enemy.cs b/scripts/Entities/Actors/Enemy.cs
new file mode 100644
index 0000000..c152a0b
--- /dev/null
+++ b/scripts/Entities/Actors/Enemy.cs
@@ -0,0 +1,54 @@
+using Godot;
+using TheLegendOfGustav.Entities.Actors.AI;
+using TheLegendOfGustav.Map;
+
+namespace TheLegendOfGustav.Entities.Actors;
+
+/// <summary>
+/// Um inimigo é uma espécie de ator que é
+/// hostil ao jogador. Inimigos são controlados por IA.
+/// </summary>
+public partial class Enemy : Actor
+{
+ public Enemy(Vector2I initialPosition, MapData map, EnemyDefinition definition) : base(initialPosition, map, definition)
+ {
+ Definition = definition;
+ SetDefinition(definition);
+ }
+
+ /// <summary>
+ /// A alma do ator. Gera ações que são executadas todo turno.
+ /// </summary>
+ public BaseAI Soul { get; private set; }
+
+ private EnemyDefinition Definition { get; set; }
+
+ /// <summary>
+ /// Além de definir as características gerais de um ator,
+ /// também define qual IA utilizar.
+ /// </summary>
+ /// <param name="definition">Definição do inimigo.</param>
+ public void SetDefinition(EnemyDefinition definition)
+ {
+ // Definimos as características do ator.
+ base.SetDefinition(Definition);
+
+ // Definimos qual IA utilizar.
+ switch (definition.AI)
+ {
+ case AIType.None:
+ break;
+ case AIType.DefaultHostile:
+ Soul = new HostileEnemyAI();
+ AddChild(Soul);
+ break;
+ }
+ }
+
+ public override void Die()
+ {
+ Soul.QueueFree();
+ Soul = null;
+ base.Die();
+ }
+}
diff --git a/scripts/Entities/Actors/Enemy.cs.uid b/scripts/Entities/Actors/Enemy.cs.uid
new file mode 100644
index 0000000..93255b7
--- /dev/null
+++ b/scripts/Entities/Actors/Enemy.cs.uid
@@ -0,0 +1 @@
+uid://bef1fo3vgvxej
diff --git a/scripts/Entities/Actors/EnemyDefinition.cs b/scripts/Entities/Actors/EnemyDefinition.cs
new file mode 100644
index 0000000..97f8f13
--- /dev/null
+++ b/scripts/Entities/Actors/EnemyDefinition.cs
@@ -0,0 +1,15 @@
+using Godot;
+using TheLegendOfGustav.Entities.Actors.AI;
+
+namespace TheLegendOfGustav.Entities.Actors;
+
+/// <summary>
+/// Além das configurações do ator, também possui qual IA utilizar.
+/// </summary>
+[GlobalClass]
+public partial class EnemyDefinition : ActorDefinition
+{
+ [ExportCategory("AI")]
+ [Export]
+ public AIType AI;
+} \ No newline at end of file
diff --git a/scripts/Entities/Actors/EnemyDefinition.cs.uid b/scripts/Entities/Actors/EnemyDefinition.cs.uid
new file mode 100644
index 0000000..1ba03e1
--- /dev/null
+++ b/scripts/Entities/Actors/EnemyDefinition.cs.uid
@@ -0,0 +1 @@
+uid://dkfdm2m2scyks
diff --git a/scripts/Entities/Actors/Inventory.cs b/scripts/Entities/Actors/Inventory.cs
new file mode 100644
index 0000000..f65dc59
--- /dev/null
+++ b/scripts/Entities/Actors/Inventory.cs
@@ -0,0 +1,46 @@
+using Godot;
+using TheLegendOfGustav.Entities.Items;
+using TheLegendOfGustav.Map;
+using TheLegendOfGustav.Utils;
+
+namespace TheLegendOfGustav.Entities.Actors;
+
+public partial class Inventory(int capacity) : Node
+{
+ private Player Player { get; set; }
+ public int Capacity { get; private set; } = capacity;
+ public Godot.Collections.Array<ConsumableItem> Items { get; private set; } = [];
+
+ public override void _Ready()
+ {
+ base._Ready();
+ Player = GetParent<Player>();
+ }
+
+ public void Drop(ConsumableItem item)
+ {
+ Items.Remove(item);
+
+ MapData data = Player.MapData;
+
+ data.InsertEntity(item);
+ data.EmitSignal(MapData.SignalName.EntityPlaced, item);
+
+ item.MapData = data;
+ item.GridPosition = Player.GridPosition;
+
+ MessageLogData.Instance.AddMessage($"Você descarta {item.DisplayName}.");
+ }
+
+ public void Add(ConsumableItem item)
+ {
+ if (Items.Count >= Capacity) return;
+
+ Items.Add(item);
+ }
+
+ public void RemoveItem(ConsumableItem item)
+ {
+ Items.Remove(item);
+ }
+} \ No newline at end of file
diff --git a/scripts/Entities/Actors/Inventory.cs.uid b/scripts/Entities/Actors/Inventory.cs.uid
new file mode 100644
index 0000000..05c2beb
--- /dev/null
+++ b/scripts/Entities/Actors/Inventory.cs.uid
@@ -0,0 +1 @@
+uid://isaqxdpou22h
diff --git a/scripts/Entities/Actors/Player.cs b/scripts/Entities/Actors/Player.cs
new file mode 100644
index 0000000..7fd80d4
--- /dev/null
+++ b/scripts/Entities/Actors/Player.cs
@@ -0,0 +1,27 @@
+using Godot;
+using TheLegendOfGustav.Map;
+
+namespace TheLegendOfGustav.Entities.Actors;
+
+/// <summary>
+/// Classe do jogador. Por enquanto não é diferente do Ator, mas isso pode mudar.
+/// </summary>
+[GlobalClass]
+public partial class Player : Actor
+{
+ public Player(Vector2I initialPosition, MapData map, PlayerDefinition definition) : base(initialPosition, map, definition)
+ {
+ Definition = definition;
+ SetDefinition(definition);
+ }
+
+ private PlayerDefinition Definition { get; set; }
+ public Inventory Inventory { get; private set; }
+
+ public void SetDefinition(PlayerDefinition definition)
+ {
+ Inventory = new(definition.InventoryCapacity);
+
+ AddChild(Inventory);
+ }
+}
diff --git a/scripts/Entities/Actors/Player.cs.uid b/scripts/Entities/Actors/Player.cs.uid
new file mode 100644
index 0000000..8229b7f
--- /dev/null
+++ b/scripts/Entities/Actors/Player.cs.uid
@@ -0,0 +1 @@
+uid://c840l08453pu2
diff --git a/scripts/Entities/Actors/PlayerDefinition.cs b/scripts/Entities/Actors/PlayerDefinition.cs
new file mode 100644
index 0000000..58ae6b4
--- /dev/null
+++ b/scripts/Entities/Actors/PlayerDefinition.cs
@@ -0,0 +1,11 @@
+using Godot;
+
+namespace TheLegendOfGustav.Entities.Actors;
+
+[GlobalClass]
+public partial class PlayerDefinition : ActorDefinition
+{
+ [ExportCategory("Player Mechanics")]
+ [Export]
+ public int InventoryCapacity = 0;
+} \ No newline at end of file
diff --git a/scripts/Entities/Actors/PlayerDefinition.cs.uid b/scripts/Entities/Actors/PlayerDefinition.cs.uid
new file mode 100644
index 0000000..9d01ab9
--- /dev/null
+++ b/scripts/Entities/Actors/PlayerDefinition.cs.uid
@@ -0,0 +1 @@
+uid://bd78nfh1tsjq6
diff --git a/scripts/Entities/Entity.cs b/scripts/Entities/Entity.cs
new file mode 100644
index 0000000..412bd7a
--- /dev/null
+++ b/scripts/Entities/Entity.cs
@@ -0,0 +1,127 @@
+using Godot;
+using TheLegendOfGustav.Map;
+using TheLegendOfGustav.Utils;
+
+namespace TheLegendOfGustav.Entities;
+
+/// <summary>
+/// Defino aqui que o jogo irá desenhar
+/// atores em cima de itens e itens acima de corpos.
+/// </summary>
+public enum EntityType
+{
+ CORPSE,
+ ITEM,
+ ACTOR
+};
+
+/// <summary>
+/// Classe para elementos móveis que o jogador pode interagir.
+/// </summary>
+public abstract partial class Entity : Sprite2D
+{
+ private Vector2I gridPosition = Vector2I.Zero;
+
+ private EntityType type;
+ private bool blocksMovement;
+ private string displayName;
+
+ public Entity(Vector2I initialPosition, MapData map, EntityDefinition definition)
+ {
+ GridPosition = initialPosition;
+ MapData = map;
+ Centered = false;
+
+ SetDefinition(definition);
+ }
+
+ /// <summary>
+ /// Usado para definir a camada da entidade no mapa.
+ /// </summary>
+ public EntityType Type
+ {
+ get => type;
+ set
+ {
+ type = value;
+ ZIndex = (int)type;
+ }
+ }
+
+ /// <summary>
+ /// É conveniente ter acesso ao mapa dentro da entidade. Isto porque ela existe dentro
+ /// do mapa, então é necessário ter acesso à algumas informações.
+ /// </summary>
+ public MapData MapData { get; set; }
+
+ /// <summary>
+ /// Posição da entidade no mapa do jogo. Diferentemente de Position, GridPosition tem como formato
+ /// os tiles do mapa.
+ /// </summary>
+ public Vector2I GridPosition
+ {
+ set
+ {
+ gridPosition = value;
+ // O sistema de coordenadas do Godot é em pixels, mas faz mais sentido para o jogo utilizar coordenadas em tiles.
+ // Esta propriedade converte um sistema para o outro automaticamente.
+ Position = Grid.GridToWorld(value);
+ }
+ get => gridPosition;
+ }
+
+ /// <summary>
+ /// Se a entidade bloqueia movimento (não pode oculpar a mesma célula de outra entidade.)
+ /// </summary>
+ public bool BlocksMovement
+ {
+ get => blocksMovement;
+ protected set
+ {
+ blocksMovement = value;
+ }
+ }
+
+ /// <summary>
+ /// Nome da entidade.
+ /// </summary>
+ public string DisplayName
+ {
+ get => displayName;
+ protected set
+ {
+ displayName = value;
+ }
+ }
+
+ /// <summary>
+ /// A definição da entidade possui caracterísitcas padrões que definem
+ /// a entidade em questão.
+ /// </summary>
+ private EntityDefinition Definition;
+
+ public override void _Ready()
+ {
+ base._Ready();
+ // Quando a entidade for carregada completamente, atualizamos sua posição para refletir
+ // sua posição real.
+ GridPosition = Grid.WorldToGrid(Position);
+ }
+
+
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="definition">A definição do ator.</param>
+ public virtual void SetDefinition(EntityDefinition definition)
+ {
+ Definition = definition;
+ BlocksMovement = definition.blocksMovement;
+ DisplayName = definition.name;
+ Type = definition.Type;
+ Texture = definition.texture;
+ }
+} \ No newline at end of file
diff --git a/scripts/Entities/Entity.cs.uid b/scripts/Entities/Entity.cs.uid
new file mode 100644
index 0000000..f178d64
--- /dev/null
+++ b/scripts/Entities/Entity.cs.uid
@@ -0,0 +1 @@
+uid://w1325qe64c6w
diff --git a/scripts/Entities/EntityDefinition.cs b/scripts/Entities/EntityDefinition.cs
new file mode 100644
index 0000000..a6080bd
--- /dev/null
+++ b/scripts/Entities/EntityDefinition.cs
@@ -0,0 +1,23 @@
+using Godot;
+
+namespace TheLegendOfGustav.Entities;
+
+[GlobalClass]
+public partial class EntityDefinition : Resource
+{
+ [ExportCategory("Entity Visuals")]
+ // Nome da entidade.
+ [Export]
+ public string name = "unnamed";
+ // Seu sprite.
+ [Export]
+ public Texture2D texture;
+ // A camada da entidade.
+ [Export]
+ public EntityType Type;
+
+ [ExportCategory("Entity Mechanics")]
+ // Se a entidade bloqueia movimento.
+ [Export]
+ public bool blocksMovement = true;
+} \ No newline at end of file
diff --git a/scripts/Entities/EntityDefinition.cs.uid b/scripts/Entities/EntityDefinition.cs.uid
new file mode 100644
index 0000000..0aed6ab
--- /dev/null
+++ b/scripts/Entities/EntityDefinition.cs.uid
@@ -0,0 +1 @@
+uid://bp33ly3hnjwk0
diff --git a/scripts/Entities/Items/ConsumableItem.cs b/scripts/Entities/Items/ConsumableItem.cs
new file mode 100644
index 0000000..f70983a
--- /dev/null
+++ b/scripts/Entities/Items/ConsumableItem.cs
@@ -0,0 +1,40 @@
+using Godot;
+using TheLegendOfGustav.Entities.Actions;
+using TheLegendOfGustav.Entities.Actors;
+using TheLegendOfGustav.Map;
+
+namespace TheLegendOfGustav.Entities.Items;
+
+/// <summary>
+/// Classe para itens consumíveis.
+/// Itens consumíveis são itens de uso limitado.
+/// </summary>
+public abstract partial class ConsumableItem(Vector2I initialPosition, MapData map, EntityDefinition definition) : Entity(initialPosition, map, definition)
+{
+
+ /// <summary>
+ /// Gera uma ação onde o ator consome o item.
+ /// </summary>
+ /// <param name="consumer"></param>
+ /// <returns></returns>
+ public Action GetAction(Player consumer)
+ {
+ return new ItemAction(consumer, this);
+ }
+
+ /// <summary>
+ /// Ativa a função deste item.
+ /// Este método é chamado pela ação gerada por ele mesmo.
+ /// Este método permite definir condições para a sua ativação.
+ /// </summary>
+ /// <param name="action">Ação gerada pelo item.</param>
+ /// <returns>Se a ação foi realizada ou não.</returns>
+ public abstract bool Activate(ItemAction action);
+
+ public void ConsumedBy(Player consumer)
+ {
+ Inventory inventory = consumer.Inventory;
+ inventory.RemoveItem(this);
+ QueueFree();
+ }
+} \ No newline at end of file
diff --git a/scripts/Entities/Items/ConsumableItem.cs.uid b/scripts/Entities/Items/ConsumableItem.cs.uid
new file mode 100644
index 0000000..e6c452a
--- /dev/null
+++ b/scripts/Entities/Items/ConsumableItem.cs.uid
@@ -0,0 +1 @@
+uid://hpppt5k743x
diff --git a/scripts/Entities/Items/ConsumableItemDefinition.cs b/scripts/Entities/Items/ConsumableItemDefinition.cs
new file mode 100644
index 0000000..9cadc0b
--- /dev/null
+++ b/scripts/Entities/Items/ConsumableItemDefinition.cs
@@ -0,0 +1,9 @@
+using Godot;
+
+namespace TheLegendOfGustav.Entities.Items;
+
+/// <summary>
+/// Esta classe só existe para agrupar seus descendentes.
+/// </summary>
+[GlobalClass]
+public abstract partial class ConsumableItemDefinition : EntityDefinition; \ No newline at end of file
diff --git a/scripts/Entities/Items/ConsumableItemDefinition.cs.uid b/scripts/Entities/Items/ConsumableItemDefinition.cs.uid
new file mode 100644
index 0000000..9ddc0f6
--- /dev/null
+++ b/scripts/Entities/Items/ConsumableItemDefinition.cs.uid
@@ -0,0 +1 @@
+uid://dpdju2ucehsb0
diff --git a/scripts/Entities/Items/HealingConsumable.cs b/scripts/Entities/Items/HealingConsumable.cs
new file mode 100644
index 0000000..32f76a6
--- /dev/null
+++ b/scripts/Entities/Items/HealingConsumable.cs
@@ -0,0 +1,30 @@
+using Godot;
+using TheLegendOfGustav.Entities.Actors;
+using TheLegendOfGustav.Utils;
+using TheLegendOfGustav.Entities.Actions;
+using TheLegendOfGustav.Map;
+
+namespace TheLegendOfGustav.Entities.Items;
+
+public partial class HealingConsumable(Vector2I initialPosition, MapData map, HealingConsumableDefinition definition) : ConsumableItem(initialPosition, map, definition)
+{
+ private HealingConsumableDefinition Definition { get; set; } = definition;
+ public float HealingPercentage { get; private set; } = definition.healingPercentage;
+
+ public override bool Activate(ItemAction action)
+ {
+ Player consumer = action.Player;
+ int intendedAmount = (int)(HealingPercentage / 100 * consumer.MaxHp);
+ int recovered = consumer.Heal(intendedAmount);
+
+ // Se não tinha o que curar, a ativação falhou.
+ if (recovered == 0)
+ {
+ MessageLogData.Instance.AddMessage("Você já está saudável.");
+ return false;
+ }
+ MessageLogData.Instance.AddMessage($"Você consome {DisplayName} e recupera {recovered} de HP");
+ ConsumedBy(consumer);
+ return true;
+ }
+} \ No newline at end of file
diff --git a/scripts/Entities/Items/HealingConsumable.cs.uid b/scripts/Entities/Items/HealingConsumable.cs.uid
new file mode 100644
index 0000000..8f8f942
--- /dev/null
+++ b/scripts/Entities/Items/HealingConsumable.cs.uid
@@ -0,0 +1 @@
+uid://ct20rmjhaukge
diff --git a/scripts/Entities/Items/HealingConsumableDefinition.cs b/scripts/Entities/Items/HealingConsumableDefinition.cs
new file mode 100644
index 0000000..d0e5850
--- /dev/null
+++ b/scripts/Entities/Items/HealingConsumableDefinition.cs
@@ -0,0 +1,14 @@
+using Godot;
+
+namespace TheLegendOfGustav.Entities.Items;
+
+[GlobalClass]
+public partial class HealingConsumableDefinition : ConsumableItemDefinition
+{
+ ///<summary>
+ /// Porcentagem da vida do ator para restaurar.
+ ///</summary>
+ [ExportCategory("Item Mechanics")]
+ [Export]
+ public float healingPercentage = 10;
+} \ No newline at end of file
diff --git a/scripts/Entities/Items/HealingConsumableDefinition.cs.uid b/scripts/Entities/Items/HealingConsumableDefinition.cs.uid
new file mode 100644
index 0000000..2fd311d
--- /dev/null
+++ b/scripts/Entities/Items/HealingConsumableDefinition.cs.uid
@@ -0,0 +1 @@
+uid://b3qy4gtjfci14