Features
- 'Large Combos' + large lifting
d9c1387490New General tab combo editor flags allow 'Large Combos', combining combos on the same layer that connect via these directions for certain features. Lifting can make use of this feature, allowing lifting groups of combos as single large objects.
- 'Large Combo' copycat triggers
96a121e6a6Allows combo triggers to interact with all combos in the same 'Large Combo' grouping as the combo being triggered. This allows ex. bombing one corner of a large rock to crack the entire thing.
- Weapon flag for carrying across screens
3065337e22weapons with this flag can be carried by the player across screen boundaries, even when QRs are set preventing carrying across screens normally.
- String flag 'No Freeze', makes a string NOT freeze action
0f28899d85 - Byrna invincibility
6f7f728c82 - Both invincibility-granting magic items can now prevent knockback
f0f80f2416If knockback is prevented, the player can ex. walk over spikes without being stopped / hit back.
- Handle armos/graves with 'Large Combos' flags
fd10f657a5
Editor
- Subscreen 'hint' draws
e9e00f57bfSome things that would normally be invisible will have a 'hint' draw to indicate where and what it is in the editor (ex. Button Items, Text with empty string, etc)
- Improve helptext for armos/grave combos
3e4ef75a3b - Combo trigger copy/paste uses system clipboard + json
fcd5ac70ce
ZScript
- JIT-compile shift and bitwise script commands
2bbac4c1d1The JIT now compiles the bit-shift, bitwise-NOT and bitwise-XOR operations natively instead of falling back to the interpreter for each one, speeding up scripts that do bit manipulation.
- Strength-reduce power-of-two multiplies in the optimizer
5a1c0b6650The optimizer now turns a multiply by a power of two into a left shift (reg * 2^k -> reg << k), which the JIT compiles to a single shift instead of a 64-bit multiply plus a fixed-point divide.
- JIT-compile array access and allocation
bf15ec043aThe JIT now natively compiles script array element reads/writes, array allocation, and bulk array initialization instead of falling back to the interpreter for each one, which avoids a register-cache flush at every array operation. Array-access-heavy scripts run measurably faster - stellar_seas_randomizer.zplay is ~5% faster.
- Collapse array constant-initialization in the optimizer
e7726d749eThe ZASM optimizer now folds a run of per-element array writes into a single bulk array write. Older quests initialize arrays as a long sequence of individual element writes; this rewrites them to the compact form the modern compiler emits, cutting instruction count (e.g. -11% of all instructions in the stellar_seas randomizer) and avoiding per-element overhead in both the interpreter and the JIT.
This sped up stellar_seas_randomizer.zplay by ~11%.
- Allow changing
SpawnScreenon most sprites9ffa09b9ddThis is useful for itemsprite->PickupExState,
npc->DeathExstate, etc. to allow scripts to set what screen the effect will run for, rather than it being tied to a specific screen based on the running script.Cannot be changed for the Hero or ffcs; the Hero just returns
Game->CurScreenfor this, which can't be modified, and the value for ffcs is directly tied to the screen they belong to, so modifying them is still disallowed. - weapon_data script access
b9768d1a3e
Bug Fixes
- Switchhook Block combo attributes overlapping
d7d543ac0fThe 'Item/Dropset' attribute was in the same spot as the 'Break SFX' attribute! The 'Item/Dropset' attribute has been moved to fix this. If you were using 'Item/Dropset' before, you'll want to update your Switchhook Block combos.
- [mac] size default window using the screen's usable area
57349fe4daThe default window size on macOS subtracted a hardcoded guess for the menu bar and ignored the dock, so the window could end up larger than the available space. It now uses the screen's actual usable area, which excludes both the menu bar and the dock, and reserves the window title bar at the correct size on high-DPI displays.
Player
- Hammer triggering combos outside its hitbox
676ea94bf4The hammer could trigger combos and secret flags above and behind the player, outside the area its hitbox was drawn. It was most obvious with the "don't trigger while the hammer is in the air" item flag enabled: on the first frame the hammer became active on the ground, it triggered combos where it had been a frame earlier, while still raised.
- Cleanup Cooldown behaviors for various itemclasses
f1db31e134- Cane of Byrna starts cooldown when the effect stops
- Remote Bombs start cooldown when detonating, not placing
- Melee Weapons handle cooldown properly
- Check bridges for standing-on-z state
3bf23e312b - Armos combos with custom enemies not clearing the combo
fb7462bcd9 - 'large' armos not detecting proper positions to change statues of
f3e92ba962 - Large armos bad tile offset
7a4e628a1b - Stop spinning tile from despawning in scrolling regions
a3137d6dddDiscord: https://discord.com/channels/876899628556091432/1518689226164670655
- Stop triforce pickup softlocking with low MP/HP counter settings
b5aabd34a9Picking up a triforce would freeze the game when MP per Block was set to 1-3 (or HP per Heart to 1). The refill animation never finished, so the pickup cutscene looped forever and quitting crashed.
Editor
- Enemy editor bad tile/hit size maximums
d2fb02b43d - Subscreen editor crashing when quest has 0 'Equipment Item's
111e36efea
ZScript
- JIT reading a function argument from the wrong stack slot
fa03288affA JIT-compiled script could read a function argument or local from the wrong stack location and get an incorrect value. In practice this showed up only when running with the ZASM optimizer disabled.
- Fix JIT-compiled script comparison using a stale result
31b76b41f3A comparison between a constant and a register could be evaluated incorrectly by the JIT when a value was waiting to be pushed onto the stack, which could send a script down the wrong branch.
- Prevent crash when taking the modulo of the minimum integer by -1
f4e0b0bd49Computing the remainder of the smallest representable integer modulo -1 crashed the game, in both the interpreter and the JIT. The result is now 0, the same as any value modulo -1.
- Script-created enemies using wrong SpawnScreen
a67adf1af7 - Correct script register deps and a crash reading an invalid sprite
5c63e6ce52Reading a sprite's no-collision timer from an invalid sprite could crash. Several sprite and combo-trigger fields were also missing internal reference information, which could cause scripts to be optimized incorrectly.
- Free script-owned objects when a script's state is cleared
7a11d0c790Resetting or clearing a script's engine data did not reliably release the script objects that the script had taken ownership of. Several script types that run in less common ways (like active subscreen scripts) could therefore leak these until the engine eventually ran out of slots.
Releasing owned objects is now an inseparable part of resetting or clearing a script's engine data, so it can no longer be skipped.
- For JIT,
Close()not closing subscreenab8ea41c54A subscreendata script that called Close() to close its own subscreen had no effect when scripts were JIT-compiled: the subscreen stayed open and the script kept running, diverging from the interpreter.
- Free objects owned by DMap and subscreen scripts
b0addbe539Objects and arrays that a DMap, OnMap, or active/passive subscreen script took ownership of were not released when the script's state was cleared, leaking them until the engine ran out of object slots.
- Correct script error stack traces under the JIT
22010b982cWhen a script logged an error while reading or writing an engine variable, the JIT reported the wrong source location in the stack trace - often a different function or even file than where the error actually occurred. These errors now report the same location as the interpreter does.
- Open debugger on top when opened from menu
ea8b82c166 - Make script debugger window readable on high-DPI displays
aa96a5496fThe script debugger's UI rendered at a fixed 1x size, so on Retina and other high-DPI displays the text and widgets were tiny and hard to read. The window is now scaled to the display's DPI: fonts are crisp, widgets and panes are proportioned correctly, the window opens at a sensible size that fits fully on screen, and moving it to a monitor with a different DPI rescales the UI.
- Bad error message for invalid call to
Game->LoadComboData()7a34f9c833
Web
- Prevent WASM JIT hanging on some older quests
5f74b174a6Playing certain older (2.55-era) quests in the web version could freeze on startup if JIT is enabled. The WebAssembly script compiler entered an infinite loop while running the quest's global init script.
Build
- Fix mac release
61c8fb7c88
Chores
- Update replay_uploads_known_good_replays.json
2cc6dedce8 - Slightly optimize string trigger code
26e784816c - Remove accidental copy/paste
d10dccc61b
Refactors
- Cleanup remote bomb code
02da4bc7a1 - Reduce player item usage code duplication
1b522760a2 - Guysbuf no longer uses malloc
658f5af21c
ZScript Standard Library (std.zh)
- Simplify VisibleOnDungeonMap
24ccfdf5c6 - Collapse many per-type overloads to sprite
1b6432a79effc/lweapon/eweapon/npc/item all extend sprite, so a single sprite overload replaces the dozens of near-identical per-type overloads.
Collapsed functions:
- CenterX
- CenterY
- ProximityX
- ProximityY
- HitboxRight
- HitboxTop
- LinkFacing
- RightOf
ZScript
- Update @versionadded
8903256105for GENEV_HEROHIT_INVINCIBLE, GENEV_HEROHIT_INVINCIBLE_KB
- Guard against self-deleting script commands the JIT ignores
1f7b6208c0Route the self-deleting interpreter commands (lweapon/eweapon delete, subscreen close) through the shared early-return path and assert there that the command is registered in command_could_return_not_ok. The JIT uses that list to decide whether to check a command's return code, so an unregistered self-deleting command is silently ignored under the JIT and diverges from the interpreter. The assertion catches that in tests.
- Log error if script_object_ref_dec fails
6168a69e9f
Tests
- Add replay test for large combo features
7dc75dc730 - Print number of frames and gameplay time when running replays
67782360ec - For .local, suggest the next release when replay fails
882c906b17 - Fix replay runner live updates with multiple clients
96f07217efupdate_views looped over the requested views but parsed the wrong variable, reusing the last connected client's view instead of the one being iterated. With more than one browser tab open, results views could get data derived from an unrelated view (or no updates at all).
- Avoid replay runner server deadlock on subprocess output
8768fa4c4cSubprocesses were created with piped stdout/stderr but awaited with wait(), reading the pipes only after exit. A child that fills the OS pipe buffer blocks on write while the server blocks on wait(), hanging the whole server. Drain the pipes with communicate() instead.
- Drop dead assignment when sorting replay runner results
99eda87fcalist.sort() sorts in place and returns None, so assigning its result clobbered a variable for no reason. Sort in place without the misleading assignment.
- Remove broken unused replay runner handler
cd9cc40bbbhandle_test_results_list_req was never registered as a route and would have crashed if called: it invoked .append on a Path object and reused the same variable for both the directory and the result list.
- Check the right websocket state before sending
f34b02f6a2update_view guarded on client.ws.status, which is not a meaningful open/closed indicator for a websocket response. Check not client.ws.closed instead so we skip clients that have disconnected.
- Avoid duplicate history entries in replay runner
8677034b96show() compared the full location.href (an absolute URL) against a path like "/new", so the equality guard never matched and every navigation pushed a redundant history entry. Compare location.pathname instead.
- Fix replay runner crash navigating back to initial page
54457185dfThe initial history entry has a null state, so the popstate handler threw when reading e.state.view. Fall back to location.pathname.
- Derive websocket scheme from page protocol
99fbc67eb1The replay runner hardcoded the ws: scheme, which fails when the page is served over https. Use wss: in that case.
- Make the replay runner web app pretty
55ceba7c0d - Make the web replay harness resilient to console-flooding replays
9e37037ff0 - Assert ZASM registers for arrays declare rINDEX dep
6d70bb2b61 - Add replay test for subscreen script object cleanup
3ef5f56103Validates that bitmaps an engine active subscreen script takes ownership of are released when the subscreen closes, by opening it repeatedly via a simulated Start press and asserting the bitmap pool is never exhausted.
- Print test results folder in run_replay_tests.py
654296e5bc
ZScript
- Run replays without the ZASM optimizer in CI
11e96118e7Some JIT bugs only surface when the ZASM optimizer isn't rewriting the bytecode first, because it can eliminate the very instructions that trigger them. Re-run some replays in CI with the optimizer disabled.
- run_replay_tests.py gains --optimize-ZASM/--no-optimize-ZASM, wired through replays.py to pass -no-optimize-ZASM to the player.
- ci.py / test.yml run the playground subset a second time with the optimizer off.
- Add weapon_data script access coverage
ca9c5add9bAdds a playground auto replay exercising the new weapondata script type: register round-trips, array members, CopyTo/ApplyTo, the buffer-backed itemdata and combodata sources, object identity and source deduplication, and lifetime invalidation when the source npc is removed or the screen changes.
Misc.
- Update 'trigger_example.qst'
ce7fa21378include example large liftables, large armos, and boss music triggers
ZScript
- Cleanup/clarify string related functions
f26f5e5799