Files
healer-man/docs/ROM_NATIVE_HEALING_SOURCE_2026-10-03.md
2026-10-04 13:13:35 -04:00

5.9 KiB

RoM native Priest healing source adapter

This is a local RuneWaker source implementation for native RoM healing. It is not a Chronicles of Arcadia formula, and it does not infer a spell rank from a source catalog value. MagicProc.MagicLv is a required runtime input. The simulator may explicitly map its trained skill level to clamp(characterLevel, 1, 80); that is simulator policy, not a claim about the source database's configured MagicLV field.

Direct HolyHeal

romNativeHealing.ts resolves the MagicProcess.cpp AtkCalType::HolyHeal branch. The source equation is:

OrgPower = DmgPower + DmgPower * DmgPower_SkillLVArg * MagicLv / 100
SpellTime = MagicCol.SpellTime; use 1 when below 1 or IsFixDmgEffect is true
MNDPart = MND * 0.3 * SpellTime
Power = OrgPower * 6 when OrgPower * 5 < MNDPart; otherwise OrgPower + MNDPart
unless IsIgnoreDmgEffect:
  add MDMG * FIX.Dmg_HealArg, once when fixed or multiplied by SpellTime otherwise
Power = Power * MPow * HealPowerRate + HealPoint * SpellTime
per target, apply Atk.Rand variation
unless MagicBase.SettingEx.IgnoreMagicAbsorb:
  Power = Power * (1 + HealAbsorbRate / 100) + HealAbsorb
clamp Power below zero; reverse sign for undead; suppress negative undead output out of combat

The generated source map binds ability and rank identity to raw MagicCollect rows and direct HP MagicObject components. It preserves source component IDs, AtkCalType, signed DmgPower, its skill-level argument, fixed fields, random range, critical rate, SpellTime, range/sharing data, and the distinct MagicCollect timing flags and mode flags. holyHealBindings is the 18-row subset with AtkCalType == 5; attackBindings retains all 225 direct HP ability rows for shared combat integrations.

The Priest bindings include Heal (490261, DmgPower 90, argument 30, SpellTime 2), Group Heal (490281, DmgPower 65, argument 20, SpellTime 3), and Urgent Heal (491147, DmgPower 35, argument 20, SpellTime 1). Their exact component and rank records are in the generated metadata. The helper expects already-effective native caster and recipient channels; callers must not run generic healing modifiers a second time when those modifiers were included in the effective inputs.

Periodic HP healing

Runewaker has a separate native HoT path. It is not a HolyHeal cast repeated each tick. MagicProcess.cpp computes the applied buff duration and invokes CalAddDotMagicLv; RD_Normal.cxx calculates the buff's source rank correction; RD_ValueCal.cxx::BuffProc uses the frozen Buff.Power at each tick. The helper supports active positive magical DotMagic with HP type 0, including Priest's Regenerate (490269). It deliberately returns no result for unsupported DotMagic types, including percentage HP type 5.

At application, the source first derives actual buff duration from EffectTime, EffectTime_SkillLVArg, DLV_EffectTime, and owner/target levels, then uses DotTimes = max(1, int(EffectTime / Dot.Time)). For a positive magic HP dot, the source calculates:

DotBase = Dot.Base * (1 + MagicLv * DotSkillLVArg / 100)
ExRate = 0.6 + abs(DotBase) / 600 for an IsPlayer owner; otherwise 1
Power = DotBase + ExRate * MDMG * int(SpellTime) * FIX.Dmg_HealDotArg / DotTimes
MNDPart = MND * 0.3 * int(SpellTime) / DotTimes
Power = Power * 6 when Power * 5 < MNDPart; otherwise Power + MNDPart
Power = Power * HealPowerRate + HealPoint * int(SpellTime) / DotTimes
unless IgnoreMagicAbsorb:
  Power = Power * (1 + HealAbsorbRate / 100) + HealAbsorb / DotTimes
clamp Power below zero; Power *= MagicPow(MagicType)
MagicLvAdjustment = int((Power - Dot.Base) / (Dot.Base * DotSkillLVArg / 100) - MagicLv)
Buff.Power = MagicLvAdjustment + MagicLv

FixDotPower or a zero DotSkillLVArg bypasses the rank correction, leaving Buff.Power = MagicLv. The source tick then computes Damage = int(-Dot.Base * (100 + DotSkillLVArg * Buff.Power) / 100) and applies AddHP(-Damage). C++ integer conversion truncates toward zero. Recipient absorb and caster channels are captured through the buff-rank correction at application; the source does not reapply them on each tick. The helper returns the resulting integer tick amount and the DotTimes interval count so the runtime can preserve that application snapshot.

The live helper requires an explicit effective MagicPow(MagicType) value. The existing native stat sidecar has an all-magic neutral MPow channel but does not expose all six element-specific MPow BodyRate channels. The caller must supply an effective source value when element-specific modifiers are modeled. For this simulator's active player-build identities, sourceIsPlayer=true is an explicit policy applied to both the active player and party companions; the original IsPlayer() test itself distinguishes actual server player roles.

The current Priest Regenerate source row contains positive HP DotMagic components 500469 (argument 18), 621318 (argument 18), and 621548 (argument 25). They share Dot.Time 2, Dot.Base 20, and duration 20 seconds. The generated map retains each component ID so the runtime can use the component selected by its own source semantics instead of guessing which raw component is active.

Source snapshot and limits

The generated artifact is src/game/generated/romNativeHolyHealBindings.generated.json; regenerate it with scripts/runewaker-pipeline/export-rom-native-healing.mjs. It is tied to the current hashes of magiccollectobject.db, magicobject.db, the local server formula files, the player catalog, and generated native stat rules. The output includes 225 direct-attack ability rows, 18 HolyHeal ability rows, and 8 positive magical HP DotMagic ability rows across the catalog.

The data and formula boundary is the local RuneWaker source version hashed in that artifact. This work does not add CoA rank data, model percentage HP periodics, or infer unavailable source skill levels from configuredCollectMagicLevel.