@reldens/modifiers Quick Reference

Fast lookup for the @reldens/modifiers package: usage patterns, operations, comparison operators, state and type constants, common mistakes and debugging tips.

Package Structure

  • lib/modifier.js - Modifier class (apply, revert, conditions, limits).
  • lib/calculator.js - math operations and their inverses.
  • lib/condition.js - Condition class and comparison methods.
  • lib/property-manager.js - deep property access.
  • lib/constants.js - all constants (ModifierConst).
  • tests/unit/ - unit tests; tests/fixtures/test-helpers.js - shared mocks.

Quick Patterns

Basic modifier

const { Modifier, Condition, ModifierConst } = require('@reldens/modifiers');

let mod = new Modifier({
    key: 'attack-boost',
    propertyKey: 'attack',
    operation: ModifierConst.OPS.INC,
    value: 20
});
mod.apply(target);
mod.revert(target);

With conditions

let condition = new Condition({
    key: 'level-check',
    propertyKey: 'level',
    conditional: ModifierConst.COMPARE.GE,
    value: 10
});
let mod = new Modifier({
    key: 'bonus',
    propertyKey: 'strength',
    operation: ModifierConst.OPS.INC,
    value: 25,
    conditions: [condition]
});

Deep property access

let mod = new Modifier({
    key: 'nested-boost',
    propertyKey: 'stats/combat/attack',
    operation: ModifierConst.OPS.INC,
    value: 30
});

Base property operations

// read from maxHealth, apply to currentHealth
let mod = new Modifier({
    key: 'percentage-heal',
    propertyKey: 'currentHealth',
    basePropertyKey: 'maxHealth',
    operation: ModifierConst.OPS.INC_P,
    value: 50
});
mod.apply(target, true, false);

With limits

let mod = new Modifier({
    key: 'heal',
    propertyKey: 'health',
    operation: ModifierConst.OPS.INC,
    value: 100,
    minValue: 0,
    maxProperty: 'maxHealth'
});

String values

let statusEffect = new Modifier({
    key: 'poison-status',
    propertyKey: 'status',
    operation: ModifierConst.OPS.SET,
    type: ModifierConst.TYPES.STRING,
    value: 'poisoned'
});

Operations

ModifierConst.OPS, where a is the current value and b the modifier value:

  • INC (1) - apply a + b, revert a - b. Flat increase.
  • DEC (2) - apply a - b, revert a + b. Flat decrease.
  • DIV (3) - apply a / b, revert a * b. Division.
  • MUL (4) - apply a * b, revert a / b. Multiplication.
  • INC_P (5) - apply a + round(a * b / 100), revert round(a / (1 + b / 100)). Percentage increase.
  • DEC_P (6) - apply a - round(a * b / 100), revert round(a / (1 - b / 100)). Percentage decrease.
  • SET (7) - apply sets b, revert sets false.
  • METHOD (8) - custom method named by value, called with (modifier, currentValue).
  • SET_N (9) - same as SET (alternative id).

Comparison Operators

ModifierConst.COMPARE, used as the Condition conditional:

  • EQ = 'eq' - strict equal (===).
  • NE = 'ne' - strict not equal (!==).
  • LT = 'lt' - less than (<).
  • GT = 'gt' - greater than (>).
  • LE = 'le' - less than or equal (<=).
  • GE = 'ge' - greater than or equal (>=).

State Constants

Values stored in modifier.state:

  • MOD_MISSING_KEY = 'mk' - no key provided.
  • MOD_MISSING_PROPERTY_KEY = 'mpk' - no propertyKey provided.
  • MOD_MISSING_OPERATION = 'mo' - no operation provided.
  • MOD_MISSING_VALUE = 'mv' - no value provided.
  • MOD_READY = 'mre' - ready to apply.
  • MOD_APPLIED = 'ma' - applied successfully.
  • MOD_REVERTED = 'mr' - reverted successfully.
  • MOD_UNDEFINED_TARGET = 'mut' - no target object.
  • MOD_INVALID_CONDITIONS = 'mic' - conditions failed.
  • MOD_MISSING_CONDITION_INSTANCE = 'mmci' - a condition is not a Condition instance.
  • MOD_MODIFIER_ERROR = 'me' - modifier error (for example, METHOD without a valid method).

Type Constants

  • ModifierConst.TYPES.INT = 'int' (default, values converted with Number()).
  • ModifierConst.TYPES.STRING = 'string' (values converted with String()).

Base Property Combinations

