new battle gui
This commit is contained in:
1 parent
b5ad139d69
commit
8fbac46378
59 files changed
+2897
-37
No files matched your search
@@ -0,0 +1,58 @@
|
||||
using Cthangover.Core.Characters;
|
||||
|
||||
namespace Cthangover.Core.Cards.StatusEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// Behaviour hooks for a status effect type. Each hook receives the
|
||||
/// owning character so a single IStatusActions instance can be
|
||||
/// shared across multiple characters. OnDealDamage / OnTakeDamage
|
||||
/// use ref int to let effects amplify or reduce damage before it
|
||||
/// is applied. ModifyAttributes is a passive stat modifier called
|
||||
/// independently of turn phases. OnFinalAction fires once when the
|
||||
/// effect expires. All hooks have empty default semantics — effects
|
||||
/// override only the hooks they care about.
|
||||
/// </summary>
|
||||
public interface IStatusActions
|
||||
{
|
||||
/// <summary>Factory key matching the <c>Actions</c> field in
|
||||
/// <see cref="StatusEffectInfo"/>, e.g. <c>"basic/stun"</c>.</summary>
|
||||
string ID { get; }
|
||||
|
||||
/// <summary>Fires when the owning character's turn begins. Use for
|
||||
/// start-of-turn damage-over-time or regeneration effects.</summary>
|
||||
void OnTurnStart(Character target);
|
||||
/// <summary>Fires when the owning character's turn ends. Mirrors
|
||||
/// <see cref="OnTurnStart"/> for symmetry; useful for effects that
|
||||
/// activate after the character acts.</summary>
|
||||
void OnTurnEnd(Character target);
|
||||
/// <summary>Called before the owning character deals damage to
|
||||
/// <paramref name="target"/>. Modify <paramref name="damage"/> via
|
||||
/// <see langword="ref"/> to amplify or reduce outgoing damage —
|
||||
/// all active effects stack multiplicatively through the queue's
|
||||
/// iteration.</summary>
|
||||
void OnDealDamage(Character source, Character target, ref int damage);
|
||||
/// <summary>Called before the owning character receives damage from
|
||||
/// <paramref name="source"/>. Modify <paramref name="damage"/> via
|
||||
/// <see langword="ref"/> to add damage reduction or vulnerability.
|
||||
/// Fires after <see cref="OnDealDamage"/> in the damage
|
||||
/// pipeline.</summary>
|
||||
void OnTakeDamage(Character target, Character source, ref int damage);
|
||||
/// <summary>Fires immediately when the effect is first placed on a
|
||||
/// character. Use for one-shot stat boosts, visual feedback, or
|
||||
/// initialisation logic.</summary>
|
||||
void OnApply(Character target);
|
||||
/// <summary>Fires when the effect is manually removed (e.g. via a
|
||||
/// cleanse card). Not called on natural expiration — use
|
||||
/// <see cref="OnFinalAction"/> for that path.</summary>
|
||||
void OnRemove(Character target);
|
||||
/// <summary>Fires once when the effect expires naturally (timer reaches
|
||||
/// zero). Use for end-of-effect triggers like explosion damage or
|
||||
/// stat restoration. Not called on manual removal.</summary>
|
||||
void OnFinalAction(Character target);
|
||||
/// <summary>Passive stat modifier invoked independently of turn phases.
|
||||
/// Receives a reference to the character's attribute container;
|
||||
/// implementors can add flat or percentage bonuses that other
|
||||
/// effects may also contribute to.</summary>
|
||||
void ModifyAttributes(CharacterAttributes attributes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Godot;
|
||||
|
||||
namespace Cthangover.Core.Cards.StatusEffect
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Status effect data contract. Separates identity (ID, Name, Icon)
|
||||
/// from mutable state (Duration, RemainingTurns) and behaviour
|
||||
/// (Actions — an IStatusActions hook set). Copy() is required so the
|
||||
/// StatusEffectQueue can clone effects when characters are duplicated.
|
||||
/// </summary>
|
||||
public interface IStatusEffect
|
||||
{
|
||||
/// <summary>Unique mod identifier, e.g. <c>"basic/stun"</c>. Used by factories
|
||||
/// and the queue to look up and remove effects by key.</summary>
|
||||
string ID { get; }
|
||||
/// <summary>Display name shown in the battle UI.</summary>
|
||||
string Name { get; }
|
||||
/// <summary>Flavour text explaining the effect to the player.</summary>
|
||||
string Description { get; }
|
||||
/// <summary>High-level category (<c>Buff</c>, <c>Debuff</c>, <c>Stun</c>) used
|
||||
/// for UI tinting and quick type checks like
|
||||
/// <see cref="StatusEffectQueue.HasStun"/>.</summary>
|
||||
StatusEffectType EffectType { get; }
|
||||
/// <summary>Base duration in turns. Set during effect creation; may be
|
||||
/// overridden by the caller via <see cref="StatusEffectQueue.Add"/>.
|
||||
/// A value of 0 or negative means the effect never expires
|
||||
/// naturally.</summary>
|
||||
int Duration { get; set; }
|
||||
/// <summary>Turns remaining before the effect expires. Decremented each
|
||||
/// turn boundary by the queue. Independent of <c>Duration</c> so
|
||||
/// that the original value is preserved for display or re-apply
|
||||
/// logic.</summary>
|
||||
int RemainingTurns { get; set; }
|
||||
/// <summary>Icon texture loaded from the <c>"characters"</c> mod group,
|
||||
/// displayed beside the effect name in the HUD.</summary>
|
||||
Texture2D Icon { get; }
|
||||
/// <summary>Behaviour hooks that fire during turn phases and on damage
|
||||
/// events. Resolved by <c>StatusEffectActionFactory</c> from the
|
||||
/// <see cref="StatusEffectInfo.Actions"/> string ID.</summary>
|
||||
IStatusActions Actions { get; }
|
||||
|
||||
/// <summary>Creates a shallow clone. <c>Actions</c> is a shared reference
|
||||
/// (stateless singleton) so it is not deep-copied. Used by
|
||||
/// <see cref="StatusEffectQueue.Copy"/> when duplicating a
|
||||
/// character.</summary>
|
||||
IStatusEffect Copy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Cthangover.Core.Characters;
|
||||
|
||||
namespace Cthangover.Core.Cards.StatusEffect.Impls
|
||||
{
|
||||
/// <summary>
|
||||
/// Stun behaviour set — intentionally all no-ops. The stun condition
|
||||
/// is checked separately via StatusEffectQueue.HasStun() rather than
|
||||
/// through action hooks, so the Actions implementation is empty.
|
||||
/// Exists as a concrete type so the factory can resolve "basic/stun"
|
||||
/// to a valid instance.
|
||||
/// </summary>
|
||||
public class StunStatusEffect : IStatusActions
|
||||
{
|
||||
/// <inheritdoc cref="IStatusActions.ID"/>
|
||||
public string ID => "basic/stun";
|
||||
|
||||
/// <inheritdoc cref="IStatusActions.OnTurnStart"/>
|
||||
public void OnTurnStart(Character target)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc cref="IStatusActions.OnTurnEnd"/>
|
||||
public void OnTurnEnd(Character target)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc cref="IStatusActions.OnDealDamage"/>
|
||||
public void OnDealDamage(Character source, Character target, ref int damage)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc cref="IStatusActions.OnTakeDamage"/>
|
||||
public void OnTakeDamage(Character target, Character source, ref int damage)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc cref="IStatusActions.OnApply"/>
|
||||
public void OnApply(Character target)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc cref="IStatusActions.OnRemove"/>
|
||||
public void OnRemove(Character target)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc cref="IStatusActions.OnFinalAction"/>
|
||||
public void OnFinalAction(Character target)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc cref="IStatusActions.ModifyAttributes"/>
|
||||
public void ModifyAttributes(CharacterAttributes attributes)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Cthangover.Core.Factories;
|
||||
|
||||
namespace Cthangover.Core.Cards.StatusEffect
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// JSON-serialisable status effect definition loaded from mod files.
|
||||
/// The Actions field is an ID string resolved by StatusEffectActionFactory
|
||||
/// into an IStatusActions instance at runtime. Type uses
|
||||
/// JsonStringEnumConverter for human-readable config files.
|
||||
/// Implements IIdentifiable so it works with PrefabFactory caching.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class StatusEffectInfo : IIdentifiable
|
||||
{
|
||||
/// <summary>Unique effect identifier used as the factory key.
|
||||
/// Serialised as <c>"Id"</c> in JSON for consistency with the
|
||||
/// modding schema.</summary>
|
||||
[JsonPropertyName("Id")]
|
||||
public string ID { get; set; }
|
||||
/// <summary>Display name for the effect, shown in tooltips and the
|
||||
/// character status panel.</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>Human-readable description explaining what the effect
|
||||
/// does.</summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>Category of the effect. Serialised as a string
|
||||
/// (<c>"Buff"</c>, <c>"Debuff"</c>, <c>"Stun"</c>) via
|
||||
/// <c>JsonStringEnumConverter</c> for readable mod configs.</summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public StatusEffectType Type { get; set; }
|
||||
/// <summary>Base duration in turns before the effect naturally
|
||||
/// expires.</summary>
|
||||
public int Duration { get; set; }
|
||||
/// <summary>Factory ID string for the behaviour set, resolved by
|
||||
/// <c>StatusEffectActionFactory</c> into an
|
||||
/// <see cref="IStatusActions"/> instance at runtime.</summary>
|
||||
public string Actions { get; set; }
|
||||
/// <summary>Filename of the icon texture within the
|
||||
/// <c>"characters"</c> mod group, loaded by
|
||||
/// <see cref="StatusEffectItem"/> during construction.</summary>
|
||||
public string Icon { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using Cthangover.Core.Factories.Impls;
|
||||
using Cthangover.Core.Utils;
|
||||
using Godot;
|
||||
|
||||
namespace Cthangover.Core.Cards.StatusEffect
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Runtime instance of a status effect. Constructed from
|
||||
/// StatusEffectInfo with the Actions behaviour resolved via
|
||||
/// StatusEffectActionFactory. Icon is loaded from the "characters"
|
||||
/// mod group via TextureUtils — effects share the same icon pool
|
||||
/// as characters. Copy() is a shallow clone of primitives and
|
||||
/// references (Actions is a shared singleton, not cloned) suitable
|
||||
/// for StatusEffectQueue duplication.
|
||||
/// </summary>
|
||||
public class StatusEffectItem : IStatusEffect
|
||||
{
|
||||
/// <inheritdoc cref="IStatusEffect.ID"/>
|
||||
public string ID { get; private set; }
|
||||
/// <inheritdoc cref="IStatusEffect.Name"/>
|
||||
public string Name { get; private set; }
|
||||
/// <inheritdoc cref="IStatusEffect.Description"/>
|
||||
public string Description { get; private set; }
|
||||
/// <inheritdoc cref="IStatusEffect.Duration"/>
|
||||
public int Duration { get; set; }
|
||||
/// <inheritdoc cref="IStatusEffect.RemainingTurns"/>
|
||||
public int RemainingTurns { get; set; }
|
||||
/// <inheritdoc cref="IStatusEffect.EffectType"/>
|
||||
public StatusEffectType EffectType { get; private set; }
|
||||
/// <inheritdoc cref="IStatusEffect.Icon"/>
|
||||
public Texture2D Icon { get; private set; }
|
||||
/// <inheritdoc cref="IStatusEffect.Actions"/>
|
||||
public IStatusActions Actions { get; private set; }
|
||||
|
||||
/// <summary>Constructs a runtime effect from its JSON-serialisable
|
||||
/// definition. Resolves the icon via
|
||||
/// <c>TextureUtils.LoadFromModGroup</c> and the action set via
|
||||
/// <c>StatusEffectActionFactory</c>.</summary>
|
||||
/// <param name="info">Deserialised effect data from a mod
|
||||
/// config.</param>
|
||||
public StatusEffectItem(StatusEffectInfo info)
|
||||
{
|
||||
ID = info.ID;
|
||||
Name = info.Name;
|
||||
Description = info.Description;
|
||||
Duration = info.Duration;
|
||||
EffectType = info.Type;
|
||||
Icon = TextureUtils.LoadFromModGroup("characters", info.Icon);
|
||||
Actions = StatusEffectActionFactory.Instance.Get(info.Actions);
|
||||
}
|
||||
|
||||
/// <summary>Parameterless constructor for cases where properties are
|
||||
/// set manually (e.g. by <see cref="Copy"/> or deserialisation
|
||||
/// paths).</summary>
|
||||
public StatusEffectItem() { }
|
||||
|
||||
/// <summary>Shallow clone that copies primitives and reuses the
|
||||
/// shared <c>Actions</c> reference. Used when the queue
|
||||
/// duplicates effects during character cloning.</summary>
|
||||
/// <returns>A new <see cref="StatusEffectItem"/> with identical
|
||||
/// property values.</returns>
|
||||
public IStatusEffect Copy()
|
||||
{
|
||||
return new StatusEffectItem
|
||||
{
|
||||
ID = ID,
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
Duration = Duration,
|
||||
RemainingTurns = RemainingTurns,
|
||||
EffectType = EffectType,
|
||||
Icon = Icon,
|
||||
Actions = Actions,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cthangover.Core.Characters;
|
||||
using Cthangover.Core.Utils;
|
||||
using Godot;
|
||||
|
||||
namespace Cthangover.Core.Cards.StatusEffect
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Per-character status-effect pipeline. Manages a list of active
|
||||
/// effects, advancing their timers on turn boundaries and removing
|
||||
/// expired ones after firing OnFinalAction. Add resolves the effect
|
||||
/// info via the factory and calls OnApply immediately. OnTurnStart
|
||||
/// iterates backwards to safely remove expired effects during
|
||||
/// traversal. OnDealDamage / OnTakeDamage pipe through every active
|
||||
/// effect via ref parameters, enabling multiplicative stacking.
|
||||
/// ActiveStatusEffects returns a defensive copy so external code
|
||||
/// cannot mutate the internal list. Copy creates a deep clone for
|
||||
/// character duplication, re-firing OnApply on the new owner.
|
||||
/// </summary>
|
||||
public class StatusEffectQueue
|
||||
{
|
||||
private readonly List<IStatusEffect> activeStatusEffectData;
|
||||
private readonly Character owner;
|
||||
|
||||
/// <summary>Defensive copy of the internal effect list. Returns
|
||||
/// a new <c>List</c> each access so external code cannot mutate
|
||||
/// the queue's backing store. Safe for enumeration.</summary>
|
||||
public List<IStatusEffect> ActiveStatusEffects => activeStatusEffectData.ToList();
|
||||
|
||||
/// <summary>Initialises an empty queue bound to <paramref name="owner"/>.
|
||||
/// The owner reference is used by all action-hook calls to pass
|
||||
/// the affected character.</summary>
|
||||
/// <param name="owner">The character who carries these
|
||||
/// effects.</param>
|
||||
public StatusEffectQueue(Character owner)
|
||||
{
|
||||
this.activeStatusEffectData = new List<IStatusEffect>();
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
/// <summary>Checks whether any active effect has
|
||||
/// <see cref="StatusEffectType.Stun"/>. Called by the battle
|
||||
/// engine to determine if the owner should skip their turn.</summary>
|
||||
/// <returns><see langword="true"/> if at least one Stun-type effect
|
||||
/// is active.</returns>
|
||||
public bool HasStun()
|
||||
{
|
||||
foreach (var statusEffect in activeStatusEffectData)
|
||||
if (statusEffect.EffectType == StatusEffectType.Stun)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Looks up <paramref name="statusEffectId"/> in the factory,
|
||||
/// constructs a <see cref="StatusEffectItem"/>, optionally
|
||||
/// overrides its duration, fires <c>OnApply</c>, and appends it
|
||||
/// to the active list.</summary>
|
||||
/// <param name="statusEffectId">Factory key matching a
|
||||
/// <see cref="StatusEffectInfo"/> entry.</param>
|
||||
/// <param name="duration">If positive, overwrites the default
|
||||
/// duration from the effect definition.</param>
|
||||
/// <returns><see langword="false"/> if the factory lookup fails
|
||||
/// (key not found); otherwise <see langword="true"/>.</returns>
|
||||
public bool Add(string statusEffectId, int duration = -1)
|
||||
{
|
||||
var statusEffectInfo = Factories.Impls.StatusEffectInfoFactory.Instance.Get(statusEffectId);
|
||||
|
||||
if (statusEffectInfo == null)
|
||||
{
|
||||
GameLogger.Log("STATUS", $"statusEffect with ID '{statusEffectId}' not found!", LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
var statusEffect = new StatusEffectItem(statusEffectInfo);
|
||||
|
||||
if (duration > 0)
|
||||
{
|
||||
statusEffect.Duration = duration;
|
||||
statusEffect.RemainingTurns = duration;
|
||||
}
|
||||
|
||||
statusEffect.Actions?.OnApply(owner);
|
||||
activeStatusEffectData.Add(statusEffect);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Removes the first effect matching
|
||||
/// <paramref name="statusEffectId"/> from the active list.
|
||||
/// Fires <c>OnRemove</c> so the effect can clean up before
|
||||
/// disappearing (e.g. revert stat modifications).</summary>
|
||||
/// <param name="statusEffectId">Factory key of the effect to
|
||||
/// remove.</param>
|
||||
/// <returns><see langword="false"/> if no matching effect was
|
||||
/// found; otherwise <see langword="true"/>.</returns>
|
||||
public bool Remove(string statusEffectId)
|
||||
{
|
||||
var statusEffect = activeStatusEffectData.FirstOrDefault(b => b.ID == statusEffectId);
|
||||
if (statusEffect == null)
|
||||
return false;
|
||||
|
||||
statusEffect.Actions?.OnRemove(owner);
|
||||
activeStatusEffectData.Remove(statusEffect);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Advances all active effects at the start of the owner's
|
||||
/// turn. Fires <c>OnTurnStart</c> on each effect, decrements
|
||||
/// <see cref="IStatusEffect.RemainingTurns"/> for timed effects,
|
||||
/// and removes expired ones after firing
|
||||
/// <c>OnFinalAction</c>. Iterates backwards so removals do not
|
||||
/// corrupt the index.</summary>
|
||||
public void OnTurnStart()
|
||||
{
|
||||
for (int i = activeStatusEffectData.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var statusEffect = activeStatusEffectData[i];
|
||||
statusEffect.Actions?.OnTurnStart(owner);
|
||||
if (statusEffect.RemainingTurns > 0 && statusEffect.Duration > 0)
|
||||
{
|
||||
statusEffect.RemainingTurns--;
|
||||
if (statusEffect.RemainingTurns <= 0)
|
||||
{
|
||||
statusEffect.Actions?.OnFinalAction(owner);
|
||||
activeStatusEffectData.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fires end-of-turn hooks on all active effects. Operates
|
||||
/// on a defensive copy so that <c>OnTurnEnd</c> callbacks that
|
||||
/// add or remove effects do not invalidate the enumerator.
|
||||
/// Decrements remaining turns and removes expired effects via
|
||||
/// <see cref="Remove"/> (which also fires
|
||||
/// <c>OnRemove</c>).</summary>
|
||||
public void OnTurnEnd()
|
||||
{
|
||||
foreach (var statusEffect in ActiveStatusEffects)
|
||||
{
|
||||
statusEffect.Actions?.OnTurnEnd(owner);
|
||||
statusEffect.RemainingTurns--;
|
||||
if (statusEffect.RemainingTurns <= 0)
|
||||
{
|
||||
statusEffect.Actions?.OnFinalAction(owner);
|
||||
Remove(statusEffect.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Pipes outgoing damage through every active effect's
|
||||
/// <c>OnDealDamage</c> hook, allowing each to modify
|
||||
/// <paramref name="damage"/> via <see langword="ref"/>.
|
||||
/// Effects iterate in insertion order; multiplicative stacking
|
||||
/// means order-agnostic results for flat modifiers but
|
||||
/// order-sensitive for percentage modifiers.</summary>
|
||||
/// <param name="target">The character receiving the damage.</param>
|
||||
/// <param name="damage">The damage value, modifiable by effects
|
||||
/// through the <see langword="ref"/> parameter.</param>
|
||||
public void OnDealDamage(Character target, ref int damage)
|
||||
{
|
||||
foreach (var statusEffect in activeStatusEffectData)
|
||||
{
|
||||
statusEffect.Actions?.OnDealDamage(owner, target, ref damage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Pipes incoming damage through every active effect's
|
||||
/// <c>OnTakeDamage</c> hook. Called after
|
||||
/// <see cref="OnDealDamage"/> in the damage pipeline, so
|
||||
/// defensive modifiers stack on top of the attacker's
|
||||
/// amplified value.</summary>
|
||||
/// <param name="source">The character dealing the damage.</param>
|
||||
/// <param name="damage">The damage value, modifiable by effects
|
||||
/// through the <see langword="ref"/> parameter.</param>
|
||||
public void OnTakeDamage(Character source, ref int damage)
|
||||
{
|
||||
foreach (var statusEffect in activeStatusEffectData)
|
||||
{
|
||||
statusEffect.Actions?.OnTakeDamage(owner, source, ref damage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all active effects, firing <c>OnRemove</c> on
|
||||
/// each before clearing the list. Used when a character dies or
|
||||
/// an encounter ends so effects do not leak between battles.</summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
foreach (var statusEffect in activeStatusEffectData)
|
||||
{
|
||||
statusEffect.Actions?.OnRemove(owner);
|
||||
}
|
||||
activeStatusEffectData.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Deep-clones the entire queue for a new character owner.
|
||||
/// Each effect is copied via <see cref="IStatusEffect.Copy"/>,
|
||||
/// and <c>OnApply</c> is re-fired on <paramref name="newOwner"/>
|
||||
/// so initialisation hooks run in the target's context. Used
|
||||
/// when a character is duplicated mid-encounter (e.g. a summon
|
||||
/// or mirror effect).</summary>
|
||||
/// <param name="newOwner">The character who will own the
|
||||
/// clone.</param>
|
||||
/// <returns>A new <see cref="StatusEffectQueue"/> bound to
|
||||
/// <paramref name="newOwner"/> with copies of all active
|
||||
/// effects.</returns>
|
||||
public StatusEffectQueue Copy(Character newOwner)
|
||||
{
|
||||
var newSystem = new StatusEffectQueue(newOwner);
|
||||
foreach (var statusEffect in activeStatusEffectData)
|
||||
{
|
||||
var copiedstatusEffect = statusEffect.Copy();
|
||||
copiedstatusEffect.Actions?.OnApply(newOwner);
|
||||
newSystem.activeStatusEffectData.Add(copiedstatusEffect);
|
||||
}
|
||||
return newSystem;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Cthangover.Core.Characters;
|
||||
|
||||
namespace Cthangover.Core.Cards.StatusEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// Side-table that owns the <see cref="StatusEffectQueue"/> of every character
|
||||
/// participating in battle. The queue used to live on <see cref="Character"/>
|
||||
/// itself, but the core character model must stay free of battle subsystems —
|
||||
/// so the battle mod keeps queues keyed by character instance instead.
|
||||
/// <para/>
|
||||
/// Uses <see cref="ConditionalWeakTable{TKey, TValue}"/>: queues die together
|
||||
/// with their character, which matters because each battle constructs fresh
|
||||
/// copies via <see cref="Character.Copy"/> and discards them afterwards. All
|
||||
/// access happens on the main thread during battle, so no extra locking is
|
||||
/// needed beyond what the weak table provides.
|
||||
/// </summary>
|
||||
public static class StatusEffectService
|
||||
{
|
||||
private static readonly ConditionalWeakTable<Character, StatusEffectQueue> queues = new();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the queue of <paramref name="character"/>, creating an empty one
|
||||
/// on first access. Returns <c>null</c> for a null character so call sites
|
||||
/// can keep their existing null-conditional patterns.
|
||||
/// </summary>
|
||||
public static StatusEffectQueue Of(Character character)
|
||||
{
|
||||
if (character == null)
|
||||
return null;
|
||||
|
||||
return queues.GetValue(character, CreateQueue);
|
||||
}
|
||||
|
||||
private static StatusEffectQueue CreateQueue(Character owner)
|
||||
{
|
||||
return new StatusEffectQueue(owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace Cthangover.Core.Cards.StatusEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// High-level classification for status effects. Buff/Debuff are
|
||||
/// informational (UI colouring, stacking rules); Stun is singled
|
||||
/// out because the queue has a dedicated HasStun() check that the
|
||||
/// battle engine queries to skip a stunned character's turn.
|
||||
/// </summary>
|
||||
public enum StatusEffectType
|
||||
{
|
||||
/// <summary>Positive effect that benefits the bearer (e.g. stat boost,
|
||||
/// regeneration). Rendered with a friendly tint in the UI.</summary>
|
||||
Buff,
|
||||
/// <summary>Negative effect that harms the bearer (e.g. damage over
|
||||
/// time, stat reduction). Rendered with a hostile tint in the
|
||||
/// UI.</summary>
|
||||
Debuff,
|
||||
/// <summary>Crowd-control effect that causes the bearer to skip
|
||||
/// their turn. Checked by
|
||||
/// <see cref="StatusEffectQueue.HasStun"/>; the action hooks
|
||||
/// are deliberately no-ops — the skip is enforced by the battle
|
||||
/// engine reading this type flag, not by the
|
||||
/// <see cref="IStatusActions"/> callbacks.</summary>
|
||||
Stun,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user