Introduce the EffectProcessor class to manage game state changes through various effects, including damage, healing, and status application. Define type specifications for effects, conditions, and passive abilities in Effects.d.ts. Add a comprehensive JSON registry for passive skills and item effects, enhancing gameplay dynamics. Update the GameLoop and TurnSystem to integrate the EffectProcessor, ensuring proper handling of environmental hazards and passive effects during combat. Enhance testing coverage for the EffectProcessor and environmental interactions to validate functionality and performance.
8.7 KiB
8.7 KiB
Effect Processor Specification: The Game Logic Engine
This document defines the architecture for the Effect Processor, the central system responsible for executing all changes to the game state (Damage, Healing, Movement, Spawning).
1. System Overview
The EffectProcessor is a stateless logic engine. It takes a Definition (What to do) and a Context (Who is doing it to whom), and applies the necessary mutations to the UnitManager or VoxelGrid.
Architectural Role
- Input: EffectDefinition (JSON), Source (Unit), Target (Unit/Tile).
- Output: State Mutation (HP changed, Unit moved) + EffectResult (Log data).
- Pattern: Strategy Pattern. Each effect_type maps to a specific Handler Function.
2. Integration Points
A. Calling the Processor
The Processor is never called directly by the UI. It is invoked by:
- SkillManager: When an Active Skill is executed.
- EventSystem: When a Passive Item triggers (e.g., "On Hit -> Apply Burn").
- Environmental Hazard: When a unit starts their turn on Fire/Acid.
B. Dependencies
The Processor requires injection of:
- VoxelGrid: To check collision, modify terrain, or move units.
- UnitManager: To find neighbors (Chain Lightning) or spawn tokens (Turrets).
- RNG: A seeded random number generator for damage variance and status chances.
3. Data Structure (JSON Schema)
Every effect in the game must adhere to this structure.
/**
* Effects.ts
* Type definitions for the Game Logic Engine (Effects, Passives, and Triggers).
*/
// =============================================================================
// 1. EFFECT DEFINITIONS (The "What")
// =============================================================================
/**
* List of all valid actions the EffectProcessor can execute.
*/
export type EffectType =
// Combat
| "DAMAGE"
| "HEAL"
| "CHAIN_DAMAGE"
| "REDIRECT_DAMAGE"
| "PREVENT_DEATH"
// Status & Stats
| "APPLY_STATUS"
| "REMOVE_STATUS"
| "REMOVE_ALL_DEBUFFS"
| "APPLY_BUFF"
| "GIVE_AP"
| "ADD_CHARGE"
| "ADD_SHIELD"
| "CONVERT_DAMAGE_TO_HEAL"
| "DYNAMIC_BUFF"
// Movement & Physics
| "TELEPORT"
| "MOVE_TO_TARGET"
| "SWAP_POSITIONS"
| "PHYSICS_PULL"
| "PUSH"
// World & Spawning
| "SPAWN_OBJECT"
| "SPAWN_HAZARD"
| "SPAWN_LOOT"
| "MODIFY_TERRAIN"
| "DESTROY_VOXEL"
| "DESTROY_OBJECTS"
| "REVEAL_OBJECTS"
| "COLLECT_LOOT"
// Meta / Logic
| "REPEAT_SKILL"
| "CANCEL_EVENT"
| "REDUCE_COST"
| "BUFF_SPAWN"
| "MODIFY_AOE";
/**
* A generic container for parameters used by Effect Handlers.
* In a strict system, this would be a Union of specific interfaces,
* but for JSON deserialization, a loose interface is often more flexible.
*/
export interface EffectParams {
// -- Combat Magnitude --
power?: number; // Base amount (Damage/Heal)
attribute?: string; // Stat to scale off (e.g., "strength", "magic")
scaling?: number; // Multiplier for attribute (Default: 1.0)
element?: "PHYSICAL" | "FIRE" | "ICE" | "SHOCK" | "VOID" | "TECH";
// -- Chaining --
bounces?: number;
decay?: number;
synergy_trigger?: string; // Status ID that triggers bonus effect
// -- Status/Buffs --
status_id?: string;
duration?: number;
stat?: string; // For Buffs
value?: number; // For Buffs/Mods
chance?: number; // 0.0 to 1.0 (Proc chance)
// -- Physics --
force?: number; // Distance
destination?: "TARGET" | "BEHIND_TARGET" | "ADJACENT_TO_TARGET";
// -- World --
object_id?: string; // Unit ID to spawn
hazard_id?: string;
tag?: string; // Filter for objects (e.g. "COVER")
range?: number; // AoE radius
// -- Logic --
percentage?: number; // 0.0 - 1.0
amount?: number; // Flat amount (AP/Charge)
amount_range?: [number, number]; // [min, max]
set_hp?: number; // Hard set HP value
shape?: "CIRCLE" | "LINE" | "CONE" | "SINGLE";
size?: number;
multiplier?: number;
}
/**
* The runtime payload passed to EffectProcessor.process().
* Combines the Type and the Params.
*/
export interface EffectDefinition extends EffectParams {
type: EffectType;
// Optional Override Condition for the Effect itself
// (Distinct from the Passive Trigger condition)
condition?: ConditionDefinition;
// Conditional Multiplier (e.g. Execute damage)
conditional_multiplier?: {
condition: string; // Condition Tag
value: number;
};
}
// =============================================================================
// 2. PASSIVE DEFINITIONS (The "When")
// =============================================================================
/**
* Triggers that the EventSystem listens for.
*/
export type TriggerType =
// Stat Calculation Hooks
| "ON_STAT_CALC"
| "STATIC_STAT_MOD"
| "ON_SKILL_COST_CALC"
| "ON_ATTACK_CALC"
| "ON_DAMAGE_CALC"
// Combat Events
| "ON_ATTACK_HIT"
| "ON_SKILL_HIT"
| "ON_SKILL_CAST"
| "ON_DAMAGED"
| "ON_ALLY_DAMAGED"
| "ON_DAMAGE_DEALT"
| "ON_HEAL_DEALT"
| "ON_HEAL_OVERFLOW"
| "ON_KILL"
| "ON_LETHAL_DAMAGE"
| "ON_SYNERGY_TRIGGER"
// Turn Lifecycle
| "ON_TURN_START"
| "ON_TURN_END"
| "ON_ACTION_COMPLETE"
// World Events
| "ON_MOVE_COMPLETE"
| "ON_HAZARD_TICK"
| "ON_TRAP_TRIGGER"
| "ON_OBJECT_DESTROYED"
| "ON_SPAWN_OBJECT"
| "ON_LEVEL_START"
// Meta / UI
| "GLOBAL_SHOP_PRICE"
| "ON_SKILL_TARGETING"
| "AURA_UPDATE";
/**
* How a stat modifier interacts with the base value.
*/
export type ModifierType =
| "ADD"
| "MULTIPLY"
| "ADD_PER_ADJACENT_ENEMY"
| "ADD_PER_DESTROYED_VOXEL"
| "ADD_STAT"
| "MULTIPLY_DAMAGE";
/**
* An entry in `passive_registry.json`.
* Defines a rule: "WHEN [Trigger] IF [Condition] THEN [Action]".
*/
export interface PassiveDefinition {
id: string;
name: string;
description: string;
// -- The Trigger --
trigger: TriggerType;
// -- The Check --
condition?: ConditionDefinition;
limit?: number; // Max times per run/turn
// -- The Effect (Action) --
// Maps to EffectDefinition.type
action?: EffectType;
// -- The Parameters --
// Merged into EffectDefinition
params?: EffectParams;
// -- For Stat Modifiers (simpler than full Effects) --
stat?: string;
modifier_type?: ModifierType;
value?: number;
range?: number; // For Aura/Per-Adjacent checks
// -- For Complex Stat Lists --
modifiers?: { stat: string; value: number }[];
// -- For Auras --
target?: "ALLIES" | "ENEMIES" | "SELF";
}
// =============================================================================
// 3. CONDITIONS (The "If")
// =============================================================================
export type ConditionType =
| "SOURCE_IS_ADJACENT"
| "IS_ADJACENT"
| "IS_BASIC_ATTACK"
| "SKILL_TAG" // value: string
| "DAMAGE_TYPE" // value: string
| "TARGET_TAG" // value: string
| "OWNER_IS_SELF"
| "IS_FLANKING"
| "TARGET_LOCKED"
| "DID_NOT_ATTACK"
| "TARGET_HP_LOW"
| "IS_MECHANICAL";
export interface ConditionDefinition {
type: ConditionType;
value?: string | number | boolean;
}
4. Handler Specifications
Handler: DAMAGE
- Logic:
FinalDamage = (BasePower + (Source[Attribute] * Scaling)) - Target.Defense. - Element Check: If Target has Resistance/Weakness to
element, modify FinalDamage. - Result:
Target.currentHP -= FinalDamage.
Handler: CHAIN_DAMAGE
- Logic: Apply
DAMAGEto primary target. Then, scan for N nearest enemies within Range R. ApplyDAMAGE * Decayto them. - Synergy: If
condition.target_statusis present on a target, the chain may branch or deal double damage.
Handler: TELEPORT
- Logic: Validate destination tile (must be Air and Unoccupied). Update
Unit.positionandVoxelGrid.unitMap. - Visuals: Trigger "Vanish" VFX at old pos, "Appear" VFX at new pos.
Handler: MODIFY_TERRAIN
- Logic: Update
VoxelGridID at Target coordinates. - Use Case: Sapper's "Breach Charge" turns
ID_WALLintoID_AIR. - Safety: Check
VoxelGrid.isDestructible(). Do not destroy bedrock.
5. Conditions of Acceptance (CoA)
CoA 1: Attribute Scaling
- Given a Damage Effect with
power: 10andattribute: "magic", if Source hasmagic: 5, the output damage must be 15.
CoA 2: Conditional Logic
- Given an Effect with
condition: { target_status: "WET" }, if the target does not have the "WET" status, the effect must not execute (return early).
CoA 3: State Mutation
- When
APPLY_STATUSis executed, the Target unit'sstatusEffectsarray must contain the new ID with the correct duration.
CoA 4: Physics Safety
- When
PUSHis executed, the system must checkVoxelGrid.isSolid()behind the target. If a wall exists, the unit must not move into the wall (optionally take "Smash" damage instead).