- Shared chest: tabs "Truhe"/"Gemeinsam" as a column left of the grid (game tab sheet), grids placed every tick, persistent across heroes - Inventory tab texts capitalized (RegisterText / CapitalizeText) - Combat-art runes stack like potions (itemstack, renamed from potionstack) - Rune master: any rune for any number of runes, price by rune count (4 runes = 1,000, x10 per missing rune), bought rune goes into the backpack, Buy disabled without runes, gold or room, last rune remembered per hero (saved), the chosen rune's name below its icon, dropping a stack gives up one rune - Keys: the old key handling no longer takes over after the save screen (text field focus only counts while its dialog is shown) - Esc crash: tooltips forgotten when the game destroys them - Pick up (Q) sends the pickup order directly; search reach doubled - Kettenblitz: tooltip shows hits per chain, chains, cast and jump range; cast range 900 (longer ray segments), jumps half of that Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
94 lines
4.8 KiB
Markdown
94 lines
4.8 KiB
Markdown
# 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.
|
||
|
||
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").
|