summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/Entities/Actions/ChangeInputStateAction.cs.uid1
-rw-r--r--scripts/Entities/Actions/SpellAction.cs34
-rw-r--r--scripts/Entities/Items/ConsumableItem.cs2
-rw-r--r--scripts/Entities/Items/ScrollConsumable.cs96
-rw-r--r--scripts/InputHandling/CastSpellInputHandler.cs93
-rw-r--r--scripts/InputHandling/CastSpellInputHandler.cs.uid1
-rw-r--r--scripts/InputHandling/InputHandler.cs12
-rw-r--r--scripts/Magic/DamageEffect.cs3
-rw-r--r--scripts/Time/TurnManager.cs8
-rw-r--r--scripts/Utils/SignalBus.cs11
10 files changed, 250 insertions, 11 deletions
diff --git a/scripts/Entities/Actions/ChangeInputStateAction.cs.uid b/scripts/Entities/Actions/ChangeInputStateAction.cs.uid
new file mode 100644
index 0000000..c8f6440
--- /dev/null
+++ b/scripts/Entities/Actions/ChangeInputStateAction.cs.uid
@@ -0,0 +1 @@
+uid://bxlakdvr1lovw
diff --git a/scripts/Entities/Actions/SpellAction.cs b/scripts/Entities/Actions/SpellAction.cs
index 086a52a..f8819b9 100644
--- a/scripts/Entities/Actions/SpellAction.cs
+++ b/scripts/Entities/Actions/SpellAction.cs
@@ -1,23 +1,45 @@
using Godot;
using TheLegendOfGustav.Entities.Actors;
using TheLegendOfGustav.Magic;
+using TheLegendOfGustav.Utils;
namespace TheLegendOfGustav.Entities.Actions;
-public partial class SpellCommand(Actor actor, Vector2I offset, SpellResource spell) : DirectionalAction(actor, offset)
+/// <summary>
+/// Ação para quando o jogador joga um feitiço.
+/// </summary>
+public partial class SpellAction : DirectionalAction
{
- private SpellResource spell = spell;
+ private SpellResource spell;
+
+ public SpellAction(Actor actor, Vector2I offset, SpellResource spell) : base(actor, offset)
+ {
+ this.spell = spell;
+
+ Cost = 5;
+ }
public override bool Perform()
{
- Actor target = null;
+ Actor target;
- if (GetTarget() is Actor actor)
+ if (spell.Type == SpellType.Self)
+ {
+ target = Actor;
+ }
+ else if (GetTarget() is Actor actor)
{
target = actor;
}
+ else
+ {
+ return false;
+ }
- if (spell.Type == SpellType.Ranged && target == null) return false;
+ if (Grid.Distance(Actor.GridPosition, target.GridPosition) > spell.Range)
+ {
+ return false;
+ }
if (Actor.Mp < spell.Cost)
{
@@ -28,6 +50,8 @@ public partial class SpellCommand(Actor actor, Vector2I offset, SpellResource sp
{
effect.Apply(Actor, target);
}
+
+ Actor.Energy -= Cost;
return true;
}
}
diff --git a/scripts/Entities/Items/ConsumableItem.cs b/scripts/Entities/Items/ConsumableItem.cs
index 4078bd3..b672b7d 100644
--- a/scripts/Entities/Items/ConsumableItem.cs
+++ b/scripts/Entities/Items/ConsumableItem.cs
@@ -31,7 +31,7 @@ public abstract partial class ConsumableItem(Vector2I initialPosition, MapData m
/// <returns>Se a ação foi realizada ou não.</returns>
public abstract bool Activate(ItemAction action);
- public void ConsumedBy(Player consumer)
+ public virtual void ConsumedBy(Player consumer)
{
Inventory inventory = consumer.Inventory;
inventory.RemoveItem(this);
diff --git a/scripts/Entities/Items/ScrollConsumable.cs b/scripts/Entities/Items/ScrollConsumable.cs
index d260263..de809a0 100644
--- a/scripts/Entities/Items/ScrollConsumable.cs
+++ b/scripts/Entities/Items/ScrollConsumable.cs
@@ -1,25 +1,113 @@
using Godot;
using TheLegendOfGustav.Entities.Actions;
using TheLegendOfGustav.Entities.Actors;
+using TheLegendOfGustav.InputHandling;
using TheLegendOfGustav.Magic;
using TheLegendOfGustav.Map;
using TheLegendOfGustav.Utils;
namespace TheLegendOfGustav.Entities.Items;
+/// <summary>
+/// Um pergaminho é um item consumível que joga exatamente um feitiço.
+/// Feitiços de pergaminhos são gratuitos e não possuem restrições.
+/// </summary>
+/// <param name="initialPosition">Posição que o item será colocado no mapa.</param>
+/// <param name="map">Mapa que o item será colocado.</param>
+/// <param name="definition">Definição do item.</param>
public partial class ScrollConsumable(Vector2I initialPosition, MapData map, ScrollConsumableDefinition definition) : ConsumableItem(initialPosition, map, definition)
{
- private ScrollConsumableDefinition definition = definition;
-
public SpellResource Spell { get; private set; } = definition.Spell;
+ /// <summary>
+ /// O pergaminho só pode ser usado uma única vez.
+ /// Alguns feitiços precisam de um alvo, o que significa que
+ /// o pergaminho não pode se auto-destruir enquanto o jogador não
+ /// escolher uma posição válida.
+ ///
+ /// Esta variável garante que seja impossível de usar o pergaminho enquanto o jogador
+ /// escolhe um alvo.
+ /// </summary>
+ private bool awaitingTermination = false;
+
+ private ScrollConsumableDefinition definition = definition;
+
+ /// <summary>
+ /// O godot exige que desconectemos sinais customizados antes de
+ /// apagar nós.
+ ///
+ /// Esta variável guarda a expressão lambda que usamos no sinal PlayerSpellCast
+ /// para ser desconectado em ConsumedBy.
+ /// </summary>
+ private SignalBus.PlayerSpellCastEventHandler bindSignal = null;
public override bool Activate(ItemAction action)
{
+ if (awaitingTermination)
+ {
+ return false;
+ }
+ // Feitiços de pergaminhos são gratuitos.
+ Spell.Cost = 0;
+
Player consumer = action.Player;
- MessageLogData.Instance.AddMessage("Foste cuckado");
- ConsumedBy(consumer);
+ // Alguns feitiços precisam de um alvo escolhido pelo jogador.
+ // Não podemos esperar pelo jogador aqui, a função precisa retornar de forma síncrona.
+ //
+ // Então, se o feitiço precisar de um alvo, foi montada uma infraestrutura de sinais
+ // para garantir que:
+ // 1. O jogador possa escolher uma localização qualquer.
+ // 2. Uma ação seja gerada com a posição escolhida e dentro de um inputHandler para entrar
+ // no ciclo de turnos normalmente.
+ // 3. O pergaminho seja destruído quando o feitiço for executado com sucesso.
+ if (Spell.Type == SpellType.Ranged)
+ {
+ // Este delegate existe somente para que eu possa desconectar o sinal de quando o feitiço é executado.
+ // A engine exige que desconectemos sinais customizados antes de apagar um nó.
+ bindSignal = delegate (bool success) { OnPlayerChoseTarget(success, action.Player); };
+
+ // Eu mando dois sinais aqui. um deles avisa para o input handler trocar para o estado de
+ // escolher posição de feitiço e o outro informa este inputhandler o feitiço deste pergaminho.
+ SignalBus.Instance.EmitSignal(SignalBus.SignalName.CommandInputHandler, (int)InputHandlers.CastSpell);
+ SignalBus.Instance.EmitSignal(SignalBus.SignalName.PlayerSpellChooseLocation, Spell);
+
+ // Eu também conecto nosso delegate declarado anteriormente para quando o feitiço for executado.
+ SignalBus.Instance.PlayerSpellCast += bindSignal;
+ awaitingTermination = true;
+
+ return true;
+ }
+
return true;
}
+
+ public override void ConsumedBy(Player consumer)
+ {
+ // De novo, a engine exige que desconectemos o sinal antes de destruir o pergaminho.
+ if (bindSignal != null)
+ {
+ SignalBus.Instance.PlayerSpellCast -= bindSignal;
+ }
+ base.ConsumedBy(consumer);
+ }
+
+ /// <summary>
+ /// Este método é executado quando o feitiço deste pergaminho for executado
+ /// (depois do jogador escolher um alvo.)
+ /// </summary>
+ /// <param name="success">Se o feitiço for executado com sucesso.</param>
+ /// <param name="consumer">Quem ativou o feitiço.</param>
+ private void OnPlayerChoseTarget(bool success, Player consumer) {
+ if (success)
+ {
+ ConsumedBy(consumer);
+ }
+ else
+ {
+ // Se o feitiço não for ativado com sucesso,
+ // podemos reutilizar o pergaminho.
+ awaitingTermination = false;
+ }
+ }
} \ No newline at end of file
diff --git a/scripts/InputHandling/CastSpellInputHandler.cs b/scripts/InputHandling/CastSpellInputHandler.cs
new file mode 100644
index 0000000..1c5d005
--- /dev/null
+++ b/scripts/InputHandling/CastSpellInputHandler.cs
@@ -0,0 +1,93 @@
+using Godot;
+using TheLegendOfGustav.Entities.Actors;
+using TheLegendOfGustav.Entities.Actions;
+using TheLegendOfGustav.Utils;
+
+using TheLegendOfGustav.InputHandling;
+using TheLegendOfGustav.Map;
+using TheLegendOfGustav.Magic;
+
+namespace TheLengendOfGustav.InputHandling;
+
+/// <summary>
+/// Esquema de controles para quando o jogador precisar escolher um alvo
+/// de feitiço.
+///
+/// É bem similar ao InspectorInputHandler.
+/// </summary>
+public partial class CastSpellInputHandler : BaseInputHandler
+{
+ private static readonly PackedScene inspectorScene = GD.Load<PackedScene>("res://scenes/Inspector.tscn");
+
+ private static readonly Godot.Collections.Dictionary<string, Vector2I> directions = new()
+ {
+ {"walk-up", Vector2I.Up},
+ {"walk-down", Vector2I.Down},
+ {"walk-left", Vector2I.Left},
+ {"walk-right", Vector2I.Right},
+ {"walk-up-right", Vector2I.Up + Vector2I.Right},
+ {"walk-up-left", Vector2I.Up + Vector2I.Left},
+ {"walk-down-right", Vector2I.Down + Vector2I.Right},
+ {"walk-down-left", Vector2I.Down + Vector2I.Left},
+ };
+
+ private Inspector inspector = null;
+ private SpellResource selectedSpell = null;
+ [Export]
+ private Map map;
+
+ public override void _Ready()
+ {
+ base._Ready();
+
+ // O jogador informa qual feitiço será usado.
+ SignalBus.Instance.PlayerSpellChooseLocation += (SpellResource spell) => selectedSpell = spell;
+ }
+
+ public override void Enter()
+ {
+ inspector = inspectorScene.Instantiate<Inspector>();
+
+ inspector.GridPosition = map.MapData.Player.GridPosition;
+
+ map.AddChild(inspector);
+ }
+
+ public override void Exit()
+ {
+ selectedSpell = null;
+ inspector.QueueFree();
+ }
+
+
+ public override Action GetAction(Player player)
+ {
+ Action action = null;
+
+ foreach (var direction in directions)
+ {
+ if (Input.IsActionJustPressed(direction.Key))
+ {
+ inspector.Walk(direction.Value);
+ }
+ }
+
+ if (selectedSpell != null && Input.IsActionJustPressed("ui_accept"))
+ {
+ action = new SpellAction(player, inspector.GridPosition - player.GridPosition, selectedSpell);
+
+ GetParent<InputHandler>().SetInputHandler(InputHandlers.MainGame);
+ }
+
+ // Se o jogador cancelar a seleção,
+ // Mandamos um sinal avisando que o feitiço não foi executado com sucesso.
+ // Pergaminhos usam esta informação.
+ if (Input.IsActionJustPressed("quit"))
+ {
+ SignalBus.Instance.EmitSignal(SignalBus.SignalName.PlayerSpellCast, false);
+ GetParent<InputHandler>().SetInputHandler(InputHandlers.MainGame);
+ }
+
+ return action;
+ }
+} \ No newline at end of file
diff --git a/scripts/InputHandling/CastSpellInputHandler.cs.uid b/scripts/InputHandling/CastSpellInputHandler.cs.uid
new file mode 100644
index 0000000..26ed706
--- /dev/null
+++ b/scripts/InputHandling/CastSpellInputHandler.cs.uid
@@ -0,0 +1 @@
+uid://ceck6d5tjpgwf
diff --git a/scripts/InputHandling/InputHandler.cs b/scripts/InputHandling/InputHandler.cs
index 55a17b4..a1e9b03 100644
--- a/scripts/InputHandling/InputHandler.cs
+++ b/scripts/InputHandling/InputHandler.cs
@@ -1,6 +1,8 @@
using Godot;
using TheLegendOfGustav.Entities.Actions;
using TheLegendOfGustav.Entities.Actors;
+using TheLegendOfGustav.Utils;
+using TheLengendOfGustav.InputHandling;
namespace TheLegendOfGustav.InputHandling;
@@ -10,7 +12,8 @@ public enum InputHandlers
GameOver,
Inspect,
Pickup,
- Inventory
+ Inventory,
+ CastSpell
}
/// <summary>
@@ -32,11 +35,18 @@ public partial class InputHandler : Node
InputHandlerDict.Add(InputHandlers.MainGame, GetNode<MainGameInputHandler>("MainGameInputHandler"));
// Controles para quando o jogador está morto.
InputHandlerDict.Add(InputHandlers.GameOver, GetNode<GameOverInputHandler>("GameOverInputHandler"));
+ // Controles para observar o cenário
InputHandlerDict.Add(InputHandlers.Inspect, GetNode<InspectInputHandler>("InspectInputHandler"));
+ // Controles para pegar um item do chão.
InputHandlerDict.Add(InputHandlers.Pickup, GetNode<PickupInputHandler>("PickupInputHandler"));
+ // Controles para quando o inventário for aberto.
InputHandlerDict.Add(InputHandlers.Inventory, GetNode<InventoryInputHandler>("InventoryInputHandler"));
+ // Controles para quando o jogador precisar escolher um alvo de feitiço.
+ InputHandlerDict.Add(InputHandlers.CastSpell, GetNode<CastSpellInputHandler>("CastSpellInputHandler"));
SetInputHandler(StartingInputHandler);
+
+ SignalBus.Instance.CommandInputHandler += SetInputHandler;
}
public Action GetAction(Player player)
diff --git a/scripts/Magic/DamageEffect.cs b/scripts/Magic/DamageEffect.cs
index abd2cca..0b2b3d6 100644
--- a/scripts/Magic/DamageEffect.cs
+++ b/scripts/Magic/DamageEffect.cs
@@ -1,5 +1,6 @@
using Godot;
using TheLegendOfGustav.Entities.Actors;
+using TheLegendOfGustav.Utils;
namespace TheLegendOfGustav.Magic;
@@ -13,6 +14,8 @@ public partial class DamageEffect : SpellEffect
{
int damageDealt = Damage - target.Men;
+ MessageLogData.Instance.AddMessage($"{caster.DisplayName} aplica {damageDealt} de dano mágico em {target.DisplayName}");
+
target.Hp -= damageDealt;
}
} \ No newline at end of file
diff --git a/scripts/Time/TurnManager.cs b/scripts/Time/TurnManager.cs
index 9713fea..3937aa0 100644
--- a/scripts/Time/TurnManager.cs
+++ b/scripts/Time/TurnManager.cs
@@ -3,6 +3,7 @@ using TheLegendOfGustav.Map;
using TheLegendOfGustav.Entities.Actors;
using TheLegendOfGustav.Entities;
using TheLegendOfGustav.Entities.Actions;
+using TheLegendOfGustav.Utils;
namespace TheLegendOfGustav.Time;
@@ -64,6 +65,13 @@ public partial class TurnManager(Map.Map map) : RefCounted
PlayerActionQueue.RemoveAt(0);
actionResult = action.Perform();
+
+ // TODO: Isto é feio, lembre-me de mudar isto antes da entrega final.
+ if (action is SpellAction)
+ {
+ GD.Print(actionResult);
+ SignalBus.Instance.EmitSignal(SignalBus.SignalName.PlayerSpellCast, actionResult);
+ }
}
// Se a ação do jogador for gratuita ou se o jogador ainda possuir energia,
diff --git a/scripts/Utils/SignalBus.cs b/scripts/Utils/SignalBus.cs
index a2aa6ca..bf97c2d 100644
--- a/scripts/Utils/SignalBus.cs
+++ b/scripts/Utils/SignalBus.cs
@@ -1,4 +1,7 @@
+using System;
using Godot;
+using TheLegendOfGustav.InputHandling;
+using TheLegendOfGustav.Magic;
namespace TheLegendOfGustav.Utils;
@@ -20,6 +23,14 @@ public partial class SignalBus : Node
[Signal]
public delegate void ExitInspectionModeEventHandler();
+ [Signal]
+ public delegate void PlayerSpellChooseLocationEventHandler(SpellResource spell);
+ [Signal]
+ public delegate void PlayerSpellCastEventHandler(bool success);
+
+ [Signal]
+ public delegate void CommandInputHandlerEventHandler(InputHandlers state);
+
public override void _Ready()
{
base._Ready();