Line endings: LF everywhere
.gitattributes forces LF (CRLF only for .bat/.cmd), core.autocrlf is off; files that differed only by CRLF are normalized.
This commit is contained in:
1 parent
65e632a66f
commit
968b8d6fe1
20 files changed
+1740
-1710
No files matched your search
@@ -0,0 +1,30 @@
|
|||||||
|
# Line endings: LF everywhere, on every OS, whatever core.autocrlf says.
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# cmd.exe misreads labels and goto in LF batch files, so they stay CRLF.
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.cmd text eol=crlf
|
||||||
|
|
||||||
|
# Binaries: never converted, never diffed as text.
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.webp binary
|
||||||
|
*.svg text
|
||||||
|
*.ogg binary
|
||||||
|
*.wav binary
|
||||||
|
*.mp3 binary
|
||||||
|
*.ttf binary
|
||||||
|
*.otf binary
|
||||||
|
*.woff binary
|
||||||
|
*.woff2 binary
|
||||||
|
*.dll binary
|
||||||
|
*.so binary
|
||||||
|
*.dylib binary
|
||||||
|
*.pdb binary
|
||||||
|
*.exe binary
|
||||||
|
*.zip binary
|
||||||
|
*.pck binary
|
||||||
|
*.moc3 binary
|
||||||
|
*.res binary
|
||||||
|
*.scn binary
|
||||||
+37
-37
@@ -1,37 +1,37 @@
|
|||||||
# Godot 4.x
|
# Godot 4.x
|
||||||
.godot/
|
.godot/
|
||||||
.idea/
|
.idea/
|
||||||
sdk/
|
sdk/
|
||||||
bin/
|
bin/
|
||||||
obj/
|
obj/
|
||||||
|
|
||||||
#ai
|
#ai
|
||||||
.omo/
|
.omo/
|
||||||
.opencode/
|
.opencode/
|
||||||
|
|
||||||
gen/
|
gen/
|
||||||
obj/
|
obj/
|
||||||
bin/
|
bin/
|
||||||
|
|
||||||
*.tres~
|
*.tres~
|
||||||
*.tscn~
|
*.tscn~
|
||||||
|
|
||||||
# GDScript LSP
|
# GDScript LSP
|
||||||
.godot/
|
.godot/
|
||||||
|
|
||||||
# Export
|
# Export
|
||||||
*.exe
|
*.exe
|
||||||
*.dmg
|
*.dmg
|
||||||
*.apk
|
*.apk
|
||||||
*.aab
|
*.aab
|
||||||
*.pck
|
*.pck
|
||||||
*.zip
|
*.zip
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# Editor
|
# Editor
|
||||||
*.import
|
*.import
|
||||||
*.godot.uid
|
*.godot.uid
|
||||||
+101
-101
@@ -1,101 +1,101 @@
|
|||||||
using Cthangover.CardBattle.UI;
|
using Cthangover.CardBattle.UI;
|
||||||
using Cthangover.Core.Battle.Actions;
|
using Cthangover.Core.Battle.Actions;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace Cthangover.CardBattle.Actions
|
namespace Cthangover.CardBattle.Actions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for animated battle actions that move, rotate, and scale card controls
|
/// Base class for animated battle actions that move, rotate, and scale card controls
|
||||||
/// through a sequence of visual phases. Subclasses implement <c>DoInternalAction</c> to advance
|
/// through a sequence of visual phases. Subclasses implement <c>DoInternalAction</c> to advance
|
||||||
/// a frame-by-frame animation state machine driven by <see cref="CardBattleCore"/>'s
|
/// a frame-by-frame animation state machine driven by <see cref="CardBattleCore"/>'s
|
||||||
/// per-frame polling loop (<c>while (!DoAction()) await frame;</c>).
|
/// per-frame polling loop (<c>while (!DoAction()) await frame;</c>).
|
||||||
/// Captures source/target positions and rotations in <see cref="DoStart"/> so that
|
/// Captures source/target positions and rotations in <see cref="DoStart"/> so that
|
||||||
/// <see cref="DoEnd"/> can restore them regardless of what the animation did.
|
/// <see cref="DoEnd"/> can restore them regardless of what the animation did.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class AbstractBattleAction : IBattleAction
|
public abstract class AbstractBattleAction : IBattleAction
|
||||||
{
|
{
|
||||||
/// <summary>Seconds timestamp of the current phase start (used for phase progress); -1 before the first phase.</summary>
|
/// <summary>Seconds timestamp of the current phase start (used for phase progress); -1 before the first phase.</summary>
|
||||||
protected double Timestamp { get; set; } = -1;
|
protected double Timestamp { get; set; } = -1;
|
||||||
/// <summary>Playback speed multiplier applied to all phase durations.</summary>
|
/// <summary>Playback speed multiplier applied to all phase durations.</summary>
|
||||||
protected float Speed { get; set; }
|
protected float Speed { get; set; }
|
||||||
/// <summary>The action definition whose stats drive the animation.</summary>
|
/// <summary>The action definition whose stats drive the animation.</summary>
|
||||||
protected ActionCharacter Action { get; set; }
|
protected ActionCharacter Action { get; set; }
|
||||||
/// <summary>Acting card node.</summary>
|
/// <summary>Acting card node.</summary>
|
||||||
protected CharacterCardNode Source { get; set; }
|
protected CharacterCardNode Source { get; set; }
|
||||||
/// <summary>Receiving card node.</summary>
|
/// <summary>Receiving card node.</summary>
|
||||||
protected CharacterCardNode Target { get; set; }
|
protected CharacterCardNode Target { get; set; }
|
||||||
/// <summary>Visual control of the source card (null-safe abort when freed mid-animation).</summary>
|
/// <summary>Visual control of the source card (null-safe abort when freed mid-animation).</summary>
|
||||||
protected Control SourceControl { get; set; }
|
protected Control SourceControl { get; set; }
|
||||||
/// <summary>Source position captured in <see cref="DoStart"/> for restoration in <see cref="DoEnd"/>.</summary>
|
/// <summary>Source position captured in <see cref="DoStart"/> for restoration in <see cref="DoEnd"/>.</summary>
|
||||||
protected Vector2 SourcePos { get; set; }
|
protected Vector2 SourcePos { get; set; }
|
||||||
/// <summary>Source rotation captured in <see cref="DoStart"/>.</summary>
|
/// <summary>Source rotation captured in <see cref="DoStart"/>.</summary>
|
||||||
protected float SourceRot { get; set; }
|
protected float SourceRot { get; set; }
|
||||||
/// <summary>Visual control of the target card.</summary>
|
/// <summary>Visual control of the target card.</summary>
|
||||||
protected Control TargetControl { get; set; }
|
protected Control TargetControl { get; set; }
|
||||||
/// <summary>Target position captured in <see cref="DoStart"/>.</summary>
|
/// <summary>Target position captured in <see cref="DoStart"/>.</summary>
|
||||||
protected Vector2 TargetPos { get; set; }
|
protected Vector2 TargetPos { get; set; }
|
||||||
/// <summary>Target rotation captured in <see cref="DoStart"/>.</summary>
|
/// <summary>Target rotation captured in <see cref="DoStart"/>.</summary>
|
||||||
protected float TargetRot { get; set; }
|
protected float TargetRot { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Advances the animation by one frame. Called in a loop by <see cref="CardBattleCore.RunEnemyTurn"/>
|
/// Advances the animation by one frame. Called in a loop by <see cref="CardBattleCore.RunEnemyTurn"/>
|
||||||
/// with <c>await root.ToSignal(frame)</c> between calls. Returns <c>true</c> when the animation
|
/// with <c>await root.ToSignal(frame)</c> between calls. Returns <c>true</c> when the animation
|
||||||
/// has completed all phases and the action result has been applied.
|
/// has completed all phases and the action result has been applied.
|
||||||
/// Returns <c>true</c> early if any required node became null (card was destroyed mid-animation).
|
/// Returns <c>true</c> early if any required node became null (card was destroyed mid-animation).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns><c>true</c> when the animation is finished or cannot proceed.</returns>
|
/// <returns><c>true</c> when the animation is finished or cannot proceed.</returns>
|
||||||
public bool DoAction()
|
public bool DoAction()
|
||||||
{
|
{
|
||||||
if (Source == null || Target == null || Action == null || SourceControl == null || TargetControl == null)
|
if (Source == null || Target == null || Action == null || SourceControl == null || TargetControl == null)
|
||||||
return true;
|
return true;
|
||||||
return DoInternalAction();
|
return DoInternalAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Frame tick of the animation; implementors advance phases and return true when finished.</summary>
|
/// <summary>Frame tick of the animation; implementors advance phases and return true when finished.</summary>
|
||||||
protected abstract bool DoInternalAction();
|
protected abstract bool DoInternalAction();
|
||||||
/// <summary>Phase setup called once before the first <see cref="DoInternalAction"/> tick.</summary>
|
/// <summary>Phase setup called once before the first <see cref="DoInternalAction"/> tick.</summary>
|
||||||
protected abstract void DoInternalStart();
|
protected abstract void DoInternalStart();
|
||||||
/// <summary>Cleanup called once after completion — restore captured positions/rotations here.</summary>
|
/// <summary>Cleanup called once after completion — restore captured positions/rotations here.</summary>
|
||||||
protected abstract void DoInternalEnd();
|
protected abstract void DoInternalEnd();
|
||||||
|
|
||||||
protected AbstractBattleAction(CharacterCardNode source, CharacterCardNode target, ActionCharacter action, float speed = 1)
|
protected AbstractBattleAction(CharacterCardNode source, CharacterCardNode target, ActionCharacter action, float speed = 1)
|
||||||
{
|
{
|
||||||
Source = source;
|
Source = source;
|
||||||
Target = target;
|
Target = target;
|
||||||
Action = action;
|
Action = action;
|
||||||
Speed = speed;
|
Speed = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Captures the current positions, rotations, and <see cref="Control"/> references of both source
|
/// Captures the current positions, rotations, and <see cref="Control"/> references of both source
|
||||||
/// and target cards before the animation begins, then delegates to the subclass
|
/// and target cards before the animation begins, then delegates to the subclass
|
||||||
/// <see cref="DoInternalStart"/> for any additional setup (e.g. resetting timestamps).
|
/// <see cref="DoInternalStart"/> for any additional setup (e.g. resetting timestamps).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DoStart()
|
public void DoStart()
|
||||||
{
|
{
|
||||||
if (Source == null || Target == null || Action == null)
|
if (Source == null || Target == null || Action == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SourceControl = Source.GetControlNode();
|
SourceControl = Source.GetControlNode();
|
||||||
SourcePos = SourceControl.GlobalPosition;
|
SourcePos = SourceControl.GlobalPosition;
|
||||||
SourceRot = SourceControl.Rotation;
|
SourceRot = SourceControl.Rotation;
|
||||||
TargetControl = Target.GetControlNode();
|
TargetControl = Target.GetControlNode();
|
||||||
TargetPos = TargetControl.GlobalPosition;
|
TargetPos = TargetControl.GlobalPosition;
|
||||||
TargetRot = TargetControl.Rotation;
|
TargetRot = TargetControl.Rotation;
|
||||||
|
|
||||||
DoInternalStart();
|
DoInternalStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restores source and target card controls to their original positions, rotations, and visual state
|
/// Restores source and target card controls to their original positions, rotations, and visual state
|
||||||
/// captured during <see cref="DoStart"/>. Delegates specific cleanup to <see cref="DoInternalEnd"/>.
|
/// captured during <see cref="DoStart"/>. Delegates specific cleanup to <see cref="DoInternalEnd"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DoEnd()
|
public void DoEnd()
|
||||||
{
|
{
|
||||||
if (Source == null || Target == null || Action == null)
|
if (Source == null || Target == null || Action == null)
|
||||||
return;
|
return;
|
||||||
DoInternalEnd();
|
DoInternalEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+26
-26
@@ -1,26 +1,26 @@
|
|||||||
namespace Cthangover.CardBattle.Actions
|
namespace Cthangover.CardBattle.Actions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Phases of the animated battle action state machine used by <see cref="PhysicsAttackAction"/>
|
/// Phases of the animated battle action state machine used by <see cref="PhysicsAttackAction"/>
|
||||||
/// and <see cref="PhysicsDefenceAction"/>. Each tick, the action evaluates the current phase,
|
/// and <see cref="PhysicsDefenceAction"/>. Each tick, the action evaluates the current phase,
|
||||||
/// interpolates card positions/rotations/scales, and transitions to the next phase when its
|
/// interpolates card positions/rotations/scales, and transitions to the next phase when its
|
||||||
/// duration elapses. The <c>Recover</c> phase signals completion by returning <c>true</c> from <c>DoInternalAction</c>.
|
/// duration elapses. The <c>Recover</c> phase signals completion by returning <c>true</c> from <c>DoInternalAction</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum ActionPhase
|
public enum ActionPhase
|
||||||
{
|
{
|
||||||
/// <summary>Initial delay before any movement.</summary>
|
/// <summary>Initial delay before any movement.</summary>
|
||||||
Wait,
|
Wait,
|
||||||
/// <summary>Source card rotates toward target and begins slight movement.</summary>
|
/// <summary>Source card rotates toward target and begins slight movement.</summary>
|
||||||
Prepare,
|
Prepare,
|
||||||
/// <summary>Source card travels from its position to the target along a movement pattern.</summary>
|
/// <summary>Source card travels from its position to the target along a movement pattern.</summary>
|
||||||
MoveForAttack,
|
MoveForAttack,
|
||||||
/// <summary>Damage/effect is applied to the target card at the point of impact.</summary>
|
/// <summary>Damage/effect is applied to the target card at the point of impact.</summary>
|
||||||
AttackImpact,
|
AttackImpact,
|
||||||
/// <summary>Target card shows knockback, squashing, and shaking reaction.</summary>
|
/// <summary>Target card shows knockback, squashing, and shaking reaction.</summary>
|
||||||
TargetReaction,
|
TargetReaction,
|
||||||
/// <summary>Source card returns toward its original position.</summary>
|
/// <summary>Source card returns toward its original position.</summary>
|
||||||
MoveForBack,
|
MoveForBack,
|
||||||
/// <summary>All cards smoothly return to their original transforms.</summary>
|
/// <summary>All cards smoothly return to their original transforms.</summary>
|
||||||
Recover
|
Recover
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+403
-403
@@ -1,403 +1,403 @@
|
|||||||
using Cthangover.Core.Battle;
|
using Cthangover.Core.Battle;
|
||||||
using Cthangover.Core.Battle.Actions;
|
using Cthangover.Core.Battle.Actions;
|
||||||
using Cthangover.Core.UI.Theming;
|
using Cthangover.Core.UI.Theming;
|
||||||
using Cthangover.Core.Utils;
|
using Cthangover.Core.Utils;
|
||||||
using Cthangover.TurnBattle;
|
using Cthangover.TurnBattle;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
using Cthangover.CardBattle.UI;
|
using Cthangover.CardBattle.UI;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace Cthangover.CardBattle.Actions
|
namespace Cthangover.CardBattle.Actions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Animated battle action that moves the source card toward an enemy target, applies damage/defence
|
/// Animated battle action that moves the source card toward an enemy target, applies damage/defence
|
||||||
/// via <see cref="ActionExecutorHub"/>, and animates the target's knockback/squash reaction.
|
/// via <see cref="ActionExecutorHub"/>, and animates the target's knockback/squash reaction.
|
||||||
/// The source card follows one of five randomly-selected <see cref="MovementPattern"/> trajectories
|
/// The source card follows one of five randomly-selected <see cref="MovementPattern"/> trajectories
|
||||||
/// (direct, arc, zigzag, spiral, dash). This is the visual counterpart of <see cref="PhysicsDamageActionCard"/>
|
/// (direct, arc, zigzag, spiral, dash). This is the visual counterpart of <see cref="PhysicsDamageActionCard"/>
|
||||||
/// — it handles the presentation while the executor handles the stat changes.
|
/// — it handles the presentation while the executor handles the stat changes.
|
||||||
/// Created by <see cref="CardBattleCore.CreateAnimatedAction"/> for enemy turns.
|
/// Created by <see cref="CardBattleCore.CreateAnimatedAction"/> for enemy turns.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PhysicsAttackAction : AbstractBattleAction
|
public class PhysicsAttackAction : AbstractBattleAction
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pre-defined movement trajectory patterns for the source card's approach to the target.
|
/// Pre-defined movement trajectory patterns for the source card's approach to the target.
|
||||||
/// Selected randomly at construction to add visual variety to enemy attack animations.
|
/// Selected randomly at construction to add visual variety to enemy attack animations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum MovementPattern
|
public enum MovementPattern
|
||||||
{
|
{
|
||||||
Direct,
|
Direct,
|
||||||
Arc,
|
Arc,
|
||||||
Zigzag,
|
Zigzag,
|
||||||
Spiral,
|
Spiral,
|
||||||
Dash
|
Dash
|
||||||
}
|
}
|
||||||
|
|
||||||
private ActionPhase phase = ActionPhase.Wait;
|
private ActionPhase phase = ActionPhase.Wait;
|
||||||
private float attackRotation;
|
private float attackRotation;
|
||||||
private bool damageApplied;
|
private bool damageApplied;
|
||||||
private bool stunEffectSpawned;
|
private bool stunEffectSpawned;
|
||||||
|
|
||||||
private Vector2 targetOriginalPos;
|
private Vector2 targetOriginalPos;
|
||||||
private Vector2 targetOriginalScale;
|
private Vector2 targetOriginalScale;
|
||||||
private Vector2 knockbackDirection;
|
private Vector2 knockbackDirection;
|
||||||
private float targetReactionIntensity = 0.3f;
|
private float targetReactionIntensity = 0.3f;
|
||||||
|
|
||||||
private Vector2 knockbackPos;
|
private Vector2 knockbackPos;
|
||||||
private float shake;
|
private float shake;
|
||||||
private Vector2 squashScale;
|
private Vector2 squashScale;
|
||||||
|
|
||||||
private float waitDuration = 0.2f;
|
private float waitDuration = 0.2f;
|
||||||
private float prepareDuration = 0.3f;
|
private float prepareDuration = 0.3f;
|
||||||
private float moveForAttackDuration = 0.4f;
|
private float moveForAttackDuration = 0.4f;
|
||||||
private float attackImpactDuration = 0.3f;
|
private float attackImpactDuration = 0.3f;
|
||||||
private float targetReactionDuration = 0.4f;
|
private float targetReactionDuration = 0.4f;
|
||||||
private float moveForBackDuration = 0.5f;
|
private float moveForBackDuration = 0.5f;
|
||||||
private float recoverDuration = 0.3f;
|
private float recoverDuration = 0.3f;
|
||||||
|
|
||||||
private MovementPattern movementPattern = MovementPattern.Direct;
|
private MovementPattern movementPattern = MovementPattern.Direct;
|
||||||
private Vector2 movementControlPoint;
|
private Vector2 movementControlPoint;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an attack animation from <paramref name="source"/> to <paramref name="target"/>
|
/// Creates an attack animation from <paramref name="source"/> to <paramref name="target"/>
|
||||||
/// using the stats defined in <paramref name="action"/>. The <see cref="MovementPattern"/> is
|
/// using the stats defined in <paramref name="action"/>. The <see cref="MovementPattern"/> is
|
||||||
/// randomly selected on construction to make enemy attacks visually distinct.
|
/// randomly selected on construction to make enemy attacks visually distinct.
|
||||||
/// <paramref name="speed"/> acts as a multiplier on all phase durations.
|
/// <paramref name="speed"/> acts as a multiplier on all phase durations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PhysicsAttackAction(CharacterCardNode source, CharacterCardNode target, ActionCharacter action, float speed = 1)
|
public PhysicsAttackAction(CharacterCardNode source, CharacterCardNode target, ActionCharacter action, float speed = 1)
|
||||||
: base(source, target, action, speed)
|
: base(source, target, action, speed)
|
||||||
{
|
{
|
||||||
movementPattern = (MovementPattern)GD.RandRange(0, 5);
|
movementPattern = (MovementPattern)GD.RandRange(0, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Resets the phase clock and starts the attack sequence from the Wait phase.</summary>
|
/// <summary>Resets the phase clock and starts the attack sequence from the Wait phase.</summary>
|
||||||
protected override void DoInternalStart()
|
protected override void DoInternalStart()
|
||||||
{
|
{
|
||||||
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
||||||
damageApplied = false;
|
damageApplied = false;
|
||||||
stunEffectSpawned = false;
|
stunEffectSpawned = false;
|
||||||
phase = ActionPhase.Wait;
|
phase = ActionPhase.Wait;
|
||||||
targetOriginalScale = TargetControl.Scale;
|
targetOriginalScale = TargetControl.Scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float GetPhaseProgress()
|
private float GetPhaseProgress()
|
||||||
{
|
{
|
||||||
float elapsed = (float)(Godot.Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Godot.Engine.TimeScale;
|
float elapsed = (float)(Godot.Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Godot.Engine.TimeScale;
|
||||||
float duration = GetCurrentPhaseDuration();
|
float duration = GetCurrentPhaseDuration();
|
||||||
return Mathf.Clamp(elapsed / duration, 0f, 1f);
|
return Mathf.Clamp(elapsed / duration, 0f, 1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private float GetCurrentPhaseDuration()
|
private float GetCurrentPhaseDuration()
|
||||||
{
|
{
|
||||||
switch (phase)
|
switch (phase)
|
||||||
{
|
{
|
||||||
case ActionPhase.Wait: return waitDuration;
|
case ActionPhase.Wait: return waitDuration;
|
||||||
case ActionPhase.Prepare: return prepareDuration;
|
case ActionPhase.Prepare: return prepareDuration;
|
||||||
case ActionPhase.MoveForAttack: return moveForAttackDuration;
|
case ActionPhase.MoveForAttack: return moveForAttackDuration;
|
||||||
case ActionPhase.AttackImpact: return attackImpactDuration;
|
case ActionPhase.AttackImpact: return attackImpactDuration;
|
||||||
case ActionPhase.TargetReaction: return targetReactionDuration;
|
case ActionPhase.TargetReaction: return targetReactionDuration;
|
||||||
case ActionPhase.MoveForBack: return moveForBackDuration;
|
case ActionPhase.MoveForBack: return moveForBackDuration;
|
||||||
case ActionPhase.Recover: return recoverDuration;
|
case ActionPhase.Recover: return recoverDuration;
|
||||||
default: return 1f;
|
default: return 1f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetTimestamp()
|
private void ResetTimestamp()
|
||||||
{
|
{
|
||||||
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Advances the multi-phase attack: dash in, impact, damage/stun application, return and recover.</summary>
|
/// <summary>Advances the multi-phase attack: dash in, impact, damage/stun application, return and recover.</summary>
|
||||||
protected override bool DoInternalAction()
|
protected override bool DoInternalAction()
|
||||||
{
|
{
|
||||||
float progress = GetPhaseProgress();
|
float progress = GetPhaseProgress();
|
||||||
|
|
||||||
switch (phase)
|
switch (phase)
|
||||||
{
|
{
|
||||||
case ActionPhase.Wait:
|
case ActionPhase.Wait:
|
||||||
if (progress >= 1f)
|
if (progress >= 1f)
|
||||||
{
|
{
|
||||||
phase = ActionPhase.Prepare;
|
phase = ActionPhase.Prepare;
|
||||||
ResetTimestamp();
|
ResetTimestamp();
|
||||||
targetOriginalPos = TargetControl.GlobalPosition;
|
targetOriginalPos = TargetControl.GlobalPosition;
|
||||||
targetOriginalScale = TargetControl.Scale;
|
targetOriginalScale = TargetControl.Scale;
|
||||||
knockbackDirection = (TargetControl.GlobalPosition - SourceControl.GlobalPosition).Normalized();
|
knockbackDirection = (TargetControl.GlobalPosition - SourceControl.GlobalPosition).Normalized();
|
||||||
InitializeMovementPattern();
|
InitializeMovementPattern();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ActionPhase.Prepare:
|
case ActionPhase.Prepare:
|
||||||
{
|
{
|
||||||
float prepareProgress = EaseOutQuad(Mathf.Min(progress, 1f));
|
float prepareProgress = EaseOutQuad(Mathf.Min(progress, 1f));
|
||||||
Vector2 preparePosition = GetMovementPosition(prepareProgress * 0.1f);
|
Vector2 preparePosition = GetMovementPosition(prepareProgress * 0.1f);
|
||||||
SourceControl.GlobalPosition = SourcePos.Lerp(preparePosition, prepareProgress);
|
SourceControl.GlobalPosition = SourcePos.Lerp(preparePosition, prepareProgress);
|
||||||
SourceControl.Rotation = Mathf.Lerp(SourceRot, TargetRot, prepareProgress * 0.3f);
|
SourceControl.Rotation = Mathf.Lerp(SourceRot, TargetRot, prepareProgress * 0.3f);
|
||||||
|
|
||||||
if (progress >= 1f)
|
if (progress >= 1f)
|
||||||
{
|
{
|
||||||
phase = ActionPhase.MoveForAttack;
|
phase = ActionPhase.MoveForAttack;
|
||||||
ResetTimestamp();
|
ResetTimestamp();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionPhase.MoveForAttack:
|
case ActionPhase.MoveForAttack:
|
||||||
{
|
{
|
||||||
float attackProgress = EaseInOutQuad(Mathf.Min(progress, 1f));
|
float attackProgress = EaseInOutQuad(Mathf.Min(progress, 1f));
|
||||||
Vector2 targetPosition = GetMovementPosition(attackProgress);
|
Vector2 targetPosition = GetMovementPosition(attackProgress);
|
||||||
SourceControl.GlobalPosition = targetPosition;
|
SourceControl.GlobalPosition = targetPosition;
|
||||||
SourceControl.Rotation = Mathf.Lerp(SourceRot, TargetRot, attackProgress);
|
SourceControl.Rotation = Mathf.Lerp(SourceRot, TargetRot, attackProgress);
|
||||||
|
|
||||||
if (progress >= 1f)
|
if (progress >= 1f)
|
||||||
{
|
{
|
||||||
phase = ActionPhase.AttackImpact;
|
phase = ActionPhase.AttackImpact;
|
||||||
ResetTimestamp();
|
ResetTimestamp();
|
||||||
attackRotation = (float)GD.RandRange(-35f, 35f) * Mathf.Pi / 180f;
|
attackRotation = (float)GD.RandRange(-35f, 35f) * Mathf.Pi / 180f;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionPhase.AttackImpact:
|
case ActionPhase.AttackImpact:
|
||||||
{
|
{
|
||||||
float impactProgress = EaseInOutQuad(Mathf.Min(progress * 3f, 1f));
|
float impactProgress = EaseInOutQuad(Mathf.Min(progress * 3f, 1f));
|
||||||
SourceControl.Rotation = Mathf.Lerp(TargetRot, attackRotation, impactProgress);
|
SourceControl.Rotation = Mathf.Lerp(TargetRot, attackRotation, impactProgress);
|
||||||
|
|
||||||
if (!damageApplied && progress >= 0.3f)
|
if (!damageApplied && progress >= 0.3f)
|
||||||
{
|
{
|
||||||
damageApplied = true;
|
damageApplied = true;
|
||||||
var result = ActionExecutorHub.Instance.Execute(Action, Source.Card, Target.Card);
|
var result = ActionExecutorHub.Instance.Execute(Action, Source.Card, Target.Card);
|
||||||
Source.UpdateInfo();
|
Source.UpdateInfo();
|
||||||
Target.UpdateInfo();
|
Target.UpdateInfo();
|
||||||
|
|
||||||
if (result.Result)
|
if (result.Result)
|
||||||
{
|
{
|
||||||
LogAttackResult(result);
|
LogAttackResult(result);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
GameLogger.Log("CARD_ACT",
|
GameLogger.Log("CARD_ACT",
|
||||||
$"PhysicsAttackAction: failed to execute action {Action.Name} (source={Source.Card.Name} target={Target.Card.Name})",
|
$"PhysicsAttackAction: failed to execute action {Action.Name} (source={Source.Card.Name} target={Target.Card.Name})",
|
||||||
LogLevel.Error);
|
LogLevel.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progress >= 1f)
|
if (progress >= 1f)
|
||||||
{
|
{
|
||||||
phase = ActionPhase.TargetReaction;
|
phase = ActionPhase.TargetReaction;
|
||||||
ResetTimestamp();
|
ResetTimestamp();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionPhase.TargetReaction:
|
case ActionPhase.TargetReaction:
|
||||||
{
|
{
|
||||||
float reactionProgress = EaseInOutQuad(Mathf.Min(progress, 1f));
|
float reactionProgress = EaseInOutQuad(Mathf.Min(progress, 1f));
|
||||||
|
|
||||||
bool isStun = Action != null && Action.GetInt("Turn", 0) > 1;
|
bool isStun = Action != null && Action.GetInt("Turn", 0) > 1;
|
||||||
float currentKnockback = targetReactionIntensity;
|
float currentKnockback = targetReactionIntensity;
|
||||||
if (isStun)
|
if (isStun)
|
||||||
currentKnockback *= 2.5f;
|
currentKnockback *= 2.5f;
|
||||||
|
|
||||||
knockbackPos = targetOriginalPos + knockbackDirection * currentKnockback;
|
knockbackPos = targetOriginalPos + knockbackDirection * currentKnockback;
|
||||||
TargetControl.GlobalPosition = targetOriginalPos.Lerp(knockbackPos, reactionProgress);
|
TargetControl.GlobalPosition = targetOriginalPos.Lerp(knockbackPos, reactionProgress);
|
||||||
|
|
||||||
squashScale = targetOriginalScale;
|
squashScale = targetOriginalScale;
|
||||||
float currentSquash = isStun ? 0.35f : 0.2f;
|
float currentSquash = isStun ? 0.35f : 0.2f;
|
||||||
squashScale.X *= 1f - reactionProgress * currentSquash;
|
squashScale.X *= 1f - reactionProgress * currentSquash;
|
||||||
squashScale.Y *= 1f + reactionProgress * (currentSquash * 0.5f);
|
squashScale.Y *= 1f + reactionProgress * (currentSquash * 0.5f);
|
||||||
TargetControl.Scale = squashScale;
|
TargetControl.Scale = squashScale;
|
||||||
|
|
||||||
float currentShakeMultiplier = isStun ? 1.8f : 1f;
|
float currentShakeMultiplier = isStun ? 1.8f : 1f;
|
||||||
shake = Mathf.Sin(reactionProgress * Mathf.Pi * (isStun ? 6f : 4f)) * (1f - reactionProgress) * 0.05f * currentShakeMultiplier;
|
shake = Mathf.Sin(reactionProgress * Mathf.Pi * (isStun ? 6f : 4f)) * (1f - reactionProgress) * 0.05f * currentShakeMultiplier;
|
||||||
TargetControl.Rotation = shake * 30f * Mathf.Pi / 180f;
|
TargetControl.Rotation = shake * 30f * Mathf.Pi / 180f;
|
||||||
|
|
||||||
if (progress >= 1f)
|
if (progress >= 1f)
|
||||||
{
|
{
|
||||||
phase = ActionPhase.MoveForBack;
|
phase = ActionPhase.MoveForBack;
|
||||||
ResetTimestamp();
|
ResetTimestamp();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionPhase.MoveForBack:
|
case ActionPhase.MoveForBack:
|
||||||
{
|
{
|
||||||
float backProgress = EaseOutQuad(Mathf.Min(progress, 1f));
|
float backProgress = EaseOutQuad(Mathf.Min(progress, 1f));
|
||||||
Vector2 backPosition = GetMovementPosition(1f - progress);
|
Vector2 backPosition = GetMovementPosition(1f - progress);
|
||||||
SourceControl.GlobalPosition = backPosition.Lerp(SourcePos, backProgress);
|
SourceControl.GlobalPosition = backPosition.Lerp(SourcePos, backProgress);
|
||||||
SourceControl.Rotation = Mathf.Lerp(attackRotation, SourceRot, backProgress);
|
SourceControl.Rotation = Mathf.Lerp(attackRotation, SourceRot, backProgress);
|
||||||
|
|
||||||
TargetControl.GlobalPosition = knockbackPos.Lerp(targetOriginalPos, backProgress);
|
TargetControl.GlobalPosition = knockbackPos.Lerp(targetOriginalPos, backProgress);
|
||||||
TargetControl.Scale = squashScale.Lerp(targetOriginalScale, backProgress);
|
TargetControl.Scale = squashScale.Lerp(targetOriginalScale, backProgress);
|
||||||
float currentShake = Mathf.Sin(backProgress * Mathf.Pi) * shake * (1f - backProgress);
|
float currentShake = Mathf.Sin(backProgress * Mathf.Pi) * shake * (1f - backProgress);
|
||||||
TargetControl.Rotation = Mathf.Lerp(currentShake * 30f * Mathf.Pi / 180f, TargetRot, backProgress);
|
TargetControl.Rotation = Mathf.Lerp(currentShake * 30f * Mathf.Pi / 180f, TargetRot, backProgress);
|
||||||
|
|
||||||
if (progress >= 1f)
|
if (progress >= 1f)
|
||||||
{
|
{
|
||||||
phase = ActionPhase.Recover;
|
phase = ActionPhase.Recover;
|
||||||
ResetTimestamp();
|
ResetTimestamp();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionPhase.Recover:
|
case ActionPhase.Recover:
|
||||||
{
|
{
|
||||||
float recoverProgress = EaseOutQuad(Mathf.Min(progress, 1f));
|
float recoverProgress = EaseOutQuad(Mathf.Min(progress, 1f));
|
||||||
|
|
||||||
SourceControl.GlobalPosition = SourceControl.GlobalPosition.Lerp(SourcePos, recoverProgress);
|
SourceControl.GlobalPosition = SourceControl.GlobalPosition.Lerp(SourcePos, recoverProgress);
|
||||||
SourceControl.Rotation = Mathf.Lerp(SourceControl.Rotation, SourceRot, recoverProgress);
|
SourceControl.Rotation = Mathf.Lerp(SourceControl.Rotation, SourceRot, recoverProgress);
|
||||||
TargetControl.GlobalPosition = TargetControl.GlobalPosition.Lerp(TargetPos, recoverProgress);
|
TargetControl.GlobalPosition = TargetControl.GlobalPosition.Lerp(TargetPos, recoverProgress);
|
||||||
TargetControl.Rotation = Mathf.Lerp(TargetControl.Rotation, TargetRot, recoverProgress);
|
TargetControl.Rotation = Mathf.Lerp(TargetControl.Rotation, TargetRot, recoverProgress);
|
||||||
TargetControl.Scale = TargetControl.Scale.Lerp(targetOriginalScale, recoverProgress);
|
TargetControl.Scale = TargetControl.Scale.Lerp(targetOriginalScale, recoverProgress);
|
||||||
|
|
||||||
if (progress >= 1f)
|
if (progress >= 1f)
|
||||||
return true;
|
return true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Restores the target's captured position, scale and rotation after the attack animation.</summary>
|
/// <summary>Restores the target's captured position, scale and rotation after the attack animation.</summary>
|
||||||
protected override void DoInternalEnd()
|
protected override void DoInternalEnd()
|
||||||
{
|
{
|
||||||
if (SourceControl != null)
|
if (SourceControl != null)
|
||||||
{
|
{
|
||||||
SourceControl.GlobalPosition = SourcePos;
|
SourceControl.GlobalPosition = SourcePos;
|
||||||
SourceControl.Rotation = SourceRot;
|
SourceControl.Rotation = SourceRot;
|
||||||
}
|
}
|
||||||
if (TargetControl != null)
|
if (TargetControl != null)
|
||||||
{
|
{
|
||||||
TargetControl.GlobalPosition = TargetPos;
|
TargetControl.GlobalPosition = TargetPos;
|
||||||
TargetControl.Rotation = TargetRot;
|
TargetControl.Rotation = TargetRot;
|
||||||
TargetControl.Scale = targetOriginalScale;
|
TargetControl.Scale = targetOriginalScale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mirrors the resolved action onto the UI: floating damage / absorbed
|
/// Mirrors the resolved action onto the UI: floating damage / absorbed
|
||||||
/// shield numbers, a colour pulse on the target card and journal rows, so
|
/// shield numbers, a colour pulse on the target card and journal rows, so
|
||||||
/// an AI attack reads the same as the player's own attack. Stun actions
|
/// an AI attack reads the same as the player's own attack. Stun actions
|
||||||
/// (a "Turn" above 1) flash the electric zap instead of damage numbers.
|
/// (a "Turn" above 1) flash the electric zap instead of damage numbers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void LogAttackResult(ChangedAttributes result)
|
private void LogAttackResult(ChangedAttributes result)
|
||||||
{
|
{
|
||||||
var targetName = TranslationServer.Translate(Target.Card.Name).ToString();
|
var targetName = TranslationServer.Translate(Target.Card.Name).ToString();
|
||||||
var isStun = Action != null && Action.GetInt("Turn", 0) > 1;
|
var isStun = Action != null && Action.GetInt("Turn", 0) > 1;
|
||||||
|
|
||||||
var defenceLost = -result.Target.Defence;
|
var defenceLost = -result.Target.Defence;
|
||||||
if (defenceLost > 0)
|
if (defenceLost > 0)
|
||||||
{
|
{
|
||||||
ShowDamageBehaviour.SpawnDefence(defenceLost, TargetControl, TargetControl.GlobalPosition);
|
ShowDamageBehaviour.SpawnDefence(defenceLost, TargetControl, TargetControl.GlobalPosition);
|
||||||
BattleLogHub.Log(
|
BattleLogHub.Log(
|
||||||
string.Format(Translate("battle/log_shield_absorb"), targetName, defenceLost),
|
string.Format(Translate("battle/log_shield_absorb"), targetName, defenceLost),
|
||||||
UI.BattleLogKind.Shield);
|
UI.BattleLogKind.Shield);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isStun)
|
if (isStun)
|
||||||
{
|
{
|
||||||
Target.PlayZap();
|
Target.PlayZap();
|
||||||
BattleLogHub.Log(
|
BattleLogHub.Log(
|
||||||
string.Format(Translate("battle/log_stun"), targetName, Action.GetInt("Turn", 0)),
|
string.Format(Translate("battle/log_stun"), targetName, Action.GetInt("Turn", 0)),
|
||||||
UI.BattleLogKind.Status);
|
UI.BattleLogKind.Status);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.Target.Damage > 0)
|
if (result.Target.Damage > 0)
|
||||||
{
|
{
|
||||||
ShowDamageBehaviour.SpawnDamage(result.Target.Damage, TargetControl, TargetControl.GlobalPosition);
|
ShowDamageBehaviour.SpawnDamage(result.Target.Damage, TargetControl, TargetControl.GlobalPosition);
|
||||||
Target.Flash(UiTokens.DangerText);
|
Target.Flash(UiTokens.DangerText);
|
||||||
BattleLogHub.Log(
|
BattleLogHub.Log(
|
||||||
string.Format(Translate("battle/log_damage"),
|
string.Format(Translate("battle/log_damage"),
|
||||||
Translate(Action.Name),
|
Translate(Action.Name),
|
||||||
TranslationServer.Translate(Source.Card.Name).ToString(),
|
TranslationServer.Translate(Source.Card.Name).ToString(),
|
||||||
targetName,
|
targetName,
|
||||||
result.Target.Damage),
|
result.Target.Damage),
|
||||||
UI.BattleLogKind.Damage);
|
UI.BattleLogKind.Damage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Translate(string key)
|
private static string Translate(string key)
|
||||||
=> TranslationServer.Translate(key).ToString();
|
=> TranslationServer.Translate(key).ToString();
|
||||||
|
|
||||||
private void InitializeMovementPattern()
|
private void InitializeMovementPattern()
|
||||||
{
|
{
|
||||||
Vector2 direction = (TargetPos - SourcePos).Normalized();
|
Vector2 direction = (TargetPos - SourcePos).Normalized();
|
||||||
Vector2 perpendicular = new Vector2(-direction.Y, direction.X);
|
Vector2 perpendicular = new Vector2(-direction.Y, direction.X);
|
||||||
|
|
||||||
switch (movementPattern)
|
switch (movementPattern)
|
||||||
{
|
{
|
||||||
case MovementPattern.Arc:
|
case MovementPattern.Arc:
|
||||||
movementControlPoint = SourcePos + direction * 0.5f + perpendicular * 2f;
|
movementControlPoint = SourcePos + direction * 0.5f + perpendicular * 2f;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MovementPattern.Zigzag:
|
case MovementPattern.Zigzag:
|
||||||
movementControlPoint = SourcePos + direction * 0.5f + perpendicular * 1.5f;
|
movementControlPoint = SourcePos + direction * 0.5f + perpendicular * 1.5f;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MovementPattern.Spiral:
|
case MovementPattern.Spiral:
|
||||||
movementControlPoint = TargetPos + perpendicular * 1.5f;
|
movementControlPoint = TargetPos + perpendicular * 1.5f;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MovementPattern.Dash:
|
case MovementPattern.Dash:
|
||||||
movementControlPoint = SourcePos + direction * 0.3f;
|
movementControlPoint = SourcePos + direction * 0.3f;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
movementControlPoint = TargetPos;
|
movementControlPoint = TargetPos;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 GetMovementPosition(float progress)
|
private Vector2 GetMovementPosition(float progress)
|
||||||
{
|
{
|
||||||
switch (movementPattern)
|
switch (movementPattern)
|
||||||
{
|
{
|
||||||
case MovementPattern.Direct:
|
case MovementPattern.Direct:
|
||||||
return SourcePos.Lerp(TargetPos, progress);
|
return SourcePos.Lerp(TargetPos, progress);
|
||||||
|
|
||||||
case MovementPattern.Arc:
|
case MovementPattern.Arc:
|
||||||
return SourcePos.Lerp(movementControlPoint, progress)
|
return SourcePos.Lerp(movementControlPoint, progress)
|
||||||
.Lerp(movementControlPoint.Lerp(TargetPos, progress), progress);
|
.Lerp(movementControlPoint.Lerp(TargetPos, progress), progress);
|
||||||
|
|
||||||
case MovementPattern.Zigzag:
|
case MovementPattern.Zigzag:
|
||||||
if (progress < 0.5f)
|
if (progress < 0.5f)
|
||||||
return SourcePos.Lerp(movementControlPoint, progress * 2f);
|
return SourcePos.Lerp(movementControlPoint, progress * 2f);
|
||||||
else
|
else
|
||||||
return movementControlPoint.Lerp(TargetPos, (progress - 0.5f) * 2f);
|
return movementControlPoint.Lerp(TargetPos, (progress - 0.5f) * 2f);
|
||||||
|
|
||||||
case MovementPattern.Spiral:
|
case MovementPattern.Spiral:
|
||||||
{
|
{
|
||||||
Vector2 toTarget = TargetPos - SourcePos;
|
Vector2 toTarget = TargetPos - SourcePos;
|
||||||
float angle = progress * Mathf.Pi * 2f;
|
float angle = progress * Mathf.Pi * 2f;
|
||||||
float radius = (1f - progress) * 1.5f;
|
float radius = (1f - progress) * 1.5f;
|
||||||
Vector2 spiralOffset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * radius;
|
Vector2 spiralOffset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * radius;
|
||||||
return SourcePos.Lerp(TargetPos, progress) + spiralOffset;
|
return SourcePos.Lerp(TargetPos, progress) + spiralOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MovementPattern.Dash:
|
case MovementPattern.Dash:
|
||||||
if (progress < 0.3f)
|
if (progress < 0.3f)
|
||||||
return SourcePos.Lerp(movementControlPoint, progress / 0.3f);
|
return SourcePos.Lerp(movementControlPoint, progress / 0.3f);
|
||||||
else if (progress < 0.7f)
|
else if (progress < 0.7f)
|
||||||
return movementControlPoint;
|
return movementControlPoint;
|
||||||
else
|
else
|
||||||
return movementControlPoint.Lerp(TargetPos, (progress - 0.7f) / 0.3f);
|
return movementControlPoint.Lerp(TargetPos, (progress - 0.7f) / 0.3f);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return SourcePos.Lerp(TargetPos, progress);
|
return SourcePos.Lerp(TargetPos, progress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private float EaseOutQuad(float t)
|
private float EaseOutQuad(float t)
|
||||||
{
|
{
|
||||||
return 1f - (1f - t) * (1f - t);
|
return 1f - (1f - t) * (1f - t);
|
||||||
}
|
}
|
||||||
|
|
||||||
private float EaseInOutQuad(float t)
|
private float EaseInOutQuad(float t)
|
||||||
{
|
{
|
||||||
return t < 0.5f ? 2f * t * t : 1f - Mathf.Pow(-2f * t + 2f, 2f) / 2f;
|
return t < 0.5f ? 2f * t * t : 1f - Mathf.Pow(-2f * t + 2f, 2f) / 2f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,56 +1,56 @@
|
|||||||
using Cthangover.Core.Cards.StatusEffect;
|
using Cthangover.Core.Cards.StatusEffect;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace Cthangover.CardBattle.Actions
|
namespace Cthangover.CardBattle.Actions
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executor that applies physical damage from an attack card to a target.
|
/// Executor that applies physical damage from an attack card to a target.
|
||||||
/// Calculates damage as <c>card_attack_multiplier × user_attack_stat</c>, reduces it by
|
/// Calculates damage as <c>card_attack_multiplier × user_attack_stat</c>, reduces it by
|
||||||
/// the target's current defence value (which is consumed in the process), and applies the
|
/// the target's current defence value (which is consumed in the process), and applies the
|
||||||
/// remainder to the target's health. Invokes <c>OnTakeDamage</c>/<c>OnDealDamage</c> hooks
|
/// remainder to the target's health. Invokes <c>OnTakeDamage</c>/<c>OnDealDamage</c> hooks
|
||||||
/// on both characters' <see cref="StatusEffectQueue"/> so that damage-modifying status effects
|
/// on both characters' <see cref="StatusEffectQueue"/> so that damage-modifying status effects
|
||||||
/// can intercept and adjust the values before they are applied.
|
/// can intercept and adjust the values before they are applied.
|
||||||
/// Registered in <see cref="CardBattleActionProvider"/> under <c>"physics/attack"</c>.
|
/// Registered in <see cref="CardBattleActionProvider"/> under <c>"physics/attack"</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PhysicsDamageActionCard : ActionBase
|
public class PhysicsDamageActionCard : ActionBase
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique identifier registered in <see cref="CardBattleActionProvider"/> as <c>"physics/attack"</c>.
|
/// Unique identifier registered in <see cref="CardBattleActionProvider"/> as <c>"physics/attack"</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override string ID => "PhysicsDamageActionCard";
|
public override string ID => "PhysicsDamageActionCard";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolves the attack: deducts action points from <paramref name="user"/> via <c>CheckRequiredAndUsePoint</c>,
|
/// Resolves the attack: deducts action points from <paramref name="user"/> via <c>CheckRequiredAndUsePoint</c>,
|
||||||
/// computes raw damage from the card's attack multiplier and the user's attack stat,
|
/// computes raw damage from the card's attack multiplier and the user's attack stat,
|
||||||
/// applies status-effect damage hooks, consumes <paramref name="target"/>'s defence shield first,
|
/// applies status-effect damage hooks, consumes <paramref name="target"/>'s defence shield first,
|
||||||
/// then subtracts remaining damage from health. Returns a <see cref="ChangedAttributes"/> with
|
/// then subtracts remaining damage from health. Returns a <see cref="ChangedAttributes"/> with
|
||||||
/// the actual damage and defence-loss deltas for the floating text display.
|
/// the actual damage and defence-loss deltas for the floating text display.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override ChangedAttributes Execute(ActionCharacter actionCard, Character user, Character target)
|
public override ChangedAttributes Execute(ActionCharacter actionCard, Character user, Character target)
|
||||||
{
|
{
|
||||||
if (target == null || user == null || !CheckRequiredAndUsePoint(actionCard, user))
|
if (target == null || user == null || !CheckRequiredAndUsePoint(actionCard, user))
|
||||||
return new ChangedAttributes { Result = false };
|
return new ChangedAttributes { Result = false };
|
||||||
|
|
||||||
var damageDelta = Mathf.RoundToInt(actionCard.GetFloat("Attack", 1f) * user.Attributes.Attack.Value);
|
var damageDelta = Mathf.RoundToInt(actionCard.GetFloat("Attack", 1f) * user.Attributes.Attack.Value);
|
||||||
StatusEffectService.Of(target).OnTakeDamage(user, ref damageDelta);
|
StatusEffectService.Of(target).OnTakeDamage(user, ref damageDelta);
|
||||||
StatusEffectService.Of(user).OnDealDamage(target, ref damageDelta);
|
StatusEffectService.Of(user).OnDealDamage(target, ref damageDelta);
|
||||||
|
|
||||||
var defenceDelta = 0;
|
var defenceDelta = 0;
|
||||||
var ignoreArmor = user.HasPassiveEffect("IgnoreArmor");
|
var ignoreArmor = user.HasPassiveEffect("IgnoreArmor");
|
||||||
if (!ignoreArmor && target.Attributes.Defence.Value > 0)
|
if (!ignoreArmor && target.Attributes.Defence.Value > 0)
|
||||||
{
|
{
|
||||||
defenceDelta = target.Attributes.Defence.Value >= damageDelta ? damageDelta : target.Attributes.Defence.Value;
|
defenceDelta = target.Attributes.Defence.Value >= damageDelta ? damageDelta : target.Attributes.Defence.Value;
|
||||||
damageDelta -= defenceDelta;
|
damageDelta -= defenceDelta;
|
||||||
}
|
}
|
||||||
target.Attributes.Defence.Value -= defenceDelta;
|
target.Attributes.Defence.Value -= defenceDelta;
|
||||||
target.Attributes.Health.Value -= damageDelta;
|
target.Attributes.Health.Value -= damageDelta;
|
||||||
|
|
||||||
return new ChangedAttributes { Result = true, Target = { Damage = damageDelta, Defence = -defenceDelta}};
|
return new ChangedAttributes { Result = true, Target = { Damage = damageDelta, Defence = -defenceDelta}};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+231
-231
@@ -1,231 +1,231 @@
|
|||||||
using Cthangover.Core.Battle;
|
using Cthangover.Core.Battle;
|
||||||
using Cthangover.Core.Battle.Actions;
|
using Cthangover.Core.Battle.Actions;
|
||||||
using Cthangover.Core.UI.Theming;
|
using Cthangover.Core.UI.Theming;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
using Cthangover.TurnBattle;
|
using Cthangover.TurnBattle;
|
||||||
using Cthangover.CardBattle.UI;
|
using Cthangover.CardBattle.UI;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace Cthangover.CardBattle.Actions
|
namespace Cthangover.CardBattle.Actions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Animated battle action that plays a self-buff or ally-support animation: the source card
|
/// Animated battle action that plays a self-buff or ally-support animation: the source card
|
||||||
/// briefly rotates toward the target, applies a defence boost via <see cref="ActionExecutorHub"/>,
|
/// briefly rotates toward the target, applies a defence boost via <see cref="ActionExecutorHub"/>,
|
||||||
/// and shows a subtle block/stagger reaction on the target. Used for both <c>ToSelf</c> and
|
/// and shows a subtle block/stagger reaction on the target. Used for both <c>ToSelf</c> and
|
||||||
/// <c>ToAlias</c> action types. This is the visual counterpart of <see cref="PhysicsDefenceActionCard"/>.
|
/// <c>ToAlias</c> action types. This is the visual counterpart of <see cref="PhysicsDefenceActionCard"/>.
|
||||||
/// Created by <see cref="CardBattleCore.CreateAnimatedAction"/> for enemy self-buff/support turns.
|
/// Created by <see cref="CardBattleCore.CreateAnimatedAction"/> for enemy self-buff/support turns.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PhysicsDefenceAction : AbstractBattleAction
|
public class PhysicsDefenceAction : AbstractBattleAction
|
||||||
{
|
{
|
||||||
private ActionPhase phase = ActionPhase.Prepare;
|
private ActionPhase phase = ActionPhase.Prepare;
|
||||||
private float defendRotation;
|
private float defendRotation;
|
||||||
private bool effectApplied;
|
private bool effectApplied;
|
||||||
|
|
||||||
private Vector2 targetOriginalScale;
|
private Vector2 targetOriginalScale;
|
||||||
private Color targetOriginalColor;
|
private Color targetOriginalColor;
|
||||||
|
|
||||||
private Vector2 blockScale;
|
private Vector2 blockScale;
|
||||||
private Vector2 blockKnockback;
|
private Vector2 blockKnockback;
|
||||||
private Color blockColor;
|
private Color blockColor;
|
||||||
|
|
||||||
private float prepareDuration = 0.3f;
|
private float prepareDuration = 0.3f;
|
||||||
private float attackImpactDuration = 0.4f;
|
private float attackImpactDuration = 0.4f;
|
||||||
private float targetReactionDuration = 0.3f;
|
private float targetReactionDuration = 0.3f;
|
||||||
private float moveForBackDuration = 0.4f;
|
private float moveForBackDuration = 0.4f;
|
||||||
private float recoverDuration = 0.3f;
|
private float recoverDuration = 0.3f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a defence/support animation. Both <paramref name="source"/> and <paramref name="target"/>
|
/// Creates a defence/support animation. Both <paramref name="source"/> and <paramref name="target"/>
|
||||||
/// can be the same card (for self-buffs) or different (for ally support).
|
/// can be the same card (for self-buffs) or different (for ally support).
|
||||||
/// <paramref name="speed"/> multiplies all phase durations.
|
/// <paramref name="speed"/> multiplies all phase durations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PhysicsDefenceAction(CharacterCardNode source, CharacterCardNode target, ActionCharacter action, float speed = 1)
|
public PhysicsDefenceAction(CharacterCardNode source, CharacterCardNode target, ActionCharacter action, float speed = 1)
|
||||||
: base(source, target, action, speed) { }
|
: base(source, target, action, speed) { }
|
||||||
|
|
||||||
/// <summary>Resets the phase clock and captures the target's original scale and colour.</summary>
|
/// <summary>Resets the phase clock and captures the target's original scale and colour.</summary>
|
||||||
protected override void DoInternalStart()
|
protected override void DoInternalStart()
|
||||||
{
|
{
|
||||||
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
||||||
effectApplied = false;
|
effectApplied = false;
|
||||||
phase = ActionPhase.Prepare;
|
phase = ActionPhase.Prepare;
|
||||||
targetOriginalScale = TargetControl.Scale;
|
targetOriginalScale = TargetControl.Scale;
|
||||||
targetOriginalColor = TargetControl.Modulate;
|
targetOriginalColor = TargetControl.Modulate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float GetPhaseProgress()
|
private float GetPhaseProgress()
|
||||||
{
|
{
|
||||||
float elapsed = (float)(Godot.Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Godot.Engine.TimeScale;
|
float elapsed = (float)(Godot.Time.GetTicksUsec() / 1_000_000.0 - Timestamp) * Speed * (float)Godot.Engine.TimeScale;
|
||||||
float duration = GetCurrentPhaseDuration();
|
float duration = GetCurrentPhaseDuration();
|
||||||
return Mathf.Clamp(elapsed / duration, 0f, 1f);
|
return Mathf.Clamp(elapsed / duration, 0f, 1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private float GetCurrentPhaseDuration()
|
private float GetCurrentPhaseDuration()
|
||||||
{
|
{
|
||||||
switch (phase)
|
switch (phase)
|
||||||
{
|
{
|
||||||
case ActionPhase.Prepare: return prepareDuration;
|
case ActionPhase.Prepare: return prepareDuration;
|
||||||
case ActionPhase.AttackImpact: return attackImpactDuration;
|
case ActionPhase.AttackImpact: return attackImpactDuration;
|
||||||
case ActionPhase.TargetReaction: return targetReactionDuration;
|
case ActionPhase.TargetReaction: return targetReactionDuration;
|
||||||
case ActionPhase.MoveForBack: return moveForBackDuration;
|
case ActionPhase.MoveForBack: return moveForBackDuration;
|
||||||
case ActionPhase.Recover: return recoverDuration;
|
case ActionPhase.Recover: return recoverDuration;
|
||||||
default: return 1f;
|
default: return 1f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetTimestamp()
|
private void ResetTimestamp()
|
||||||
{
|
{
|
||||||
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
Timestamp = Godot.Time.GetTicksUsec() / 1_000_000.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Advances the guard animation: approach, impact, defence stat application, return and recover.</summary>
|
/// <summary>Advances the guard animation: approach, impact, defence stat application, return and recover.</summary>
|
||||||
protected override bool DoInternalAction()
|
protected override bool DoInternalAction()
|
||||||
{
|
{
|
||||||
float progress = GetPhaseProgress();
|
float progress = GetPhaseProgress();
|
||||||
|
|
||||||
switch (phase)
|
switch (phase)
|
||||||
{
|
{
|
||||||
case ActionPhase.Prepare:
|
case ActionPhase.Prepare:
|
||||||
{
|
{
|
||||||
float prepareProgress = EaseOutQuad(Mathf.Min(progress, 1f));
|
float prepareProgress = EaseOutQuad(Mathf.Min(progress, 1f));
|
||||||
SourceControl.Rotation = Mathf.Lerp(SourceRot, TargetRot, prepareProgress);
|
SourceControl.Rotation = Mathf.Lerp(SourceRot, TargetRot, prepareProgress);
|
||||||
|
|
||||||
if (progress >= 1f)
|
if (progress >= 1f)
|
||||||
{
|
{
|
||||||
phase = ActionPhase.AttackImpact;
|
phase = ActionPhase.AttackImpact;
|
||||||
ResetTimestamp();
|
ResetTimestamp();
|
||||||
defendRotation = (float)GD.RandRange(-15f, 15f) * Mathf.Pi / 180f;
|
defendRotation = (float)GD.RandRange(-15f, 15f) * Mathf.Pi / 180f;
|
||||||
|
|
||||||
targetOriginalScale = TargetControl.Scale;
|
targetOriginalScale = TargetControl.Scale;
|
||||||
targetOriginalColor = TargetControl.Modulate;
|
targetOriginalColor = TargetControl.Modulate;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionPhase.AttackImpact:
|
case ActionPhase.AttackImpact:
|
||||||
{
|
{
|
||||||
float impactProgress = EaseInOutQuad(Mathf.Min(progress * 2f, 1f));
|
float impactProgress = EaseInOutQuad(Mathf.Min(progress * 2f, 1f));
|
||||||
SourceControl.Rotation = Mathf.Lerp(TargetRot, defendRotation, impactProgress);
|
SourceControl.Rotation = Mathf.Lerp(TargetRot, defendRotation, impactProgress);
|
||||||
|
|
||||||
blockScale = targetOriginalScale * (1f + impactProgress * 0.1f);
|
blockScale = targetOriginalScale * (1f + impactProgress * 0.1f);
|
||||||
TargetControl.Scale = blockScale;
|
TargetControl.Scale = blockScale;
|
||||||
|
|
||||||
blockColor = targetOriginalColor;
|
blockColor = targetOriginalColor;
|
||||||
blockColor.G = Mathf.Min(1f, blockColor.G + impactProgress * 0.3f);
|
blockColor.G = Mathf.Min(1f, blockColor.G + impactProgress * 0.3f);
|
||||||
blockColor.B = Mathf.Min(1f, blockColor.B + impactProgress * 0.2f);
|
blockColor.B = Mathf.Min(1f, blockColor.B + impactProgress * 0.2f);
|
||||||
TargetControl.Modulate = blockColor;
|
TargetControl.Modulate = blockColor;
|
||||||
|
|
||||||
if (!effectApplied && progress >= 0.4f)
|
if (!effectApplied && progress >= 0.4f)
|
||||||
{
|
{
|
||||||
effectApplied = true;
|
effectApplied = true;
|
||||||
var result = ActionExecutorHub.Instance.Execute(Action, Source.Card, Target.Card);
|
var result = ActionExecutorHub.Instance.Execute(Action, Source.Card, Target.Card);
|
||||||
Source.UpdateInfo();
|
Source.UpdateInfo();
|
||||||
Target.UpdateInfo();
|
Target.UpdateInfo();
|
||||||
|
|
||||||
if (result.Result && result.Target.Defence > 0)
|
if (result.Result && result.Target.Defence > 0)
|
||||||
{
|
{
|
||||||
ShowDamageBehaviour.SpawnDefence(result.Target.Defence, TargetControl, TargetControl.GlobalPosition);
|
ShowDamageBehaviour.SpawnDefence(result.Target.Defence, TargetControl, TargetControl.GlobalPosition);
|
||||||
Target.Flash(UiTokens.Info);
|
Target.Flash(UiTokens.Info);
|
||||||
BattleLogHub.Log(
|
BattleLogHub.Log(
|
||||||
string.Format(Translate("battle/log_shield_gain"),
|
string.Format(Translate("battle/log_shield_gain"),
|
||||||
TranslationServer.Translate(Target.Card.Name).ToString(),
|
TranslationServer.Translate(Target.Card.Name).ToString(),
|
||||||
result.Target.Defence),
|
result.Target.Defence),
|
||||||
UI.BattleLogKind.Buff);
|
UI.BattleLogKind.Buff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progress >= 1f)
|
if (progress >= 1f)
|
||||||
{
|
{
|
||||||
phase = ActionPhase.TargetReaction;
|
phase = ActionPhase.TargetReaction;
|
||||||
ResetTimestamp();
|
ResetTimestamp();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionPhase.TargetReaction:
|
case ActionPhase.TargetReaction:
|
||||||
{
|
{
|
||||||
float reactionProgress = EaseInOutQuad(Mathf.Min(progress * 1.5f, 1f));
|
float reactionProgress = EaseInOutQuad(Mathf.Min(progress * 1.5f, 1f));
|
||||||
|
|
||||||
blockKnockback = TargetPos + (TargetPos - SourcePos).Normalized() * 0.1f;
|
blockKnockback = TargetPos + (TargetPos - SourcePos).Normalized() * 0.1f;
|
||||||
TargetControl.GlobalPosition = TargetPos.Lerp(blockKnockback, reactionProgress * 0.5f);
|
TargetControl.GlobalPosition = TargetPos.Lerp(blockKnockback, reactionProgress * 0.5f);
|
||||||
|
|
||||||
TargetControl.Scale = blockScale.Lerp(targetOriginalScale, reactionProgress);
|
TargetControl.Scale = blockScale.Lerp(targetOriginalScale, reactionProgress);
|
||||||
|
|
||||||
if (progress >= 1f)
|
if (progress >= 1f)
|
||||||
{
|
{
|
||||||
phase = ActionPhase.MoveForBack;
|
phase = ActionPhase.MoveForBack;
|
||||||
ResetTimestamp();
|
ResetTimestamp();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionPhase.MoveForBack:
|
case ActionPhase.MoveForBack:
|
||||||
{
|
{
|
||||||
float backProgress = EaseOutQuad(Mathf.Min(progress, 1f));
|
float backProgress = EaseOutQuad(Mathf.Min(progress, 1f));
|
||||||
SourceControl.GlobalPosition = TargetPos.Lerp(SourcePos, backProgress);
|
SourceControl.GlobalPosition = TargetPos.Lerp(SourcePos, backProgress);
|
||||||
SourceControl.Rotation = Mathf.Lerp(defendRotation, SourceRot, backProgress);
|
SourceControl.Rotation = Mathf.Lerp(defendRotation, SourceRot, backProgress);
|
||||||
|
|
||||||
TargetControl.GlobalPosition = TargetControl.GlobalPosition.Lerp(TargetPos, backProgress);
|
TargetControl.GlobalPosition = TargetControl.GlobalPosition.Lerp(TargetPos, backProgress);
|
||||||
TargetControl.Scale = TargetControl.Scale.Lerp(targetOriginalScale, backProgress);
|
TargetControl.Scale = TargetControl.Scale.Lerp(targetOriginalScale, backProgress);
|
||||||
|
|
||||||
Color currentColor = TargetControl.Modulate;
|
Color currentColor = TargetControl.Modulate;
|
||||||
TargetControl.Modulate = currentColor.Lerp(targetOriginalColor, backProgress);
|
TargetControl.Modulate = currentColor.Lerp(targetOriginalColor, backProgress);
|
||||||
|
|
||||||
if (progress >= 1f)
|
if (progress >= 1f)
|
||||||
{
|
{
|
||||||
phase = ActionPhase.Recover;
|
phase = ActionPhase.Recover;
|
||||||
ResetTimestamp();
|
ResetTimestamp();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionPhase.Recover:
|
case ActionPhase.Recover:
|
||||||
{
|
{
|
||||||
float recoverProgress = EaseOutQuad(Mathf.Min(progress, 1f));
|
float recoverProgress = EaseOutQuad(Mathf.Min(progress, 1f));
|
||||||
|
|
||||||
SourceControl.GlobalPosition = SourceControl.GlobalPosition.Lerp(SourcePos, recoverProgress);
|
SourceControl.GlobalPosition = SourceControl.GlobalPosition.Lerp(SourcePos, recoverProgress);
|
||||||
SourceControl.Rotation = Mathf.Lerp(SourceControl.Rotation, SourceRot, recoverProgress);
|
SourceControl.Rotation = Mathf.Lerp(SourceControl.Rotation, SourceRot, recoverProgress);
|
||||||
TargetControl.Rotation = Mathf.Lerp(TargetControl.Rotation, TargetRot, recoverProgress);
|
TargetControl.Rotation = Mathf.Lerp(TargetControl.Rotation, TargetRot, recoverProgress);
|
||||||
|
|
||||||
if (progress >= 1f)
|
if (progress >= 1f)
|
||||||
return true;
|
return true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Restores the target's captured visual state after the defence animation.</summary>
|
/// <summary>Restores the target's captured visual state after the defence animation.</summary>
|
||||||
protected override void DoInternalEnd()
|
protected override void DoInternalEnd()
|
||||||
{
|
{
|
||||||
if (SourceControl != null)
|
if (SourceControl != null)
|
||||||
{
|
{
|
||||||
SourceControl.GlobalPosition = SourcePos;
|
SourceControl.GlobalPosition = SourcePos;
|
||||||
SourceControl.Rotation = SourceRot;
|
SourceControl.Rotation = SourceRot;
|
||||||
}
|
}
|
||||||
if (TargetControl != null)
|
if (TargetControl != null)
|
||||||
{
|
{
|
||||||
TargetControl.GlobalPosition = TargetPos;
|
TargetControl.GlobalPosition = TargetPos;
|
||||||
TargetControl.Rotation = TargetRot;
|
TargetControl.Rotation = TargetRot;
|
||||||
TargetControl.Scale = targetOriginalScale;
|
TargetControl.Scale = targetOriginalScale;
|
||||||
TargetControl.Modulate = targetOriginalColor;
|
TargetControl.Modulate = targetOriginalColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private float EaseOutQuad(float t)
|
private float EaseOutQuad(float t)
|
||||||
{
|
{
|
||||||
return 1f - (1f - t) * (1f - t);
|
return 1f - (1f - t) * (1f - t);
|
||||||
}
|
}
|
||||||
|
|
||||||
private float EaseInOutQuad(float t)
|
private float EaseInOutQuad(float t)
|
||||||
{
|
{
|
||||||
return t < 0.5f ? 2f * t * t : 1f - Mathf.Pow(-2f * t + 2f, 2f) / 2f;
|
return t < 0.5f ? 2f * t * t : 1f - Mathf.Pow(-2f * t + 2f, 2f) / 2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Translate(string key)
|
private static string Translate(string key)
|
||||||
=> TranslationServer.Translate(key).ToString();
|
=> TranslationServer.Translate(key).ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,39 +1,39 @@
|
|||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
|
|
||||||
namespace Cthangover.CardBattle.Actions
|
namespace Cthangover.CardBattle.Actions
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executor that adds a fixed amount of defence to the target character.
|
/// Executor that adds a fixed amount of defence to the target character.
|
||||||
/// Defence acts as a damage-absorbing shield that is consumed before health when the
|
/// Defence acts as a damage-absorbing shield that is consumed before health when the
|
||||||
/// character takes damage (see <see cref="PhysicsDamageActionCard"/>). The defence delta
|
/// character takes damage (see <see cref="PhysicsDamageActionCard"/>). The defence delta
|
||||||
/// comes from the <c>"Defence"</c> property on the action card.
|
/// comes from the <c>"Defence"</c> property on the action card.
|
||||||
/// Registered in <see cref="CardBattleActionProvider"/> under <c>"physics/defence"</c>.
|
/// Registered in <see cref="CardBattleActionProvider"/> under <c>"physics/defence"</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PhysicsDefenceActionCard : ActionBase
|
public class PhysicsDefenceActionCard : ActionBase
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique identifier registered in <see cref="CardBattleActionProvider"/> as <c>"physics/defence"</c>.
|
/// Unique identifier registered in <see cref="CardBattleActionProvider"/> as <c>"physics/defence"</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override string ID => "PhysicsDefenceActionCard";
|
public override string ID => "PhysicsDefenceActionCard";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a flat defence value to <paramref name="target"/> after deducting action points
|
/// Adds a flat defence value to <paramref name="target"/> after deducting action points
|
||||||
/// from <paramref name="user"/>. The returned <see cref="ChangedAttributes"/> carries the
|
/// from <paramref name="user"/>. The returned <see cref="ChangedAttributes"/> carries the
|
||||||
/// defence delta for floating text display.
|
/// defence delta for floating text display.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override ChangedAttributes Execute(ActionCharacter actionCard, Character user, Character target)
|
public override ChangedAttributes Execute(ActionCharacter actionCard, Character user, Character target)
|
||||||
{
|
{
|
||||||
if (target == null || user == null || !CheckRequiredAndUsePoint(actionCard, user))
|
if (target == null || user == null || !CheckRequiredAndUsePoint(actionCard, user))
|
||||||
return new ChangedAttributes { Result = false };
|
return new ChangedAttributes { Result = false };
|
||||||
|
|
||||||
var defenceDelta = actionCard.GetInt("Defence");
|
var defenceDelta = actionCard.GetInt("Defence");
|
||||||
target.Attributes.Defence.Value += defenceDelta;
|
target.Attributes.Defence.Value += defenceDelta;
|
||||||
|
|
||||||
return new ChangedAttributes { Result = true, Target = { Defence = defenceDelta}};
|
return new ChangedAttributes { Result = true, Target = { Defence = defenceDelta}};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,40 +1,40 @@
|
|||||||
using Cthangover.Core.Cards.StatusEffect;
|
using Cthangover.Core.Cards.StatusEffect;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
|
|
||||||
namespace Cthangover.CardBattle.Actions
|
namespace Cthangover.CardBattle.Actions
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executor that applies a stun status effect to the target character.
|
/// Executor that applies a stun status effect to the target character.
|
||||||
/// The stun duration (number of turns) is read from the <c>"Turn"</c> property on the action card.
|
/// The stun duration (number of turns) is read from the <c>"Turn"</c> property on the action card.
|
||||||
/// During a stunned character's turn, <see cref="StatusEffectQueue.HasStun"/> returns <c>true</c>,
|
/// During a stunned character's turn, <see cref="StatusEffectQueue.HasStun"/> returns <c>true</c>,
|
||||||
/// causing <see cref="CardBattleCore.RunEnemyTurn"/> to skip that character's actions entirely.
|
/// causing <see cref="CardBattleCore.RunEnemyTurn"/> to skip that character's actions entirely.
|
||||||
/// Registered in <see cref="CardBattleActionProvider"/> under <c>"physics/stun"</c>.
|
/// Registered in <see cref="CardBattleActionProvider"/> under <c>"physics/stun"</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PhysicsStunActionCard : ActionBase
|
public class PhysicsStunActionCard : ActionBase
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique identifier registered in <see cref="CardBattleActionProvider"/> as <c>"physics/stun"</c>.
|
/// Unique identifier registered in <see cref="CardBattleActionProvider"/> as <c>"physics/stun"</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override string ID => "PhysicsStunActionCard";
|
public override string ID => "PhysicsStunActionCard";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a <see cref="StunStatusEffect"/> with the configured turn count to <paramref name="target"/>'s
|
/// Adds a <see cref="StunStatusEffect"/> with the configured turn count to <paramref name="target"/>'s
|
||||||
/// <see cref="StatusEffectQueue"/> after deducting action points from <paramref name="user"/>.
|
/// <see cref="StatusEffectQueue"/> after deducting action points from <paramref name="user"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override ChangedAttributes Execute(ActionCharacter actionCard, Character user, Character target)
|
public override ChangedAttributes Execute(ActionCharacter actionCard, Character user, Character target)
|
||||||
{
|
{
|
||||||
if (target == null || user == null || !CheckRequiredAndUsePoint(actionCard, user))
|
if (target == null || user == null || !CheckRequiredAndUsePoint(actionCard, user))
|
||||||
return new ChangedAttributes { Result = false };
|
return new ChangedAttributes { Result = false };
|
||||||
|
|
||||||
if (target.HasPassiveEffect("StunImmune"))
|
if (target.HasPassiveEffect("StunImmune"))
|
||||||
return new ChangedAttributes { Result = true };
|
return new ChangedAttributes { Result = true };
|
||||||
|
|
||||||
StatusEffectService.Of(target).Add("effect/physics/stun", actionCard.GetInt("Turn", 3));
|
StatusEffectService.Of(target).Add("effect/physics/stun", actionCard.GetInt("Turn", 3));
|
||||||
|
|
||||||
return new ChangedAttributes { Result = true };
|
return new ChangedAttributes { Result = true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,52 +1,52 @@
|
|||||||
using Cthangover.CardBattle.UI;
|
using Cthangover.CardBattle.UI;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
|
|
||||||
namespace Cthangover.CardBattle.Player
|
namespace Cthangover.CardBattle.Player
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Factory that returns the appropriate <see cref="ICardActionStrategy"/> implementation
|
/// Factory that returns the appropriate <see cref="ICardActionStrategy"/> implementation
|
||||||
/// for a given <see cref="ActionCharacter"/>. Dispatches based on <see cref="ActionCharacterType"/>:
|
/// for a given <see cref="ActionCharacter"/>. Dispatches based on <see cref="ActionCharacterType"/>:
|
||||||
/// <c>ToEnemy</c> → <see cref="AttackToEnemyStrategy"/>,
|
/// <c>ToEnemy</c> → <see cref="AttackToEnemyStrategy"/>,
|
||||||
/// <c>ToAlias</c> → <see cref="SupportStrategy"/>,
|
/// <c>ToAlias</c> → <see cref="SupportStrategy"/>,
|
||||||
/// <c>ToSelf</c> → <see cref="SelfStrategy"/>.
|
/// <c>ToSelf</c> → <see cref="SelfStrategy"/>.
|
||||||
/// Used by <see cref="CardController"/> during drag-and-drop to determine valid targeting behavior
|
/// Used by <see cref="CardController"/> during drag-and-drop to determine valid targeting behavior
|
||||||
/// and by <see cref="CanTarget"/> for pre-validation.
|
/// and by <see cref="CanTarget"/> for pre-validation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CardActionStrategyFactory
|
public static class CardActionStrategyFactory
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the matching strategy for the action's type, or <c>null</c> if any argument is null
|
/// Returns the matching strategy for the action's type, or <c>null</c> if any argument is null
|
||||||
/// or the type is unrecognized.
|
/// or the type is unrecognized.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ICardActionStrategy Get(ActionCharacter action, CharacterCardNode source, CharacterCardNode target)
|
public static ICardActionStrategy Get(ActionCharacter action, CharacterCardNode source, CharacterCardNode target)
|
||||||
{
|
{
|
||||||
if (action == null || source == null || target == null)
|
if (action == null || source == null || target == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
switch (action.Type)
|
switch (action.Type)
|
||||||
{
|
{
|
||||||
case ActionCharacterType.ToEnemy:
|
case ActionCharacterType.ToEnemy:
|
||||||
return new AttackToEnemyStrategy();
|
return new AttackToEnemyStrategy();
|
||||||
case ActionCharacterType.ToAlias:
|
case ActionCharacterType.ToAlias:
|
||||||
return new SupportStrategy();
|
return new SupportStrategy();
|
||||||
case ActionCharacterType.ToSelf:
|
case ActionCharacterType.ToSelf:
|
||||||
return new SelfStrategy();
|
return new SelfStrategy();
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convenience method that gets the strategy via <see cref="Get"/> and immediately calls
|
/// Convenience method that gets the strategy via <see cref="Get"/> and immediately calls
|
||||||
/// <see cref="ICardActionStrategy.Check"/> on it. Returns <c>false</c> if no strategy matches
|
/// <see cref="ICardActionStrategy.Check"/> on it. Returns <c>false</c> if no strategy matches
|
||||||
/// or any argument is null.
|
/// or any argument is null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool CanTarget(ActionCharacter action, CharacterCardNode source, CharacterCardNode target)
|
public static bool CanTarget(ActionCharacter action, CharacterCardNode source, CharacterCardNode target)
|
||||||
{
|
{
|
||||||
if (action == null || source == null || target == null)
|
if (action == null || source == null || target == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return Get(action, source, target)?.Check(action, source, target) ?? false;
|
return Get(action, source, target)?.Check(action, source, target) ?? false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,32 +1,32 @@
|
|||||||
using Cthangover.CardBattle.UI;
|
using Cthangover.CardBattle.UI;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
|
|
||||||
namespace Cthangover.CardBattle.Player
|
namespace Cthangover.CardBattle.Player
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Strategy interface that defines validation, visual highlighting, and execution for
|
/// Strategy interface that defines validation, visual highlighting, and execution for
|
||||||
/// a specific type of card action during the player's drag-and-drop targeting flow.
|
/// a specific type of card action during the player's drag-and-drop targeting flow.
|
||||||
/// The <see cref="CardActionStrategyFactory"/> dispatches to the correct strategy based on
|
/// The <see cref="CardActionStrategyFactory"/> dispatches to the correct strategy based on
|
||||||
/// the action's <see cref="ActionCharacterType"/>. <see cref="CardController"/> calls
|
/// the action's <see cref="ActionCharacterType"/>. <see cref="CardController"/> calls
|
||||||
/// <see cref="Check"/> during drag to validate targets, <see cref="HighlightTarget"/> to show
|
/// <see cref="Check"/> during drag to validate targets, <see cref="HighlightTarget"/> to show
|
||||||
/// valid/invalid visual feedback, and <see cref="Execute"/> when the card is dropped on a valid target.
|
/// valid/invalid visual feedback, and <see cref="Execute"/> when the card is dropped on a valid target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ICardActionStrategy
|
public interface ICardActionStrategy
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validates whether <paramref name="action"/> can be performed by <paramref name="source"/>
|
/// Validates whether <paramref name="action"/> can be performed by <paramref name="source"/>
|
||||||
/// on <paramref name="target"/>. Called each frame during drag to determine visual feedback.
|
/// on <paramref name="target"/>. Called each frame during drag to determine visual feedback.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool Check(ActionCharacter action, CharacterCardNode source, CharacterCardNode target);
|
bool Check(ActionCharacter action, CharacterCardNode source, CharacterCardNode target);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies visual highlighting to both the <paramref name="actionCard"/> being dragged
|
/// Applies visual highlighting to both the <paramref name="actionCard"/> being dragged
|
||||||
/// and the <paramref name="target"/> character card (e.g. red outline for attack, green for support).
|
/// and the <paramref name="target"/> character card (e.g. red outline for attack, green for support).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void HighlightTarget(ActionCardNode actionCard, CharacterCardNode target);
|
void HighlightTarget(ActionCardNode actionCard, CharacterCardNode target);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes the action via <see cref="ActionExecutorHub"/> and spawns floating damage/defence text.
|
/// Executes the action via <see cref="ActionExecutorHub"/> and spawns floating damage/defence text.
|
||||||
/// Called on successful card drop by <see cref="CardController.OnEndDrag"/>.
|
/// Called on successful card drop by <see cref="CardController.OnEndDrag"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Execute(CharacterCardNode source, CharacterCardNode target, ActionCharacter action);
|
void Execute(CharacterCardNode source, CharacterCardNode target, ActionCharacter action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,49 +1,49 @@
|
|||||||
using Cthangover.CardBattle.UI;
|
using Cthangover.CardBattle.UI;
|
||||||
using Cthangover.Core.Battle.Actions;
|
using Cthangover.Core.Battle.Actions;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
|
|
||||||
namespace Cthangover.CardBattle.Player
|
namespace Cthangover.CardBattle.Player
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Strategy for stun actions targeting enemies. Extends the standard <c>ToEnemy</c> check
|
/// Strategy for stun actions targeting enemies. Extends the standard <c>ToEnemy</c> check
|
||||||
/// by also verifying that the action has at least 2 turns of stun duration
|
/// by also verifying that the action has at least 2 turns of stun duration
|
||||||
/// (<c>"Turn" > 1</c>). Highlights with red attack overlays and applies
|
/// (<c>"Turn" > 1</c>). Highlights with red attack overlays and applies
|
||||||
/// the stun effect via <see cref="ActionExecutorHub"/> without spawning damage text
|
/// the stun effect via <see cref="ActionExecutorHub"/> without spawning damage text
|
||||||
/// (stun has no numerical effect to display).
|
/// (stun has no numerical effect to display).
|
||||||
/// Not currently returned by <see cref="CardActionStrategyFactory"/> — available for
|
/// Not currently returned by <see cref="CardActionStrategyFactory"/> — available for
|
||||||
/// future use when stun cards need distinct drag-and-drop behavior from attack cards.
|
/// future use when stun cards need distinct drag-and-drop behavior from attack cards.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StunToEnemyStrategy : ICardActionStrategy
|
public class StunToEnemyStrategy : ICardActionStrategy
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns <c>true</c> when the action targets an enemy and has a turn count greater than 1.
|
/// Returns <c>true</c> when the action targets an enemy and has a turn count greater than 1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Check(ActionCharacter action, CharacterCardNode source, CharacterCardNode target)
|
public bool Check(ActionCharacter action, CharacterCardNode source, CharacterCardNode target)
|
||||||
{
|
{
|
||||||
return action.Type == ActionCharacterType.ToEnemy && target != null && !target.IsPlayer
|
return action.Type == ActionCharacterType.ToEnemy && target != null && !target.IsPlayer
|
||||||
&& action.GetInt("Turn", 0) > 1;
|
&& action.GetInt("Turn", 0) > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shows a red attack overlay on both cards, same as <see cref="AttackToEnemyStrategy"/>.
|
/// Shows a red attack overlay on both cards, same as <see cref="AttackToEnemyStrategy"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void HighlightTarget(ActionCardNode actionCard, CharacterCardNode target)
|
public void HighlightTarget(ActionCardNode actionCard, CharacterCardNode target)
|
||||||
{
|
{
|
||||||
actionCard.Attack();
|
actionCard.Attack();
|
||||||
target.Attack();
|
target.Attack();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies the stun via <see cref="ActionExecutorHub"/>. Does not spawn damage/defence text.
|
/// Applies the stun via <see cref="ActionExecutorHub"/>. Does not spawn damage/defence text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Execute(CharacterCardNode source, CharacterCardNode target, ActionCharacter action)
|
public void Execute(CharacterCardNode source, CharacterCardNode target, ActionCharacter action)
|
||||||
{
|
{
|
||||||
if (source?.Card == null || target?.Card == null || action == null)
|
if (source?.Card == null || target?.Card == null || action == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ActionExecutorHub.Instance.Execute(action, source.Card, target.Card);
|
ActionExecutorHub.Instance.Execute(action, source.Card, target.Card);
|
||||||
source.UpdateInfo();
|
source.UpdateInfo();
|
||||||
target.UpdateInfo();
|
target.UpdateInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
namespace Cthangover.CardBattle.StatusEffect
|
namespace Cthangover.CardBattle.StatusEffect
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface exposed by <see cref="StatusEffectQueue"/> to <see cref="IStatusEffect"/> instances
|
/// Interface exposed by <see cref="StatusEffectQueue"/> to <see cref="IStatusEffect"/> instances
|
||||||
/// so that status effects can communicate back to the queue during turn lifecycle callbacks.
|
/// so that status effects can communicate back to the queue during turn lifecycle callbacks.
|
||||||
/// <c>SkipTurn</c> allows an effect (e.g. stun) to mark the character's entire turn as skipped.
|
/// <c>SkipTurn</c> allows an effect (e.g. stun) to mark the character's entire turn as skipped.
|
||||||
/// <c>RemoveStatus</c> allows an effect to self-remove from the queue.
|
/// <c>RemoveStatus</c> allows an effect to self-remove from the queue.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IStatusActions
|
public interface IStatusActions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marks the character's current turn as skipped. Called by <see cref="StunStatusEffect.OnTurnStart"/>.
|
/// Marks the character's current turn as skipped. Called by <see cref="StunStatusEffect.OnTurnStart"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void SkipTurn();
|
void SkipTurn();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the specified status effect from the queue. Called by effects that want to
|
/// Removes the specified status effect from the queue. Called by effects that want to
|
||||||
/// self-terminate before their turn count expires.
|
/// self-terminate before their turn count expires.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void RemoveStatus(IStatusEffect status);
|
void RemoveStatus(IStatusEffect status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
|
|
||||||
namespace Cthangover.CardBattle.StatusEffect
|
namespace Cthangover.CardBattle.StatusEffect
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a status effect that is applied to a <see cref="Character"/> during battle
|
/// Defines a status effect that is applied to a <see cref="Character"/> during battle
|
||||||
/// and processed each turn by <see cref="StatusEffectQueue"/>. Effects receive callbacks
|
/// and processed each turn by <see cref="StatusEffectQueue"/>. Effects receive callbacks
|
||||||
/// at turn start and turn end, and self-report expiration via <see cref="IsExpired"/>
|
/// at turn start and turn end, and self-report expiration via <see cref="IsExpired"/>
|
||||||
/// when their <see cref="Turns"/> counter reaches zero. The <see cref="IStatusActions"/>
|
/// when their <see cref="Turns"/> counter reaches zero. The <see cref="IStatusActions"/>
|
||||||
/// callback parameter allows effects to influence the character's turn (e.g. skip it).
|
/// callback parameter allows effects to influence the character's turn (e.g. skip it).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IStatusEffect
|
public interface IStatusEffect
|
||||||
{
|
{
|
||||||
/// <summary>Integer identifier for the effect type (1 = stun). Used for serialization.</summary>
|
/// <summary>Integer identifier for the effect type (1 = stun). Used for serialization.</summary>
|
||||||
int Type { get; }
|
int Type { get; }
|
||||||
/// <summary>Remaining turn count before expiry. Decremented by <see cref="StatusEffectQueue"/>.</summary>
|
/// <summary>Remaining turn count before expiry. Decremented by <see cref="StatusEffectQueue"/>.</summary>
|
||||||
int Turns { get; set; }
|
int Turns { get; set; }
|
||||||
/// <summary>Returns <c>true</c> when <see cref="Turns"/> ≤ 0, signalling removal from the queue.</summary>
|
/// <summary>Returns <c>true</c> when <see cref="Turns"/> ≤ 0, signalling removal from the queue.</summary>
|
||||||
bool IsExpired { get; }
|
bool IsExpired { get; }
|
||||||
|
|
||||||
/// <summary>Called by <see cref="StatusEffectQueue.OnTurnStart"/> before the turn counter is decremented.</summary>
|
/// <summary>Called by <see cref="StatusEffectQueue.OnTurnStart"/> before the turn counter is decremented.</summary>
|
||||||
void OnTurnStart(Character character, IStatusActions actions);
|
void OnTurnStart(Character character, IStatusActions actions);
|
||||||
/// <summary>Called by <see cref="StatusEffectQueue.OnTurnEnd"/> after the character's actions have resolved.</summary>
|
/// <summary>Called by <see cref="StatusEffectQueue.OnTurnEnd"/> after the character's actions have resolved.</summary>
|
||||||
void OnTurnEnd(Character character, IStatusActions actions);
|
void OnTurnEnd(Character character, IStatusActions actions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,27 @@
|
|||||||
namespace Cthangover.CardBattle.StatusEffect
|
namespace Cthangover.CardBattle.StatusEffect
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Data-only container for a status effect's serializable fields (<see cref="Type"/> and <see cref="Turns"/>).
|
/// Data-only container for a status effect's serializable fields (<see cref="Type"/> and <see cref="Turns"/>).
|
||||||
/// Used in <see cref="StatusEffectQueue.Copy"/> to deep-clone status effect state when a character
|
/// Used in <see cref="StatusEffectQueue.Copy"/> to deep-clone status effect state when a character
|
||||||
/// is copied for battle. Cast to <see cref="IStatusEffect"/> when added back to a queue.
|
/// is copied for battle. Cast to <see cref="IStatusEffect"/> when added back to a queue.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StatusEffectItem
|
public class StatusEffectItem
|
||||||
{
|
{
|
||||||
/// <summary>Effect type identifier (matches <see cref="IStatusEffect.Type"/>).</summary>
|
/// <summary>Effect type identifier (matches <see cref="IStatusEffect.Type"/>).</summary>
|
||||||
public int Type { get; set; }
|
public int Type { get; set; }
|
||||||
/// <summary>Remaining turns before expiry.</summary>
|
/// <summary>Remaining turns before expiry.</summary>
|
||||||
public int Turns { get; set; }
|
public int Turns { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a field-by-field copy for deep-cloning during character duplication.
|
/// Creates a field-by-field copy for deep-cloning during character duplication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StatusEffectItem Copy()
|
public StatusEffectItem Copy()
|
||||||
{
|
{
|
||||||
return new StatusEffectItem
|
return new StatusEffectItem
|
||||||
{
|
{
|
||||||
Type = Type,
|
Type = Type,
|
||||||
Turns = Turns
|
Turns = Turns
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,103 +1,103 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
|
|
||||||
namespace Cthangover.CardBattle.StatusEffect
|
namespace Cthangover.CardBattle.StatusEffect
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages the collection of <see cref="IStatusEffect"/> instances attached to a single <see cref="Character"/>.
|
/// Manages the collection of <see cref="IStatusEffect"/> instances attached to a single <see cref="Character"/>.
|
||||||
/// Implements <see cref="IStatusActions"/> so that individual effects can call back into the queue
|
/// Implements <see cref="IStatusActions"/> so that individual effects can call back into the queue
|
||||||
/// (e.g. to skip the turn). During <see cref="OnTurnStart"/>, each effect's callback is invoked and
|
/// (e.g. to skip the turn). During <see cref="OnTurnStart"/>, each effect's callback is invoked and
|
||||||
/// its turn counter is decremented; expired effects are removed. <see cref="SkipTurn"/> is checked by
|
/// its turn counter is decremented; expired effects are removed. <see cref="SkipTurn"/> is checked by
|
||||||
/// <see cref="CardBattleCore.RunEnemyTurn"/> to skip stunned enemies.
|
/// <see cref="CardBattleCore.RunEnemyTurn"/> to skip stunned enemies.
|
||||||
/// Supports deep copying for character duplication via <see cref="Copy"/>.
|
/// Supports deep copying for character duplication via <see cref="Copy"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StatusEffectQueue : IStatusActions
|
public class StatusEffectQueue : IStatusActions
|
||||||
{
|
{
|
||||||
private readonly Character _character;
|
private readonly Character _character;
|
||||||
private readonly List<IStatusEffect> _effects = new();
|
private readonly List<IStatusEffect> _effects = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set to <c>true</c> when an effect calls <see cref="IStatusActions.SkipTurn"/> during <see cref="OnTurnStart"/>.
|
/// Set to <c>true</c> when an effect calls <see cref="IStatusActions.SkipTurn"/> during <see cref="OnTurnStart"/>.
|
||||||
/// Reset to <c>false</c> at the start of each turn before effects are processed.
|
/// Reset to <c>false</c> at the start of each turn before effects are processed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool SkipTurn { get; private set; }
|
public bool SkipTurn { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new queue bound to the given <paramref name="character"/>.
|
/// Creates a new queue bound to the given <paramref name="character"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StatusEffectQueue(Character character)
|
public StatusEffectQueue(Character character)
|
||||||
{
|
{
|
||||||
_character = character;
|
_character = character;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a deep copy of this queue bound to <paramref name="newCharacter"/>.
|
/// Creates a deep copy of this queue bound to <paramref name="newCharacter"/>.
|
||||||
/// <see cref="StatusEffectItem"/> effects are field-copied; other effect types are shared by reference.
|
/// <see cref="StatusEffectItem"/> effects are field-copied; other effect types are shared by reference.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StatusEffectQueue Copy(Character newCharacter)
|
public StatusEffectQueue Copy(Character newCharacter)
|
||||||
{
|
{
|
||||||
var clone = new StatusEffectQueue(newCharacter);
|
var clone = new StatusEffectQueue(newCharacter);
|
||||||
foreach (var effect in _effects)
|
foreach (var effect in _effects)
|
||||||
{
|
{
|
||||||
if (effect is StatusEffectItem item)
|
if (effect is StatusEffectItem item)
|
||||||
clone._effects.Add(item.Copy() as IStatusEffect);
|
clone._effects.Add(item.Copy() as IStatusEffect);
|
||||||
else
|
else
|
||||||
clone._effects.Add(effect);
|
clone._effects.Add(effect);
|
||||||
}
|
}
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a new status effect to the end of the processing list. Effects are processed
|
/// Adds a new status effect to the end of the processing list. Effects are processed
|
||||||
/// in reverse order during turn callbacks (last-added first).
|
/// in reverse order during turn callbacks (last-added first).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddEffect(IStatusEffect effect)
|
public void AddEffect(IStatusEffect effect)
|
||||||
{
|
{
|
||||||
_effects.Add(effect);
|
_effects.Add(effect);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes all effects at the start of the character's turn: resets <see cref="SkipTurn"/>,
|
/// Processes all effects at the start of the character's turn: resets <see cref="SkipTurn"/>,
|
||||||
/// calls <see cref="IStatusEffect.OnTurnStart"/> for each effect in reverse order,
|
/// calls <see cref="IStatusEffect.OnTurnStart"/> for each effect in reverse order,
|
||||||
/// decrements the turn counter, and removes expired effects.
|
/// decrements the turn counter, and removes expired effects.
|
||||||
/// Called by <see cref="CardBattleCore.StartPlayerTurn"/> and <see cref="CardBattleCore.RunEnemyTurn"/>.
|
/// Called by <see cref="CardBattleCore.StartPlayerTurn"/> and <see cref="CardBattleCore.RunEnemyTurn"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void OnTurnStart()
|
public void OnTurnStart()
|
||||||
{
|
{
|
||||||
SkipTurn = false;
|
SkipTurn = false;
|
||||||
for (int i = _effects.Count - 1; i >= 0; i--)
|
for (int i = _effects.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
var effect = _effects[i];
|
var effect = _effects[i];
|
||||||
effect.OnTurnStart(_character, this);
|
effect.OnTurnStart(_character, this);
|
||||||
effect.Turns--;
|
effect.Turns--;
|
||||||
if (effect.IsExpired)
|
if (effect.IsExpired)
|
||||||
_effects.RemoveAt(i);
|
_effects.RemoveAt(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes all effects at the end of the character's turn: calls
|
/// Processes all effects at the end of the character's turn: calls
|
||||||
/// <see cref="IStatusEffect.OnTurnEnd"/> in reverse order and removes expired effects.
|
/// <see cref="IStatusEffect.OnTurnEnd"/> in reverse order and removes expired effects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void OnTurnEnd()
|
public void OnTurnEnd()
|
||||||
{
|
{
|
||||||
for (int i = _effects.Count - 1; i >= 0; i--)
|
for (int i = _effects.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
var effect = _effects[i];
|
var effect = _effects[i];
|
||||||
effect.OnTurnEnd(_character, this);
|
effect.OnTurnEnd(_character, this);
|
||||||
if (effect.IsExpired)
|
if (effect.IsExpired)
|
||||||
_effects.RemoveAt(i);
|
_effects.RemoveAt(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IStatusActions.SkipTurn()
|
void IStatusActions.SkipTurn()
|
||||||
{
|
{
|
||||||
SkipTurn = true;
|
SkipTurn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IStatusActions.RemoveStatus(IStatusEffect status)
|
void IStatusActions.RemoveStatus(IStatusEffect status)
|
||||||
{
|
{
|
||||||
_effects.Remove(status);
|
_effects.Remove(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,52 +1,52 @@
|
|||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
|
|
||||||
namespace Cthangover.CardBattle.StatusEffect
|
namespace Cthangover.CardBattle.StatusEffect
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Status effect that causes a character to skip their entire turn.
|
/// Status effect that causes a character to skip their entire turn.
|
||||||
/// On <see cref="OnTurnStart"/>, calls <see cref="IStatusActions.SkipTurn"/> which sets
|
/// On <see cref="OnTurnStart"/>, calls <see cref="IStatusActions.SkipTurn"/> which sets
|
||||||
/// <see cref="StatusEffectQueue.SkipTurn"/> to <c>true</c>. <see cref="CardBattleCore.RunEnemyTurn"/>
|
/// <see cref="StatusEffectQueue.SkipTurn"/> to <c>true</c>. <see cref="CardBattleCore.RunEnemyTurn"/>
|
||||||
/// checks this flag and skips the character's action selection and resolution entirely.
|
/// checks this flag and skips the character's action selection and resolution entirely.
|
||||||
/// Created by <see cref="PhysicsStunActionCard"/> when a stun card is played,
|
/// Created by <see cref="PhysicsStunActionCard"/> when a stun card is played,
|
||||||
/// and added to the target's <see cref="StatusEffectQueue"/> via <c>Add("effect/physics/stun", turns)</c>.
|
/// and added to the target's <see cref="StatusEffectQueue"/> via <c>Add("effect/physics/stun", turns)</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StunStatusEffect : IStatusEffect
|
public class StunStatusEffect : IStatusEffect
|
||||||
{
|
{
|
||||||
/// <summary>Type identifier for stun (1).</summary>
|
/// <summary>Type identifier for stun (1).</summary>
|
||||||
public int Type => 1;
|
public int Type => 1;
|
||||||
/// <summary>Number of turns this stun remains active.</summary>
|
/// <summary>Number of turns this stun remains active.</summary>
|
||||||
public int Turns { get; set; }
|
public int Turns { get; set; }
|
||||||
/// <summary>Expired when the turn counter reaches zero or below.</summary>
|
/// <summary>Expired when the turn counter reaches zero or below.</summary>
|
||||||
public bool IsExpired => Turns <= 0;
|
public bool IsExpired => Turns <= 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a stun effect lasting for <paramref name="turns"/> turns.
|
/// Creates a stun effect lasting for <paramref name="turns"/> turns.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StunStatusEffect(int turns)
|
public StunStatusEffect(int turns)
|
||||||
{
|
{
|
||||||
Turns = turns;
|
Turns = turns;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parameterless constructor for deserialization. Sets <see cref="Turns"/> to 0 (expired immediately).
|
/// Parameterless constructor for deserialization. Sets <see cref="Turns"/> to 0 (expired immediately).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StunStatusEffect()
|
public StunStatusEffect()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calls <see cref="IStatusActions.SkipTurn"/> to mark the character's turn as skipped.
|
/// Calls <see cref="IStatusActions.SkipTurn"/> to mark the character's turn as skipped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void OnTurnStart(Character character, IStatusActions actions)
|
public void OnTurnStart(Character character, IStatusActions actions)
|
||||||
{
|
{
|
||||||
actions.SkipTurn();
|
actions.SkipTurn();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stun has no end-of-turn effect.
|
/// Stun has no end-of-turn effect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void OnTurnEnd(Character character, IStatusActions actions)
|
public void OnTurnEnd(Character character, IStatusActions actions)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+267
-267
@@ -1,269 +1,269 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Cthangover.Core.Characters;
|
using Cthangover.Core.Characters;
|
||||||
using Cthangover.Core.Scenes;
|
using Cthangover.Core.Scenes;
|
||||||
using Cthangover.Core.UI;
|
using Cthangover.Core.UI;
|
||||||
using Cthangover.Core.UI.Theming;
|
using Cthangover.Core.UI.Theming;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
using Cthangover.Mods.Interface.Base.Lists;
|
using Cthangover.Mods.Interface.Base.Lists;
|
||||||
using Cthangover.Mods.Interface.View;
|
using Cthangover.Mods.Interface.View;
|
||||||
namespace Cthangover.CardBattle.UI
|
namespace Cthangover.CardBattle.UI
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Container panel that manages a list of <see cref="CharacterCardNode"/> instances for one side
|
/// Container panel that manages a list of <see cref="CharacterCardNode"/> instances for one side
|
||||||
/// of the battle (player or enemy). Handles card creation, multi-row layout when enemy count exceeds
|
/// of the battle (player or enemy). Handles card creation, multi-row layout when enemy count exceeds
|
||||||
/// <c>MAX_CARDS_ROW</c> (10 per row), smooth animated repositioning, health-triggered death,
|
/// <c>MAX_CARDS_ROW</c> (10 per row), smooth animated repositioning, health-triggered death,
|
||||||
/// and the <see cref="CardDeathAnimation"/> dissolve sequence.
|
/// and the <see cref="CardDeathAnimation"/> dissolve sequence.
|
||||||
/// Two instances are created by <see cref="CardBattleCore.Init"/>: a left-aligned player panel
|
/// Two instances are created by <see cref="CardBattleCore.Init"/>: a left-aligned player panel
|
||||||
/// and a right-aligned enemy panel.
|
/// and a right-aligned enemy panel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class BattleCardPanel : ModWidget
|
public partial class BattleCardPanel : ModWidget
|
||||||
{
|
{
|
||||||
private const int MAX_CARDS_ROW = 10;
|
private const int MAX_CARDS_ROW = 10;
|
||||||
private const float BASE_CARD_HEIGHT = 280f;
|
private const float BASE_CARD_HEIGHT = 280f;
|
||||||
|
|
||||||
[Export] private Control content;
|
[Export] private Control content;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Controls card alignment within the panel. <c>"Left"</c> for the player side,
|
/// Controls card alignment within the panel. <c>"Left"</c> for the player side,
|
||||||
/// <c>"Right"</c> for the enemy side. Affects <see cref="CalcTargetPosition"/>.
|
/// <c>"Right"</c> for the enemy side. Affects <see cref="CalcTargetPosition"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Export] public string AlignType { get; set; } = "Left";
|
[Export] public string AlignType { get; set; } = "Left";
|
||||||
[Export] private float cellOffset = 10f;
|
[Export] private float cellOffset = 10f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All character cards currently managed by this panel. Populated by <see cref="Init"/>
|
/// All character cards currently managed by this panel. Populated by <see cref="Init"/>
|
||||||
/// and modified by <see cref="Dead"/> (removal) and <see cref="ClearAll"/> (full clear).
|
/// and modified by <see cref="Dead"/> (removal) and <see cref="ClearAll"/> (full clear).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<CharacterCardNode> CardList { get; } = new();
|
public List<CharacterCardNode> CardList { get; } = new();
|
||||||
private Dictionary<CharacterCardNode, Tween> activeTweens = new();
|
private Dictionary<CharacterCardNode, Tween> activeTweens = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When <c>true</c>, cards are laid out in multiple rows (up to 3) instead of a single row.
|
/// When <c>true</c>, cards are laid out in multiple rows (up to 3) instead of a single row.
|
||||||
/// Set by <see cref="CardBattleCore.Start"/> when enemy count exceeds 6.
|
/// Set by <see cref="CardBattleCore.Start"/> when enemy count exceeds 6.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseRows { get; set; }
|
public bool UseRows { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when a card's death animation completes. <see cref="CardBattleCore"/> subscribes
|
/// Fired when a card's death animation completes. <see cref="CardBattleCore"/> subscribes
|
||||||
/// to record the defeated character in <see cref="BattleSceneContext"/>.
|
/// to record the defeated character in <see cref="BattleSceneContext"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event System.Action<CharacterCardNode> OnCardDead;
|
public event System.Action<CharacterCardNode> OnCardDead;
|
||||||
|
|
||||||
/// <summary>Builds the card grid container rows for one battle side.</summary>
|
/// <summary>Builds the card grid container rows for one battle side.</summary>
|
||||||
protected override void Construct()
|
protected override void Construct()
|
||||||
{
|
{
|
||||||
CthangoverTheme.Apply(this);
|
CthangoverTheme.Apply(this);
|
||||||
|
|
||||||
content ??= GetNodeOrNull<Control>("Content");
|
content ??= GetNodeOrNull<Control>("Content");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears the current card list and creates new <see cref="CharacterCardNode"/> instances
|
/// Clears the current card list and creates new <see cref="CharacterCardNode"/> instances
|
||||||
/// for each <paramref name="cards"/> entry. Sets player/enemy team colors and subscribes
|
/// for each <paramref name="cards"/> entry. Sets player/enemy team colors and subscribes
|
||||||
/// the health-change handler. Returns the created card list.
|
/// the health-change handler. Returns the created card list.
|
||||||
/// Called by <see cref="CardBattleCore.Start"/> with the full character roster for each side.
|
/// Called by <see cref="CardBattleCore.Start"/> with the full character roster for each side.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<CharacterCardNode> Init(IEnumerable<Character> cards, bool isPlayer, float cardScale)
|
public List<CharacterCardNode> Init(IEnumerable<Character> cards, bool isPlayer, float cardScale)
|
||||||
{
|
{
|
||||||
CardList.Clear();
|
CardList.Clear();
|
||||||
activeTweens.Clear();
|
activeTweens.Clear();
|
||||||
|
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
foreach (var card in cards)
|
foreach (var card in cards)
|
||||||
{
|
{
|
||||||
CardList.Add(Create(card, isPlayer, cardScale));
|
CardList.Add(Create(card, isPlayer, cardScale));
|
||||||
idx++;
|
idx++;
|
||||||
}
|
}
|
||||||
Redraw(animate: false);
|
Redraw(animate: false);
|
||||||
|
|
||||||
return CardList;
|
return CardList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Instantiates a single <see cref="CharacterCardNode"/>, adds it to the content panel,
|
/// Instantiates a single <see cref="CharacterCardNode"/>, adds it to the content panel,
|
||||||
/// sets its scale, initializes it with card data, applies team colors, and hooks
|
/// sets its scale, initializes it with card data, applies team colors, and hooks
|
||||||
/// the health-change event. Called by <see cref="Init"/> for each character.
|
/// the health-change event. Called by <see cref="Init"/> for each character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CharacterCardNode Create(Character card, bool isPlayer, float cardScale)
|
public CharacterCardNode Create(Character card, bool isPlayer, float cardScale)
|
||||||
{
|
{
|
||||||
var behaviour = (CharacterCardNode)TscnScenes.LoadAndBuild("scenes/character_card.tscn");
|
var behaviour = (CharacterCardNode)TscnScenes.LoadAndBuild("scenes/character_card.tscn");
|
||||||
behaviour.EnsureConstructed();
|
behaviour.EnsureConstructed();
|
||||||
|
|
||||||
if (content != null)
|
if (content != null)
|
||||||
content.AddChild(behaviour);
|
content.AddChild(behaviour);
|
||||||
else
|
else
|
||||||
AddChild(behaviour);
|
AddChild(behaviour);
|
||||||
|
|
||||||
behaviour.Scale = new Vector2(cardScale, cardScale);
|
behaviour.Scale = new Vector2(cardScale, cardScale);
|
||||||
|
|
||||||
behaviour.Init(card);
|
behaviour.Init(card);
|
||||||
behaviour.IsPlayer = isPlayer;
|
behaviour.IsPlayer = isPlayer;
|
||||||
behaviour.SetTeamColors();
|
behaviour.SetTeamColors();
|
||||||
|
|
||||||
if (card != null)
|
if (card != null)
|
||||||
card.Attributes.Health.OnChange += CheckHealthAttribute;
|
card.Attributes.Health.OnChange += CheckHealthAttribute;
|
||||||
|
|
||||||
return behaviour;
|
return behaviour;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckHealthAttribute(float value, float basevalue)
|
private void CheckHealthAttribute(float value, float basevalue)
|
||||||
{
|
{
|
||||||
if (value > 0)
|
if (value > 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var cardItem in CardList.ToList())
|
foreach (var cardItem in CardList.ToList())
|
||||||
{
|
{
|
||||||
if (cardItem.Card.Attributes.Health.Value <= 0)
|
if (cardItem.Card.Attributes.Health.Value <= 0)
|
||||||
Dead(cardItem);
|
Dead(cardItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Frees all card nodes from the scene tree, clears the card list, and triggers a redraw.
|
/// Frees all card nodes from the scene tree, clears the card list, and triggers a redraw.
|
||||||
/// Called by <see cref="CardBattleCore.OnBattleCleared"/> when the battle scene is cleaned up.
|
/// Called by <see cref="CardBattleCore.OnBattleCleared"/> when the battle scene is cleaned up.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ClearAll()
|
public void ClearAll()
|
||||||
{
|
{
|
||||||
foreach (var card in CardList.ToList())
|
foreach (var card in CardList.ToList())
|
||||||
{
|
{
|
||||||
if (GodotObject.IsInstanceValid(card))
|
if (GodotObject.IsInstanceValid(card))
|
||||||
card.QueueFree();
|
card.QueueFree();
|
||||||
}
|
}
|
||||||
CardList.Clear();
|
CardList.Clear();
|
||||||
activeTweens.Clear();
|
activeTweens.Clear();
|
||||||
Redraw(animate: false);
|
Redraw(animate: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recalculates and applies positions for all cards in the list. When <paramref name="animate"/>
|
/// Recalculates and applies positions for all cards in the list. When <paramref name="animate"/>
|
||||||
/// is <c>true</c>, cards tween to their new positions with a cubic ease-out; otherwise they
|
/// is <c>true</c>, cards tween to their new positions with a cubic ease-out; otherwise they
|
||||||
/// snap instantly. Called after cards are added, removed, or when a selection/unselection
|
/// snap instantly. Called after cards are added, removed, or when a selection/unselection
|
||||||
/// changes the sort order.
|
/// changes the sort order.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Redraw(bool animate = true)
|
public void Redraw(bool animate = true)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < CardList.Count; i++)
|
for (int i = 0; i < CardList.Count; i++)
|
||||||
{
|
{
|
||||||
var card = CardList[i];
|
var card = CardList[i];
|
||||||
if (card == null)
|
if (card == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var (row, col) = GetRowCol(i);
|
var (row, col) = GetRowCol(i);
|
||||||
SetCardPosition(card, i, row, col, animate);
|
SetCardPosition(card, i, row, col, animate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private (int row, int col) GetRowCol(int index)
|
private (int row, int col) GetRowCol(int index)
|
||||||
{
|
{
|
||||||
if (!UseRows || index < MAX_CARDS_ROW)
|
if (!UseRows || index < MAX_CARDS_ROW)
|
||||||
return (0, index);
|
return (0, index);
|
||||||
|
|
||||||
if (index < MAX_CARDS_ROW * 2)
|
if (index < MAX_CARDS_ROW * 2)
|
||||||
return (1, index - MAX_CARDS_ROW);
|
return (1, index - MAX_CARDS_ROW);
|
||||||
|
|
||||||
return (2, index - MAX_CARDS_ROW * 2);
|
return (2, index - MAX_CARDS_ROW * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int CalcBaseZIndex(int row, int col)
|
private int CalcBaseZIndex(int row, int col)
|
||||||
{
|
{
|
||||||
return row * 20 + col;
|
return row * 20 + col;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 CalcTargetPosition(CharacterCardNode card, int row, int col)
|
private Vector2 CalcTargetPosition(CharacterCardNode card, int row, int col)
|
||||||
{
|
{
|
||||||
var panelSize = Size;
|
var panelSize = Size;
|
||||||
float effectiveWidth = card.Size.X * card.Scale.X;
|
float effectiveWidth = card.Size.X * card.Scale.X;
|
||||||
float effectiveHeight = card.Size.Y * card.Scale.Y;
|
float effectiveHeight = card.Size.Y * card.Scale.Y;
|
||||||
|
|
||||||
float y = row * (effectiveHeight + cellOffset);
|
float y = row * (effectiveHeight + cellOffset);
|
||||||
|
|
||||||
if (AlignType == "Left")
|
if (AlignType == "Left")
|
||||||
return new Vector2((cellOffset + effectiveWidth) * col, y);
|
return new Vector2((cellOffset + effectiveWidth) * col, y);
|
||||||
else
|
else
|
||||||
return new Vector2(panelSize.X - (cellOffset + effectiveWidth) * (col + 1), y);
|
return new Vector2(panelSize.X - (cellOffset + effectiveWidth) * (col + 1), y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates how many rows are needed to display <paramref name="enemyCount"/> enemies
|
/// Calculates how many rows are needed to display <paramref name="enemyCount"/> enemies
|
||||||
/// when <see cref="UseRows"/> is enabled. Returns 1, 2, or 3.
|
/// when <see cref="UseRows"/> is enabled. Returns 1, 2, or 3.
|
||||||
/// Called by <see cref="CardBattleCore.CalculateCardScale"/> to determine maximum card scale.
|
/// Called by <see cref="CardBattleCore.CalculateCardScale"/> to determine maximum card scale.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int GetCurrentRowCount(int enemyCount)
|
public int GetCurrentRowCount(int enemyCount)
|
||||||
{
|
{
|
||||||
if (!UseRows)
|
if (!UseRows)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
if (enemyCount <= MAX_CARDS_ROW)
|
if (enemyCount <= MAX_CARDS_ROW)
|
||||||
return 1;
|
return 1;
|
||||||
if (enemyCount <= MAX_CARDS_ROW * 2)
|
if (enemyCount <= MAX_CARDS_ROW * 2)
|
||||||
return 2;
|
return 2;
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the maximum cards per row constant (<c>MAX_CARDS_ROW = 10</c>).
|
/// Returns the maximum cards per row constant (<c>MAX_CARDS_ROW = 10</c>).
|
||||||
/// Used by <see cref="CardBattleCore.CalculateCardScale"/> for scale calculations.
|
/// Used by <see cref="CardBattleCore.CalculateCardScale"/> for scale calculations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int GetMaxCardsPerRow()
|
public int GetMaxCardsPerRow()
|
||||||
{
|
{
|
||||||
return MAX_CARDS_ROW;
|
return MAX_CARDS_ROW;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetCardPosition(CharacterCardNode card, int index, int row, int col, bool animate)
|
private void SetCardPosition(CharacterCardNode card, int index, int row, int col, bool animate)
|
||||||
{
|
{
|
||||||
if (card == null)
|
if (card == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var targetPos = CalcTargetPosition(card, row, col);
|
var targetPos = CalcTargetPosition(card, row, col);
|
||||||
|
|
||||||
if (activeTweens.TryGetValue(card, out var prevTween) && prevTween != null && prevTween.IsValid())
|
if (activeTweens.TryGetValue(card, out var prevTween) && prevTween != null && prevTween.IsValid())
|
||||||
prevTween.Kill();
|
prevTween.Kill();
|
||||||
|
|
||||||
if (animate)
|
if (animate)
|
||||||
{
|
{
|
||||||
var tween = CreateTween();
|
var tween = CreateTween();
|
||||||
tween.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
tween.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
||||||
tween.TweenProperty(card, "position", targetPos, 0.3f);
|
tween.TweenProperty(card, "position", targetPos, 0.3f);
|
||||||
activeTweens[card] = tween;
|
activeTweens[card] = tween;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
card.Position = targetPos;
|
card.Position = targetPos;
|
||||||
activeTweens.Remove(card);
|
activeTweens.Remove(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseZ = CalcBaseZIndex(row, col);
|
var baseZ = CalcBaseZIndex(row, col);
|
||||||
card.SetBaseZIndex(baseZ);
|
card.SetBaseZIndex(baseZ);
|
||||||
|
|
||||||
if (content != null)
|
if (content != null)
|
||||||
content.MoveChild(card, index);
|
content.MoveChild(card, index);
|
||||||
else
|
else
|
||||||
MoveChild(card, index);
|
MoveChild(card, index);
|
||||||
card.UpdateInfo();
|
card.UpdateInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marks the card as dead and starts the <see cref="CardDeathAnimation"/> dissolve sequence.
|
/// Marks the card as dead and starts the <see cref="CardDeathAnimation"/> dissolve sequence.
|
||||||
/// Once the animation completes, the card is removed from <see cref="CardList"/>,
|
/// Once the animation completes, the card is removed from <see cref="CardList"/>,
|
||||||
/// <see cref="OnCardDead"/> is fired, and the remaining cards are redrawn.
|
/// <see cref="OnCardDead"/> is fired, and the remaining cards are redrawn.
|
||||||
/// Triggered by the health-change handler when a character's health reaches zero.
|
/// Triggered by the health-change handler when a character's health reaches zero.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dead(CharacterCardNode characterCardNode)
|
public void Dead(CharacterCardNode characterCardNode)
|
||||||
{
|
{
|
||||||
if (characterCardNode == null || characterCardNode.IsDead)
|
if (characterCardNode == null || characterCardNode.IsDead)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
characterCardNode.IsDead = true;
|
characterCardNode.IsDead = true;
|
||||||
var animator = new Cthangover.CardBattle.UI.CardDeathAnimation();
|
var animator = new Cthangover.CardBattle.UI.CardDeathAnimation();
|
||||||
characterCardNode.AddChild(animator);
|
characterCardNode.AddChild(animator);
|
||||||
animator.StartAnimation(characterCardNode, () =>
|
animator.StartAnimation(characterCardNode, () =>
|
||||||
{
|
{
|
||||||
OnCardDead?.Invoke(characterCardNode);
|
OnCardDead?.Invoke(characterCardNode);
|
||||||
CardList.Remove(characterCardNode);
|
CardList.Remove(characterCardNode);
|
||||||
activeTweens.Remove(characterCardNode);
|
activeTweens.Remove(characterCardNode);
|
||||||
Redraw();
|
Redraw();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+101
-101
@@ -1,101 +1,101 @@
|
|||||||
using System;
|
using System;
|
||||||
using Cthangover.Core.Mods;
|
using Cthangover.Core.Mods;
|
||||||
using Cthangover.Core.Mods.Resolvers;
|
using Cthangover.Core.Mods.Resolvers;
|
||||||
using Cthangover.Core.UI;
|
using Cthangover.Core.UI;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace Cthangover.CardBattle.UI
|
namespace Cthangover.CardBattle.UI
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Plays a death animation on a <see cref="CharacterCardNode"/> when its health reaches zero.
|
/// Plays a death animation on a <see cref="CharacterCardNode"/> when its health reaches zero.
|
||||||
/// The card simultaneously fades out, shrinks toward the screen center, spins 360 degrees,
|
/// The card simultaneously fades out, shrinks toward the screen center, spins 360 degrees,
|
||||||
/// and applies a dissolve shader to all its <see cref="CharacterCardNode.AllImages"/> textures.
|
/// and applies a dissolve shader to all its <see cref="CharacterCardNode.AllImages"/> textures.
|
||||||
/// The dissolve shader (<c>"scene_transition"</c>) is resolved once via <see cref="ModManager"/>
|
/// The dissolve shader (<c>"scene_transition"</c>) is resolved once via <see cref="ModManager"/>
|
||||||
/// and cached statically. Created and started by <see cref="BattleCardPanel.Dead"/>.
|
/// and cached statically. Created and started by <see cref="BattleCardPanel.Dead"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class CardDeathAnimation : Node
|
public partial class CardDeathAnimation : Node
|
||||||
{
|
{
|
||||||
private CharacterCardNode card;
|
private CharacterCardNode card;
|
||||||
private Vector2 center;
|
private Vector2 center;
|
||||||
private Action onComplete;
|
private Action onComplete;
|
||||||
|
|
||||||
private static Shader dissolveShader;
|
private static Shader dissolveShader;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Begins the death animation sequence. Calculates a target position at the viewport center
|
/// Begins the death animation sequence. Calculates a target position at the viewport center
|
||||||
/// (relative to the card's parent), starts fading all child <see cref="Label"/> nodes,
|
/// (relative to the card's parent), starts fading all child <see cref="Label"/> nodes,
|
||||||
/// applies the dissolve shader material to all card images, and tweens position/rotation/alpha.
|
/// applies the dissolve shader material to all card images, and tweens position/rotation/alpha.
|
||||||
/// On completion, invokes <paramref name="onComplete"/> and frees this node.
|
/// On completion, invokes <paramref name="onComplete"/> and frees this node.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StartAnimation(CharacterCardNode card, Action onComplete)
|
public void StartAnimation(CharacterCardNode card, Action onComplete)
|
||||||
{
|
{
|
||||||
this.card = card;
|
this.card = card;
|
||||||
this.onComplete = onComplete;
|
this.onComplete = onComplete;
|
||||||
|
|
||||||
var viewportCenter = card.GetViewport().GetVisibleRect().Size / 2;
|
var viewportCenter = card.GetViewport().GetVisibleRect().Size / 2;
|
||||||
center = card.GetParent<Control>() is Control parent
|
center = card.GetParent<Control>() is Control parent
|
||||||
? viewportCenter - parent.GlobalPosition
|
? viewportCenter - parent.GlobalPosition
|
||||||
: viewportCenter - card.GlobalPosition + card.Position;
|
: viewportCenter - card.GlobalPosition + card.Position;
|
||||||
|
|
||||||
RunAnimation();
|
RunAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RunAnimation()
|
private void RunAnimation()
|
||||||
{
|
{
|
||||||
float duration = 0.8f;
|
float duration = 0.8f;
|
||||||
|
|
||||||
Vector2 endPos = center;
|
Vector2 endPos = center;
|
||||||
|
|
||||||
StartFadeOutRecursive(card, duration);
|
StartFadeOutRecursive(card, duration);
|
||||||
|
|
||||||
if (dissolveShader == null)
|
if (dissolveShader == null)
|
||||||
dissolveShader = Shaders.Resolve("scene_transition");
|
dissolveShader = Shaders.Resolve("scene_transition");
|
||||||
|
|
||||||
if (dissolveShader != null)
|
if (dissolveShader != null)
|
||||||
{
|
{
|
||||||
foreach (var img in card.AllImages)
|
foreach (var img in card.AllImages)
|
||||||
{
|
{
|
||||||
var mat = new ShaderMaterial { Shader = dissolveShader };
|
var mat = new ShaderMaterial { Shader = dissolveShader };
|
||||||
mat.SetShaderParameter("noise_scale", 5f);
|
mat.SetShaderParameter("noise_scale", 5f);
|
||||||
mat.SetShaderParameter("smoothness", 0.25f);
|
mat.SetShaderParameter("smoothness", 0.25f);
|
||||||
mat.SetShaderParameter("distortion", 0.092f);
|
mat.SetShaderParameter("distortion", 0.092f);
|
||||||
mat.SetShaderParameter("glow_intensity", 0f);
|
mat.SetShaderParameter("glow_intensity", 0f);
|
||||||
mat.SetShaderParameter("hue_shift", 0f);
|
mat.SetShaderParameter("hue_shift", 0f);
|
||||||
mat.SetShaderParameter("invert_direction", false);
|
mat.SetShaderParameter("invert_direction", false);
|
||||||
mat.SetShaderParameter("progress", 0f);
|
mat.SetShaderParameter("progress", 0f);
|
||||||
img.Material = mat;
|
img.Material = mat;
|
||||||
|
|
||||||
var matTween = CreateTween();
|
var matTween = CreateTween();
|
||||||
matTween.TweenMethod(
|
matTween.TweenMethod(
|
||||||
Callable.From<float>(t => mat.SetShaderParameter("progress", t)),
|
Callable.From<float>(t => mat.SetShaderParameter("progress", t)),
|
||||||
0f, 1f, duration);
|
0f, 1f, duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tween = CreateTween();
|
var tween = CreateTween();
|
||||||
tween.SetParallel(true);
|
tween.SetParallel(true);
|
||||||
tween.TweenProperty(card, "position", endPos, duration);
|
tween.TweenProperty(card, "position", endPos, duration);
|
||||||
tween.TweenProperty(card, "rotation", Mathf.DegToRad(360f), duration);
|
tween.TweenProperty(card, "rotation", Mathf.DegToRad(360f), duration);
|
||||||
tween.TweenProperty(card, "modulate:a", 0f, duration);
|
tween.TweenProperty(card, "modulate:a", 0f, duration);
|
||||||
|
|
||||||
tween.Finished += () =>
|
tween.Finished += () =>
|
||||||
{
|
{
|
||||||
onComplete?.Invoke();
|
onComplete?.Invoke();
|
||||||
QueueFree();
|
QueueFree();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StartFadeOutRecursive(Node node, float duration)
|
private static void StartFadeOutRecursive(Node node, float duration)
|
||||||
{
|
{
|
||||||
foreach (Node child in node.GetChildren())
|
foreach (Node child in node.GetChildren())
|
||||||
{
|
{
|
||||||
if (child is Label label)
|
if (child is Label label)
|
||||||
{
|
{
|
||||||
var t = child.CreateTween();
|
var t = child.CreateTween();
|
||||||
t.TweenProperty(label, "modulate:a", 0f, duration);
|
t.TweenProperty(label, "modulate:a", 0f, duration);
|
||||||
}
|
}
|
||||||
StartFadeOutRecursive(child, duration);
|
StartFadeOutRecursive(child, duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+47
-47
@@ -1,48 +1,48 @@
|
|||||||
using Cthangover.Core.UI;
|
using Cthangover.Core.UI;
|
||||||
using Cthangover.Core.UI.Theming;
|
using Cthangover.Core.UI.Theming;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
using Cthangover.Mods.Interface.Base.Lists;
|
using Cthangover.Mods.Interface.Base.Lists;
|
||||||
namespace Cthangover.CardBattle.UI
|
namespace Cthangover.CardBattle.UI
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A styled button widget that allows the player to manually end their turn.
|
/// A styled button widget that allows the player to manually end their turn.
|
||||||
/// Displayed during the player's turn and hidden during enemy turns and battle cleanup.
|
/// Displayed during the player's turn and hidden during enemy turns and battle cleanup.
|
||||||
/// Fires <see cref="OnPressed"/> when clicked, which <see cref="CardBattleCore"/> binds
|
/// Fires <see cref="OnPressed"/> when clicked, which <see cref="CardBattleCore"/> binds
|
||||||
/// to <c>OnPlayerTurnEnd</c>. The button text is translated via the <c>"battle/end_turn"</c> key.
|
/// to <c>OnPlayerTurnEnd</c>. The button text is translated via the <c>"battle/end_turn"</c> key.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class EndTurnButton : ModWidget
|
public partial class EndTurnButton : ModWidget
|
||||||
{
|
{
|
||||||
private Button _button;
|
private Button _button;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when the button is pressed. Subscribed by <see cref="CardBattleCore.OnEndTurnPressed"/>.
|
/// Fired when the button is pressed. Subscribed by <see cref="CardBattleCore.OnEndTurnPressed"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event System.Action OnPressed;
|
public event System.Action OnPressed;
|
||||||
|
|
||||||
/// <summary>Builds the end-turn button and binds its pressed event.</summary>
|
/// <summary>Builds the end-turn button and binds its pressed event.</summary>
|
||||||
protected override void Construct()
|
protected override void Construct()
|
||||||
{
|
{
|
||||||
CthangoverTheme.Apply(this);
|
CthangoverTheme.Apply(this);
|
||||||
|
|
||||||
_button = GetNode<Button>("BtnEndTurn");
|
_button = GetNode<Button>("BtnEndTurn");
|
||||||
CthangoverTheme.ApplyPrimary(_button);
|
CthangoverTheme.ApplyPrimary(_button);
|
||||||
|
|
||||||
Size = CustomMinimumSize;
|
Size = CustomMinimumSize;
|
||||||
|
|
||||||
_button.Pressed += RaisePressed;
|
_button.Pressed += RaisePressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RaisePressed() => OnPressed?.Invoke();
|
private void RaisePressed() => OnPressed?.Invoke();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shows or hides the underlying <see cref="Button"/> control.
|
/// Shows or hides the underlying <see cref="Button"/> control.
|
||||||
/// Called by <see cref="CardBattleCore"/> during turn transitions and battle cleanup.
|
/// Called by <see cref="CardBattleCore"/> during turn transitions and battle cleanup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetVisible(bool visible)
|
public void SetVisible(bool visible)
|
||||||
{
|
{
|
||||||
if (_button != null)
|
if (_button != null)
|
||||||
_button.Visible = visible;
|
_button.Visible = visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user