Files
sacred_k/docs/chain-lightning.md
T
khallaandClaude Opus 5.5 5d80a33e9b Blitzschlag: chains of one cast take separate routes
The chains of a cast share the enemies they have claimed and prefer
unclaimed ones. Each chain keeps its heading and ranks candidates by
distance and turn; a chain's first jump turns away from the directions
the other chains set out in, so they keep fanning out. Jumps reach 75 %
of the cast range.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 03:44:16 +02:00

116 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Chain lightning (`src/mods/chainlightning.cpp`)
Rebalancing request (user, 2026-10-15) for the Battlemage's Blitzschlag (art
0x21, icon `GUI_SPELL_BLITZSCHLAG`; 0x0e has the same icon):
- hits per chain N = 3 + level / 5 (the first target included);
- chains B = 1 + level / 10, all starting at the first target
("hero -> T1 -> A1 -> A2 ..., T1 -> B1 -> B2 ...");
- a chain that finds no new target hits the previous one again (a lone enemy
takes N hits).
## How the game casts it (measured with an object / hit log, 2026-10-15)
About 0.3 s after the cast order (0x105, art 0x21) the caster's art code
(`0x5222c0`, Blitzschlag part around `0x526c91`..`0x527102`):
- builds the visual path: up to 20 segment objects of type 799 (0x31f),
each created linked to the previous one (`CreateAt` third argument), ids
kept at caster +0x263;
- creates the bolt object, type 872 (0x368), at the target, linked to the
last segment, copies the caster's +0x2b5 to its +0x39, and sends it a
cEventFX 0x60 (`0x41e0a0`: [2] caster id, [3] target id, [4..7] position,
[8..13] two strings, [14] damage, [16..29] damage block);
- plays sound 0x7db.
The bolt object then sends the hit (`cEvent_creature` 5 from `0x5b52d3`,
attacker = caster, +0x14 = 0x368, +0x18 = the bolt object's id).
## The game's own chain (measured 2026-10-15)
The game's Blitzschlag already chains. The 0x60 event's [14] is the number of
hits left in the chain (3 at level 1, from the art level, `0x4d24f0`). After
its hit, a bolt with hits left looks for the next target in its update
(`cObjectManager::FindJumpTarget` `0x6005d0`, called at `0x5b469f` with
(position, 100000 = squared range, caster, current target): the nearest
creature, excluding only caster and current target), builds the arc (segment
objects 799 created at `0x5b4aff`, each set up with a cEventFX), and creates
the next bolt at the target (`0x5b4d7c`) with [14] - 1. No target: the chain
ends (a lone enemy is hit once). Two enemies: it bounces between them.
## The mod
- The first bolt of a hero's cast (the first 0x60 bolt setup after the cast
order) gets [14] = N = 3 + level / 5 (level = cooldown entry level + bonus
levels of the art cast).
- The jump search call (`0x5b469f`) goes to `NextTarget` (thunk passing the
bolt in ebp): for bolts of our chains (bolt id -> chain, filled when a bolt
of a chain is set up at the target the chain said it jumps to) the next
target is the nearest enemy within the game's range not hit by this chain
yet, else another enemy, else the current one again (a lone enemy takes all
N hits). Other bolts use the game's search.
- B = 1 + level / 10 chains: while the first bolt is set up, B - 1 more bolts
are created at the first jump targets of the other chains (copies of the
event with [14] = N - 1); their first hop has no arc.
Separate routes (2026-10-18). Request: the chains should not all follow the
same route. Before, each chain only avoided the enemies it had hit itself, and
only the extra chains' first targets differed. After that, parallel chains
picked the same nearest enemies and could swap onto each other's route. Now
the chains of one cast share a list of claimed enemies (the first target, and
every target as soon as a chain picks it). A jump prefers an enemy no chain
of the cast has claimed, then one this chain has not hit, then any other,
then the current one.
Follow-up (2026-10-18): "only on the first spread, then they most times merge
together again". The nearest free enemy of two chains often lies in the same
group. Each chain now keeps a heading (caster -> first target for the main
chain, then the direction of its last jump), and the candidates of a tier are
ranked by distance × (1 + 2 × turn), with turn = 0 straight ahead and 1
straight back. A chain's first jump has no heading yet; there the turn is the
largest closeness to the directions the cast's chains set out in, so the
branches fan away from the main chain and from each other.
First version (bolts copied for every jump, not using the game's chain)
replaced the same day: it doubled the jumps.
## Jump range = cast range (2026-10-16)
User request: a jump reaches as far as a cast. Blitzschlag's hit check is in
the art code (cCreature art switch `0x5222c0`, case 0x21): the ray from the
caster towards the target is laid out in segments of 30, at most 20, each on
passable ground (`0x636b60`); the bolt is created on the target only if the
ray reaches it, otherwise the visible ray just stops. So the cast range is
20 × 30 = 600. `CastRange()` in `chainlightning.cpp` returns it for every jump
(also the first hop of extra chains), instead of the game's jump search radius
(squared 100000, about 316). It is the one place to make the range dynamic
later. Jumps do not check the ground in between.
## Tooltip (2026-10-17)
Request: the tooltip should match the mod. The game's text (cCreatureArts::ArtTooltip
0x589270, measured for art 0x21 at level 5) ends each level section with
"Anzahl: X", the game's own hit count. The mod replaces that line with
"Treffer je Kette" (3 + L/5), "Ketten" (1 + L/10) and "Sprungweite" (the cast
range), with L = the hero's level (base + bonus) in the "Aktuelle Stufe" section
and L + 1 in "Nächste erlernte Stufe". The string is rewritten with the game's
append (0x592ef0), so the game's allocator owns the buffer.
## Range +50 % (2026-10-17)
Request: Kettenblitz range about 50 % larger. The ray's 20 segments are
bounded by the caster's segment id array (+0x263, 20 dwords), so the segments
get longer instead: 30 -> 45, cast and jump range 600 -> 900. Patched in the
art code: `fsub [30.0]` 0x526db6 and `fcomp [30.0]` 0x526e0b (redirected to
our 45.0; 0x88ef18 is shared by 72 instructions), `mov eax,30` 0x526e23 (the
segment length given to each segment object) and `push 30.0f` 0x526f3d (the
step of the ray point).
Follow-up (2026-10-17): jumps at 900 felt too far. The cast keeps 900; jumps
use `JumpRange()` = half the cast range (450). The tooltip shows both
("Reichweite", "Sprungweite").
Follow-up (2026-10-18): with several chains, half the cast range left too
few separate targets ("maybe chain range needs to increase to 75% of cast
range"). Jumps now reach 75 % of the cast range (675).