Arguments apply(target, useBasePropertyToGetValue, applyOnBaseProperty):

  • false, false - read propertyKey, write propertyKey. Normal modification.
  • true, false - read basePropertyKey, write propertyKey. Percentage of max applied to current.
  • false, true - read propertyKey, write basePropertyKey. Current to base.
  • true, true - read basePropertyKey, write basePropertyKey. Base to base.

Limit Application Order

  1. minValue (if it is a number).
  2. maxValue (if it is a number).
  3. minProperty (if defined and the property value is not 0).
  4. maxProperty (if defined and the property value is not 0).

Example with a calculated value of 200: minValue: 0 leaves 200, maxValue: 150 clamps to 150, maxProperty: 'maxHealth' with a max health of 100 clamps again to 100.

PropertyManager Details

  • Paths use / as separator: stats/combat/attack.
  • extractChildPropertyOwner() returns the parent object of the final property, not its value: for stats/attack it returns player.stats.
  • Reading a missing final property throws an error.
  • Writing a missing final property creates it.
  • Every intermediate segment must exist, both for reads and writes.
const { PropertyManager } = require('@reldens/modifiers');

let propertyManager = new PropertyManager();
propertyManager.getPropertyValue(player, 'stats/attack'); // throws if stats.attack does not exist
propertyManager.setOwnerProperty(player, 'stats/attack', 75); // creates stats.attack if missing

Common Mistakes

1. Not setting basePropertyKey

// WRONG - basePropertyKey defaults to propertyKey, so this reads currentHealth
modifier.apply(target, true, false);

// RIGHT - set basePropertyKey explicitly
let modifier = new Modifier({
    key: 'percentage-heal',
    propertyKey: 'currentHealth',
    basePropertyKey: 'maxHealth',
    operation: ModifierConst.OPS.INC_P,
    value: 50
});

2. Property path typos

// WRONG - writing creates a NEW property stats.atk
propertyKey: 'stats/atk'

// RIGHT - use the existing property
propertyKey: 'stats/attack'

3. Type mismatches in conditions

// WRONG - player.level (number 10) === '10' (string) is false
type: ModifierConst.TYPES.STRING,
value: '10'

// RIGHT - matching types
type: ModifierConst.TYPES.INT,
value: 10

4. Forgetting to revert

// WRONG - the stats stay changed forever
modifier.apply(player);

// RIGHT - always pair apply and revert
modifier.apply(player);
// later, on unequip or when the effect ends:
modifier.revert(player);

5. METHOD declared on the prototype

The METHOD operation looks up the method as an own property of the modifier instance. Assign it in the constructor (this.myMethod = (modifier, currentValue) => ...) instead of declaring it only in the class body.

6. Numeric strings as limits

minValue and maxValue are only applied when they are numbers. Convert values coming from configuration before creating the modifier.

Debug Tips

Check the modifier state

const { ModifierConst } = require('@reldens/modifiers');

modifier.apply(target);
if(ModifierConst.MOD_APPLIED !== modifier.state){
    console.log('Modifier not applied, state:', modifier.state);
}

Test a condition

const { PropertyManager } = require('@reldens/modifiers');

let currentValue = new PropertyManager().getPropertyValue(target, condition.propertyKey);
console.log('Valid:', condition.isValidOn(target));
console.log('Target value:', currentValue, 'Condition value:', condition.value, 'Comparison:', condition.conditional);

Preview a calculation without writing it

modifier.target = target;
// calculated value with the limits applied, the target is not modified:
console.log('Calculated:', modifier.getModifiedValue(false, false));

Testing Commands

# run all tests
npm test

# run the test files whose path contains the filter
npm test -- --filter=calculator

# watch mode
npm run test:watch

# coverage
npm run test:coverage

Dependencies and Integration

  • Only dependency: @reldens/utils - ErrorManager (errors), Logger (logging) and sc Shortcuts (helpers).
  • Used by @reldens/items-system - item stat modifiers on equip and use.
  • Used by @reldens/skills - level modifiers, owner conditions, skill effects.
  • Used by the Reldens platform - character stats, equipment, buffs and debuffs.

Important Reminders

  1. basePropertyKey defaults to propertyKey - set it explicitly when you need it.
  2. extractChildPropertyOwner() returns the parent object, not the property value.
  3. Reads validate the final property; writes create it when missing.
  4. All conditions must pass - a single failed condition stops the modifier.
  5. Limits are applied after the calculation and before the value is written.
  6. Percentage operations round the result to avoid decimal drift.
  7. modifier.state tracks the lifecycle - check it when debugging.
  8. Modifiers are reusable - the same instance can be applied to several targets.

Related Documentation

Go Up