[CPLUS plus ex] SkillEffectModifier Base Class - Redacted-Rice/ITB-RedactedRiceExts GitHub Wiki

SkillEffectModifier Base Class

Base class for pilot skills that modify weapon damage and effects. Automatically intercepts skill effect creation to let you add, modify, or react to SpaceDamage objects.

Note: SkillEffectModifier inherits from SkillActive , so you can also use setupEffect() to register additional event handlers.

Basic Usage

local mySkill = cplus_plus_ex.baseClasses.SkillEffectModifier:new{
    id = "MyDamageSkill",
    shortName = "Bonus Dmg",
    fullName = "Bonus Damage",
    description = "Adds +1 damage to all attacks",
    priority = 100,  -- Optional: Lower values run first (default: 100)
    modifiesKillDamage = true,  -- Optional, default true. Set false to skip IsDeadly registration
}

-- Override modifySpaceDamage to modify weapon effects
function mySkill:modifySpaceDamage(source, attackingPawn, phase, spaceDamage, indexes, targetPawn)
    local newDamage = self:modifyKillDamage(source, attackingPawn, spaceDamage, indexes, targetPawn, spaceDamage.iDamage)
    if newDamage == spaceDamage.iDamage then
        return
    end
    if source == self.SOURCE_ATTACKER then
        spaceDamage.iDamage = newDamage
        -- Side effects (icons, sScript, etc.) only belong here, not in modifyKillDamage
    end
end

-- No side effects - used for Board:IsDeadly / GetModifiedDamage preview
function mySkill:modifyKillDamage(source, attackingPawn, spaceDamage, indexes, targetPawn, currentDamage)
    if source == self.SOURCE_ATTACKER then
        return currentDamage + 1
    end
    return currentDamage
end

function mod:init()
    cplus_plus_ex:registerSkill("Custom", mySkill)
end

Methods to Override

modifySpaceDamage

modifySpaceDamage(source, attackingPawn, phase, spaceDamage, indexes, targetPawn)

Called for each SpaceDamage during skill/final effect builds. Mutate spaceDamage in place. Do not return anything (returns are ignored).

Parameters:

  • source (string): Either SOURCE_ATTACKER or SOURCE_TARGET
    • SOURCE_ATTACKER: The attacking pawn's pilot has this skill
    • SOURCE_TARGET: The target pawn's pilot (at spaceDamage.loc after push/move tracking) has this skill
  • attackingPawn (Pawn): The pawn using the weapon
  • phase (number): One of cplus_plus_ex.damageModifierLib.PHASE_* (see Phases below)
  • spaceDamage (SpaceDamage): The damage object being processed - modify this directly
  • indexes (table): Array of skill slot numbers where this skill appears (e.g., {1}, {2}, or {1, 2})
  • targetPawn (Pawn or nil): The pawn at spaceDamage.loc, if any

Example - Modifying existing damage:

function mySkill:modifySpaceDamage(source, attackingPawn, phase, spaceDamage, indexes, targetPawn)
    if source == self.SOURCE_ATTACKER then
        spaceDamage.iDamage = spaceDamage.iDamage * 2
    end
end

To add new SpaceDamages after the full pass, override SkillEffectEvaluated (not modifySpaceDamage).

modifyKillDamage

modifyKillDamage(source, attackingPawn, spaceDamage, indexes, targetPawn, currentDamage)

Called from DamageModifierLib / Board:IsDeadly preview (and typically from your modifySpaceDamage for consistent totals).

Returns: Adjusted damage total (number). No side effects (no icons, sScript, etc.).

Default implementation returns currentDamage unchanged.

Set modifiesKillDamage = false on the skill table to skip onEvaluatingDeadly registration entirely.

function mySkill:modifyKillDamage(source, attackingPawn, spaceDamage, indexes, targetPawn, currentDamage)
    if source == self.SOURCE_ATTACKER and spaceDamage.iDamage > 0 then
        return currentDamage + 1
    end
    return currentDamage
end

SkillEffectEvaluated

SkillEffectEvaluated(phase)

Optional. Called after all SpaceDamages in a pass have been processed by all modifiers.

Returns: nil, a single SpaceDamage, or an array of SpaceDamage to append. Derived effects are modifier-processed again until the chain settles (see DamageModifierLib settle before add behavior). Avoid infinite loops.

function mySkill:SkillEffectEvaluated(phase)
    -- After seeing the full pass, queue a follow-up effect
    return SpaceDamage(Point(0, 0), 1)
end

setupEffect()

Optional override. Inherits from SkillActive. Use this if you need to register additional event handlers beyond weapon modification.

Example:

