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

112 lines
5.9 KiB
Markdown

# 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:
```text
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:
```text
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`.