Skills, auto-attack, aura and chain lightning rework, Esc, cache fix
- Skill bar: no art selection; slot 0 is the right-click art, keys 0-6 cast slots directly; cooldown labels in exact seconds; slot 0 set apart. - Arts regenerate on their own (casting no longer refills the group). - Auto-attack after targeting or casting at a hostile creature, at the speed of a held button. - Auras can be recast while active (renew, no second visual). - Blicke wie Klingen hits every enemy in range, physical, with life leech. - Blitzschlag steers the game's own chain: 3 + level/5 hits, 1 + level/10 chains, re-hits a lone enemy. - Weapon sets: click selects, drag only from the selected set; the leftmost set is clickable (signed hit tests). - Esc closes open windows before opening the game menu. - HUD: standalone buttons and texts are centred. - Record cache sized for widened views (town lag while walking). - Shared game code: orders, fx events, text drawing, cooldowns, effects. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f721d2a486
commit
1fd89524d3
41 files changed
+2347
-214
No files matched your search
@@ -0,0 +1,32 @@
|
||||
# Art regeneration (`src/mods/artregen.cpp`, `game::GetArtCooldown`)
|
||||
|
||||
Goal (user, 2026-10-15): casting an art no longer affects the cooldowns of
|
||||
other arts; the cooldown labels show exact seconds.
|
||||
|
||||
## How the game regenerates arts (measured 2026-10-15)
|
||||
|
||||
Cooldown entries: see `src/game/engine/arts.h` (cCreatureArts +0x102,
|
||||
0x16 bytes each: group, art, level, bonus levels, total, speed factor,
|
||||
remaining). Found with a hardware write watch on one entry's remaining time:
|
||||
|
||||
- countdown, every creature update (`0x562998`):
|
||||
`remaining -= speed factor * rate * seconds`, rate = cCreatureArts +0xee
|
||||
(magic group, Energieblitze) or +0xf2; clamped to 0..total;
|
||||
- casting an art: the cast code computes the art's regeneration
|
||||
(`cCreatureArts::RegenFor` `0x582710`) and adds it with
|
||||
`cCreatureArts::AddGroupRegen` (`0x562330`, (amount, group, filter)) to
|
||||
**every** entry of the group (combat arts / magic), capped at each total:
|
||||
one cast puts the whole group into regeneration. Flags +0x148 & 8 / & 4 cut
|
||||
the amount to 80 % for the magic / combat group; Flammenwerfer, Meteor,
|
||||
Kanone and Energieblitze always count as magic. A combo (creature +0x2b4,
|
||||
arts +0x59) or group 3 takes another branch (combo slots at +0x10e);
|
||||
- the speed factor (+0xe) is reset by the stats recalculation (`0x579b7d`,
|
||||
`0x570fad`), the level byte +7 raised by item bonuses (`0x581e40`, which
|
||||
adds the change of the total to the remaining time).
|
||||
|
||||
## The mod
|
||||
|
||||
For player heroes, an AddGroupRegen call right after RegenFor (same thread)
|
||||
adds the amount to the cast art's entry only. Other calls (negative amounts,
|
||||
filters, combos, other callers) go to the game. Labels: seconds =
|
||||
remaining / (speed factor * rate).
|
||||
@@ -0,0 +1,44 @@
|
||||
# Periodic aura hits (`src/mods/aurahits.cpp`)
|
||||
|
||||
Rebalancing request (user, 2026-10-15): "Blicke wie Klingen" (art 0x3f8,
|
||||
`GUI_MOVE_KLINGENBLICK`) hit only one enemy per tick; it should hit all
|
||||
enemies in range.
|
||||
|
||||
## What the game does (measured with a hit log, 2026-10-15)
|
||||
|
||||
The aura gives the hero an effect of kind 0x12 (value 0x445f in the test).
|
||||
Its hits reach the enemies as `cEvent_creature` 5 (attacker id at +8, the
|
||||
damage at +0x18.. in one of four damage-type slots, chosen at random), sent
|
||||
from `0x56323d`, one enemy about once per second.
|
||||
|
||||
Code (creature arts update, branch `0x562f08`, only for effects of kind 0x12
|
||||
of the local player's creature):
|
||||
|
||||
- timer at effect +8 (a vector holding one int, ms); every 1000 ms:
|
||||
- walk the creatures near the player (`0xad1c60`, 8 bytes each, count
|
||||
`0xad1b48`); a creature within `(cCreatureArts +0x56 + 20) * 10` of the
|
||||
owner (`cCreature::DistanceTo` `0x536f20`) becomes the target if there is
|
||||
none yet, otherwise replaces it with 1/3 chance;
|
||||
- player heroes as targets go through PvP checks (`0x4d4be0`);
|
||||
- the hit: `cEvent_creature` 5 built on the stack, sent with the target's
|
||||
`ReceiveOrder` (vf18).
|
||||
|
||||
## The mod
|
||||
|
||||
`mov eax, [ecx]; call [eax+18h]` at `0x56323b` becomes a call of
|
||||
`HitAllInRange` (via a thunk passing ebp = cCreatureArts and ebx = the
|
||||
effect's offset): the chosen enemy gets its hit, then every other living
|
||||
creature of the nearby list within the same range gets a copy of the same
|
||||
hit. Player heroes are skipped (they need the PvP checks). Applies to every
|
||||
effect of kind 0x12, i.e. by behaviour, not by art id.
|
||||
|
||||
## Damage type and life leech (2026-10-15)
|
||||
|
||||
- The game puts the aura's value into one of the four damage slots at
|
||||
random (physical, fire, magic, poison), so a quarter of the ticks poisoned;
|
||||
the mod makes every aura hit physical (user's choice).
|
||||
- Life leech, its beam and sound are applied by the damage code (`0x56aff0`,
|
||||
leech block `0x5707a4`..`0x570a88`: property 0x349 x difficulty factor x
|
||||
damage x distance falloff, capped at max health) for the hit's attacker,
|
||||
order +0x1c. Weapon hits carry the hero's id there, enemy hits the enemy's,
|
||||
aura hits 0, so they never leeched. The mod sets it to the aura's owner.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Recasting active auras (`src/mods/aurarecast.cpp`)
|
||||
|
||||
Goal (user, 2026-10-15): auras / buffs can be cast again while active; the
|
||||
new cast renews the remaining time, it does not add to it.
|
||||
|
||||
## What blocks it (measured 2026-10-15)
|
||||
|
||||
Heldenmut (0x3f7) on the hero: effects kind 1 and 2 (the buff) and kind 0x22
|
||||
(the lock `cCreature::StartArtCooldown` 0x539ce0 adds), all with the aura's
|
||||
duration (66 s); its art cooldown (cArtCooldowns, 301.9 units) ran out while
|
||||
the aura still had 30 s left, and a cast order (0x105, art 0x3f7) was still
|
||||
ignored.
|
||||
|
||||
`cCreature::IsValidArtTarget` (`0x51d680`) switches on the art: 0x28, 0x29,
|
||||
0x41, 0x48, 0x3ed, 0x3f7, 0x3f8 and table entries for 0x3fa.. go to
|
||||
`0x51d8b1`, which rejects the target if it has any effect of the art
|
||||
(`cCreatureArts::HasEffectOfArt` `0x5829c0`, effect +0x1c). 0x3fa / 0x3fb
|
||||
and 0x446 have their own variants of the test right after it (not changed).
|
||||
|
||||
## The mod
|
||||
|
||||
- The `je` after that test (`0x51d8bd`) becomes a `jmp`: the target is valid
|
||||
whether or not the effect is active. The art's cooldown still applies.
|
||||
- `cCreatureArts::AddEffect` (`0x582c10`, (kind, ?, value, duration, art,
|
||||
network, ?, ?), ret 0x20) is hooked: for a buff art (launch type
|
||||
SelfOrAlly) on a player hero, if an effect of the same kind and art exists,
|
||||
its remaining time is set to the new duration and nothing is added. The
|
||||
value of the old effect stays (a recast after raising the art's level keeps
|
||||
the old strength until it runs out).
|
||||
- The aura visual is an effect object (cObjectFX, vtable `0x88f99c`) each
|
||||
cast creates and attaches with a cEventFX 0x42 (owner +8, art +0x3c; the
|
||||
object keeps the owner at +0x68). `cObjectFX::ReceiveEvent` (`0x5a37b0`)
|
||||
is hooked: when a new one attaches to a player hero for a buff art, the
|
||||
previous one for the same hero and art is flagged dead (object +0x14 bit
|
||||
0x10, as saving does). Untested whether the object manager removes it
|
||||
promptly.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Auto-attack (`src/mods/autoattack.cpp`)
|
||||
|
||||
Goal (user, 2026-10-15): once an enemy was targeted, by a left click or by
|
||||
casting an art at it, the hero keeps attacking it with the weapon between
|
||||
casts. Left click stays the normal attack; right click casts slot 0.
|
||||
|
||||
## What the game does (order log, 2026-10-15)
|
||||
|
||||
Orders logged in `cCreature::ReceiveOrder` (`0x52e400`) for the hero, with
|
||||
the hero's fields per tick:
|
||||
|
||||
| Order | Caller | Fields |
|
||||
|---|---|---|
|
||||
| left click on an enemy: `cmd 7` | `0x617abb` | +0x18 target id, +0x1c / +0x20 its position |
|
||||
| art: `cmd 0x105` | `0x6172f9` (`SendCreatureOrder`) | +0x10 target id, +0x18 / +0x1c ground point, +0x28 0x64, +0x2c 1, +0x30 art id |
|
||||
| walk: `cmd 4` | `0x6172f9` | see docs/wasd.md |
|
||||
| hit by an enemy: `cmd 5` | `0x4ea478` | +0x8 attacker id, +0x10 the hero, +0x2c 1.0f (every ~0.5 s) |
|
||||
|
||||
One left click is one swing: hero +0x150 goes 0x10000 (idle) -> 0x10003
|
||||
(attack) -> 0x20003 -> 0x10000 within about a second; +0xfc 0 -> 4 ->
|
||||
0x20004 -> 0 at the start. An art: 0x10001 -> 0x10008 -> 0x20008 -> 0 ->
|
||||
0x10000. Low word of +0x150: current action (0 none, 1 art, 3 attack, 6
|
||||
dying), high word its phase. `cCreature::IsAlive` (`0x548ef0`): +0xfc != 9,
|
||||
+0x150 != 6, health +0x4d8 != 0.
|
||||
|
||||
Holding the left button on an enemy (measured): one attack order, then +0x150
|
||||
stays 0x10003 for as long as the button is held (9 s in the test), no further
|
||||
orders. At the end of each swing (after the hit, `0x4e6584`) the attack code
|
||||
calls `cMouseCursor::IsButtonHeld` (`0x655ab0`, cursor word bit 8) at
|
||||
`0x4e65c6` (attacker in esi, target creature in ebp; measured: the only
|
||||
caller answered "held" during a held attack): held -> the next swing starts
|
||||
right away, otherwise recovery (+0x152 = 2) and idle. (`0x4f665a`, a similar
|
||||
check in another creature update, is not reached by the hero.) Re-sending
|
||||
the attack order after the hero went idle took ~1.45 s per swing (0.36 s
|
||||
wind-up, 0.64 s attack, 0.43 s recovery).
|
||||
|
||||
## The mod
|
||||
|
||||
- An attack order or an art order whose target is hostile
|
||||
(`cCreature::IsHostileTo` `0x548dd0`, faction relation) sets the combat
|
||||
target. (First version: "not an ally", which included NPCs: clicking a
|
||||
trader made auto-attack talk to it again and again, reopening its window
|
||||
with new stock.) any walk order (mouse, WASD, stop) clears it, as
|
||||
does the target's death.
|
||||
- Each engine tick, if the hero is idle (+0x150 low word 0), no mouse button
|
||||
is held (engine +0x54 & 0x10), input is not blocked (0x4000) and no skill
|
||||
key is held, it sends the attack order a left click sends. After sending it
|
||||
waits up to 5 ticks for the swing to start.
|
||||
- The end-of-swing button check (`call` at `0x4e65c6`) is redirected: it
|
||||
also answers "held" when the hero hits its auto-attack target, the target
|
||||
is alive and no skill key is held. Swings then chain like
|
||||
with a held button; the tick only restarts the attack after casts.
|
||||
- Self / ally arts pointed at an enemy lose their target
|
||||
(`src/mods/arttarget.cpp`) and therefore do not start auto-attack.
|
||||
@@ -0,0 +1,57 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Esc closes windows first (`src/mods/escape.cpp`)
|
||||
|
||||
Asked 2026-10-15: with a dialog open, Esc should close it instead of opening
|
||||
the game menu.
|
||||
|
||||
## The game (`cUI_Manager::HandleEsc` 0x756ba0, measured 2026-10-15)
|
||||
|
||||
On an Esc key event the UI manager (`0x757544`) calls HandleEsc. It knows its
|
||||
windows by slot (cUI_Manager dwords): 0x20 taskbar, 0x21 inventory, 0x22
|
||||
equipment, 0x23 blacksmith, 0x24 merchant, 0x25 megamap, 0x26 overview map,
|
||||
0x27 mercenary (party panel), 0x28 stats, 0x29 console, 0x2a diary (quest
|
||||
book), 0x2b savegame, 0x2c ESC menu, 0x2d purchase, 0x2e master, 0x2f options,
|
||||
0x30 chest, 0x31 horse, 0x32 network info, 0x33 net portrait, 0x34
|
||||
character, 0x35 cube. It closes the ESC menu, the megamap, the quest book, or
|
||||
(with 0x21/0x23/0x24/0x2e/0x30 open) the inventory; hides 0x2b / 0x2f;
|
||||
otherwise opens the ESC menu. With a trader open only the inventory closed,
|
||||
and the trader opened it again.
|
||||
|
||||
Closing a window: a cEventUI2 (vtable 0x88edd0, 0x80 bytes: +4 code 0x12,
|
||||
+8 subtype 5 = hide, names via 0x5904e0: sender at +0x28, window at +0x48 as
|
||||
measured in the commands the game sends when a window is closed by its
|
||||
button or hotkey; a string at +0x68, 0x423920 / 0x416660) sent to the UI
|
||||
manager's event handler (vf00, 0x757120), which routes it by name. Sent to
|
||||
the window's own vf10 it was not understood (first version).
|
||||
|
||||
## The mod
|
||||
|
||||
HandleEsc is hooked: unless the ESC menu is open, every visible window of a
|
||||
table (slot, name) gets that hide command, and Esc does nothing else. If none
|
||||
is open, the game handles Esc as before.
|
||||
|
||||
Not covered: dialogs that are not one of these windows, e.g. quest dialogs
|
||||
(standalone buttons and texts in the UI manager, docs/hud.md).
|
||||
+12
@@ -58,3 +58,15 @@ copying and writing back hold the real object's lock).
|
||||
A primitive drawn inside a moved window that covers the whole 1024x768 layout
|
||||
(e.g. the dimming behind the exit confirmation) is stretched across the render
|
||||
width instead of shifted.
|
||||
|
||||
## Standalone controls (quest dialog)
|
||||
|
||||
Some dialogs (e.g. accepting a quest) are not one of the hooked window
|
||||
classes: their buttons and texts sit directly in the UI manager as top-level
|
||||
controls (measured: the clicked button's root window was the cUI_Button2
|
||||
itself). They were drawn unshifted or with whichever window's scope happened
|
||||
to be active, so the dialog jumped between the 1024 layout and the centre and
|
||||
clicks only hit at the unshifted position. `cUI_Button2`, `cUI_Static2`,
|
||||
`cUI_StaticText64`, `cUI_StaticText64FX` (render and event) and
|
||||
`cUI_TextLines` (render) are hooked as centred windows too; inside other
|
||||
windows they are in that window's scope already (outermost wins).
|
||||
@@ -0,0 +1,20 @@
|
||||
# Performance
|
||||
|
||||
## Record cache in widened views (`src/mods/recordcache.cpp`)
|
||||
|
||||
Measured 2026-10-15 (Battlemage save in a town, frame time at Flip, sampling
|
||||
profiler over all threads): walking took 90-130 ms per frame in the widened
|
||||
view, 17 ms with widescreen off; idle 17 ms in both.
|
||||
|
||||
The render thread spent most samples in a std::map walk (0x832b80 /
|
||||
0x832ba0, called from 0x636222) inside FUN_006360e0: a record-by-id cache
|
||||
(map at +0xde5c, size at +0xde60, 16-byte value at node +0x14, last-used
|
||||
stamp at node +0x24 from 0xcd59d0; misses read the record from the file at
|
||||
+0x23c). With 4096 entries or more, every insert walks the whole map to
|
||||
erase the single least recently used entry. The widened view needs more
|
||||
records than that, so walking evicted and reloaded constantly.
|
||||
|
||||
Fix: the limit (`cmp ecx, 1000h` at 0x636201) is raised to 0x8000 so the
|
||||
per-insert walk never runs; before a lookup with 0x6000 entries the mod
|
||||
erases the less recently used half in one pass (map::erase 0x63da10). Walking
|
||||
in town: 17 ms per frame again (confirmed by the user).
|
||||
+47
-1
@@ -115,6 +115,51 @@ the 1024 frame (66-78 px each side). In the widened view the centred bar has
|
||||
room for that (16:9: 1365 wide, 170 px each side). Decision 2026-10-14: keep
|
||||
the original spacing; 4:3 is not a target (cut off there).
|
||||
|
||||
### Art slots without selection (2026-10-15)
|
||||
|
||||
Decision: art slots are no longer selected. Slot 0 (leftmost) is always the
|
||||
right mouse button's art; left click stays the normal attack. Keys 0-6 cast
|
||||
slots 0-6 directly (`src/mods/skillkeys.cpp`): key 0 presses the right
|
||||
button, keys 1-6 select their slot for as long as the key is held and press
|
||||
the right button. Without a held skill key the selection returns to slot 0
|
||||
(engine tick). `cUI_Taskbar2::SelectSkillSlot` turns every other request
|
||||
(clicks on the bar, wheel, COMBINE_SLOTS, slot count check) into slot 0;
|
||||
slots on the bar are only filled by drag & drop. Weapon sets are still
|
||||
selected (F1-F7, clicks).
|
||||
|
||||
How a slot is drawn (`RenderArtSlots` `0x6e31e0`): each slot is a
|
||||
`UI_STATIC` control drawn by its vf14; the function swaps the control's atlas
|
||||
entry and rect, draws, and restores them. An art has 3 icons (indices into
|
||||
`g_uiAtlasEntries` `0x9eaee0`, 0x54 each): ready + selected slot uses the
|
||||
third, otherwise the first; while recharging, the slot is drawn twice, split
|
||||
vertically by the ready fraction (first icon above, second below). Icons
|
||||
0x276-0x278 replace them when the art cannot be used mounted and
|
||||
`0x54fa90` holds (fill from creature +0x4e8 / +0x4ec). Over the icon a frame
|
||||
is drawn: `g_uiTaskbarArtFrameSelected` `0x9f4be4` / `g_uiTaskbarArtFrame`
|
||||
`0x9f7f14`, `0x9f5d9c` as a drop target, `0x9f696c` with creature flag
|
||||
0x2000. `UpdateSlotHighlight` (`0x6df9a0`) sets the slot backgrounds
|
||||
(`0x9ed084` selected / `0x9ed030`). Patched (`kTaskbarSkillSelectionLook`):
|
||||
frame and background test "slot 0" instead of "selected slot", and every
|
||||
ready art uses its third icon. Slots 1-6 are moved right by half a slot
|
||||
(0x21) after the layout, and the decorative strip right of the centre starts
|
||||
that much further right (`add ecx, 23Ah` at `0x6e01cc`).
|
||||
|
||||
Weapon set clicks (2026-10-15): with an empty cursor, the taskbar's mouse
|
||||
handling jumps to `0x6e10d8` (`je` at `0x6e0ca7`; taskbar in esi, inventory
|
||||
in ebp, set at [esp+0B8h], hand at [esp+0BCh]), which takes the set's weapon
|
||||
onto the cursor (`TakeWeaponSetItem`) and re-selects the selected set. That
|
||||
jump now goes through `WeaponSetClickThunk`: a click on a set that is not
|
||||
selected only selects it (exit `0x6e124d`, al = 1); dragging starts from the
|
||||
selected set only.
|
||||
|
||||
Negative x (2026-10-15): with 7 slots the leftmost weapon set (set 1, F1)
|
||||
lies at x = -68..-2 of the layout. The taskbar's own hit test accepts it,
|
||||
but every place that asks the slot controls (`vf1c`, signed compares) passes
|
||||
the position as `and reg, 0FFFFh`, so -43 became 65493 and the set could
|
||||
not be clicked. `kTaskbarSignedHitTests` sign-extends them (slot lookups
|
||||
`0x6dfcfe`/`0x6dfd5e`, the click loops `0x6e1a0c`/`0x6e1a5c`, drop and
|
||||
drag `0x6e1e8e`.. `0x6e20f2`).
|
||||
|
||||
The two 5 x 0xbc blocks at +0x258 / +0x604 are the belt (3D item views), not
|
||||
slots. Still to map: rendering of the slot contents (icons, cooldowns,
|
||||
`Render` `0x6e3ac0`), drag & drop onto slots (`HandleEvent` mouse branch,
|
||||
@@ -142,4 +187,5 @@ only (or disabled in multiplayer).
|
||||
(agreed 2026-10-14).
|
||||
5. Taskbar: 7 controls each (arrays inside the object are fixed at 5: own
|
||||
table for the extra controls), layout, rendering, hit tests, drag & drop.
|
||||
6. Keys: 1-7 select and cast art slots, F1-F7 select weapon sets.
|
||||
6. Keys: 0-6 cast art slots (since 2026-10-15; before: 1-7 select and
|
||||
cast), F1-F7 select weapon sets.
|
||||
@@ -8,6 +8,95 @@ recreates it after a load for Heldenmut (0x3f7) and Klingenblick (0x3f8). Spell
|
||||
auras (Steinhaut, Lichtschild, ...) use other effects and are not covered
|
||||
yet; check them if their visuals go missing too.
|
||||
|
||||
## Data-driven arts instead of hardcoded art ids
|
||||
|
||||
Asked 2026-10-15: naming single arts as constants (`kArtHeldenmut`,
|
||||
`kArtFlammenwerfer`, ... in `src/game/engine/arts.h`) does not scale. Mods
|
||||
should decide by an art's properties, read from the game's own data:
|
||||
|
||||
- the definition tables `g_spellDefs` (`0x950a38`, 71 x 0x94) and
|
||||
`g_combatMoveDefs` (`0x94ddb0`, 95 x 0x78): id, icon name, launch type,
|
||||
usable mounted, ... (`tools/arttable.mjs` dumps them; most fields unnamed);
|
||||
- map the remaining fields (regeneration group, lasting effect / aura,
|
||||
duration, ...) and expose them as an `ArtInfo` lookup in `src/game/`;
|
||||
- where the game itself hardcodes ids (e.g. the arts that always count as
|
||||
magic in `cCreatureArts::AddGroupRegen`, the "already active" switch in
|
||||
`IsValidArtTarget`), keep one table of those ids with the reason, instead of
|
||||
named constants spread over the mods;
|
||||
- optionally an own data file (JSON next to the DLL) for mod-side settings per
|
||||
art, if we need values the game does not have.
|
||||
|
||||
## Art ranges: scaling and indicators
|
||||
|
||||
Asked 2026-10-15 (after making periodic aura hits strike everything in
|
||||
range, docs/aura-hits.md):
|
||||
|
||||
- art ranges should scale with the art's level, and possibly with new
|
||||
attributes;
|
||||
- possibly show a range indicator (e.g. a ring around the hero for auras, or
|
||||
at the cursor for aimed arts).
|
||||
|
||||
Known so far: the periodic-hit aura (effect kind 0x12) uses
|
||||
`(cCreatureArts +0x56 + 20) * 10` world units, measured from the owner with
|
||||
`cCreature::DistanceTo` (`0x536f20`); what +0x56 is (level? a stat?) is not
|
||||
mapped yet. Other arts' ranges are not mapped. Fits the data-driven arts
|
||||
item above (range as a property of an art, one place to compute it).
|
||||
|
||||
## Rename life leech to "omnivamp"
|
||||
|
||||
Asked 2026-10-15: life leech (property 0x349, "Lebensentzug") becomes
|
||||
"omnivamp". To do: the in-game texts (item property, tooltips, character
|
||||
window). The leech code is the game's (`0x5707a4`.., property 0x349, in the
|
||||
damage code `0x56aff0`); it credits the hit's attacker (order +0x1c). Omnivamp usually means healing from all damage,
|
||||
not only weapon hits: aura hits (docs/aura-hits.md) now name their attacker
|
||||
so the game applies it;
|
||||
other damage sources (spells, combat arts' extra hits, damage over time) may
|
||||
follow.
|
||||
|
||||
## Map and map zoom in widened views
|
||||
|
||||
Reported 2026-10-15: the map (and zooming it) does not support the widened
|
||||
render sizes yet (docs/widescreen.md, docs/hud.md: windows are laid out for
|
||||
1024 and centred). To check: the map window's draw area and the zoom's
|
||||
hardcoded 1024/768 (or 512/384 centre) values.
|
||||
|
||||
## Quest dialog glitches in widened views
|
||||
|
||||
Reported 2026-10-15: accepting a quest, the dialog window "kept glitching"
|
||||
and button clicks were not detected correctly. Probably the HUD layer
|
||||
(src/mods/hud.cpp: windows shifted while they render / handle events): the
|
||||
dialog may not be covered, or shifted twice. Details asked from the user.
|
||||
|
||||
## WASD walks around walls (caves)
|
||||
|
||||
Reported 2026-10-15: in caves WASD movement behaves "as if the player tries
|
||||
to find another way to the target location if a wall comes up". WASD sends a
|
||||
walk order to a point 300 world units ahead (docs/wasd.md); when that point is
|
||||
behind a wall the pathfinder plans a detour. Idea: find the game's straight
|
||||
line / walkability check and shorten the target when the line is blocked (or
|
||||
stop / slide along the wall). Related to the pathfinder item above.
|
||||
|
||||
## Esc closes an open dialog first
|
||||
|
||||
Asked 2026-10-15: with any dialog open (quest, trader, inventory, ...), Esc
|
||||
should close that dialog instead of opening the game menu (ESC menu,
|
||||
`cUI_EscMenu`). Esc is handled in the engine / UI manager hotkeys
|
||||
(docs/wasd.md: engine `0x618130`, UI manager `0x757120`).
|
||||
|
||||
## In-DLL smoke tests (later)
|
||||
|
||||
A test mode inside the mod DLL, started by an ini setting or command-line flag,
|
||||
so builds can be checked without play-testing and without driving the real
|
||||
mouse/keyboard like `tools/autotest-load.ps1`:
|
||||
|
||||
1. load a fixed test save;
|
||||
2. play scripted input through the existing hooks (keys, casting arts, weapon
|
||||
set switches, save + reload);
|
||||
3. take back-buffer screenshots at checkpoints and log checks to
|
||||
`sacred_re.log` (aura effect present after reload, slot 6-7 inventories
|
||||
survive a save, ...);
|
||||
4. exit the game.
|
||||
|
||||
## Better pathfinder (mouse clicks and monsters)
|
||||
|
||||
Replace the game's pathfinding with a more reliable and efficient one, for
|
||||
|
||||
Reference in new issue
Block a user