function mySkill:setupEffect()
    -- Call parent to register with weapon modification system
    cplus_plus_ex.baseClasses.SkillEffectModifier.setupEffect(self)
    
    -- Add custom event handlers
    table.insert(self.events, modApi.events.onMissionStart:subscribe(function()
        LOG("Mission started!")
    end))
end

Helper Methods

:getPawnAt(loc)

Gets the pawn at a location, accounting for pending moves tracked by DamageModifierLib during the weapon preview. Prefer this over Board:GetPawn when checking tiles during skill builds.

Parameters:

  • loc (Point): Location to check

Returns: Pawn at that location, or nil

Example:

function mySkill:modifySpaceDamage(source, attackingPawn, phase, spaceDamage, indexes, targetPawn)
    for dir = DIR_START, DIR_END do
        local adjLoc = spaceDamage.loc + DIR_VECTORS[dir]
        local adjPawn = self:getPawnAt(adjLoc)
        if adjPawn and adjPawn:IsEnemy() then
            -- Found an adjacent enemy
        end
    end
end

Note: You usually don't need to call this directly since targetPawn is provided. Use it when checking tiles other than spaceDamage.loc.

:getPawnSpace(pawn)

Returns the current tracked board space for pawn during a skill build (DamageModifierLib move tracking). Useful when the pawn has already moved as part of the effect.

Parameters:

  • pawn (Pawn): Pawn to resolve

Returns: Point (or lib-defined equivalent) for the pawn's tracked space

Constants

Source Constants

Indicate which pawn has the skill:

  • SkillEffectModifier.SOURCE_ATTACKER = "attacker" - Attacking pawn has the skill
  • SkillEffectModifier.SOURCE_TARGET = "target" - Target pawn has the skill

Phase Constants

Indicate the current weapon preview phase. These live on DamageModifierLib, not on SkillEffectModifier:

  • cplus_plus_ex.damageModifierLib.PHASE_NONE = 0
  • cplus_plus_ex.damageModifierLib.PHASE_SKILL_EFFECT = 1
  • cplus_plus_ex.damageModifierLib.PHASE_TARGET_AREA = 2
  • cplus_plus_ex.damageModifierLib.PHASE_QUEUED_SKILL = 3
  • cplus_plus_ex.damageModifierLib.PHASE_SECOND_TARGET_AREA = 4
  • cplus_plus_ex.damageModifierLib.PHASE_FINAL_EFFECT = 5
  • cplus_plus_ex.damageModifierLib.PHASE_QUEUED_FINAL_EFFECT = 6

SkillEffectModifier only defines source constants (SOURCE_ATTACKER / SOURCE_TARGET).

Priority System

Skills are processed in order of their priority value (lower runs first). Default is 100.

This allows for predictable ordering and interaction of skills. For example this can allow all +dmg skills to run before any skill that multiplies damage.

Example priorities:

  • 50: Run early (before most skills)
  • 80: Before damage doubling
  • 100: Default (runs with most skills)
  • 180: After kill effects

Set priority when creating your skill:

local mySkill = cplus_plus_ex.baseClasses.SkillEffectModifier:new{
    id = "MySkill",
    shortName = "My Skill",
    fullName = "My Skill",
    description = "Does something",
    priority = 50  -- Run before other skills
}

Skill Definition Properties

Registering with SkillEffectModifier:new{}, you can include any of the standard properties as supported by Skill Registration . See that page for full details on supported properties.

How It Works Internally

SkillEffectModifier does not walk skill effects itself. Effect walking, push/move tracking, chaining, and Board:IsDeadly preview live in DamageModifierLib.

On setupEffect(), this class bridges CPLUS+ pilot-skill state into that lib by subscribing to:

  1. damageModifierLib.events.onSkillEffectModify -> calls modifySpaceDamage(...)
  2. damageModifierLib.events.onEvaluatingDeadly -> calls modifyKillDamage(...) (unless modifiesKillDamage = false)
  3. damageModifierLib.events.onSkillEffectEvaluated -> calls SkillEffectEvaluated(phase) and appends returned damages

Subscriptions use the skill's priority (lower runs first). clearEvents() unsubscribes those handlers and then clears SkillActive events.

You don't need these internals to use the base class; they explain why getPawnAt / settle-before-add behavior comes from the lib.

Common Pitfalls

  1. Checking source incorrectly: Always check if source == self.SOURCE_ATTACKER or source == self.SOURCE_TARGET to know which pawn has the skill.

  2. Not checking damage type: Check spaceDamage.iDamage > 0 and spaceDamage.iDamage ~= DAMAGE_ZERO to avoid processing non-damage effects.

  3. Forgetting nil checks: targetPawn can be nil if there's no pawn at the damage location. Always check before using it.

  4. Using Board:GetSpace: This will not account for pushed pawns

  5. Priority matters: If your skill depends on another skill's modifications, set appropriate priorities.