Lilium Dynamic Light

Lilium Dynamic Light is a vanilla-friendly addon that brings real-time dynamic lighting to players, mobs, and items.
Back to Files

Lilium Dynamic Light - V2.1.0.mcaddon

File nameLilium Dynamic Light - V2.1.0.mcaddon
Uploader
LiliumStudioLiliumStudio
Uploaded
Sep 21, 2026
Downloads
23.1K
Size
766.7 KB
File ID
8943140
Type
R
Release
Supported game versions
  • 26.50
  • 26.40
  • 26.30
  • 26.20
  • 26.13
  • 26.12
  • 26.11
  • 26.10
  • 26.3
  • 26.0.02
  • 26.0

What's new

Changelog - Lilium Dynamic Light

[2.1.0]

Added

  • Ignore List (Fixes the Aether Legends Portal Conflict): Any identifier can now be excluded from dynamic lighting entirely, from the Ignore List entry in the main menu or /scriptevent ldl:ignored. One list covers items, mobs and blocks, because a single source can be more than one of those: minecraft:end_crystal is the item in your hand and minecraft:ender_crystal the entity once placed, and ignoring only one of them leaves the other lit. RuntimeConfig.isIgnored() is checked at the top of all three resolver entry points — getItemLightLevel(), getEntityLightInfo() and getBlockLightInfo() — ahead of the user config, the ldl:light component, ldl:* tags and the vanilla tables, so an ignored identifier emits nothing no matter who declared it. Equipped items are covered through getEquippedLightInfo(), which resolves each slot with getItemLightLevel().

    This exists because an invisible light block is still a block. Any add-on that requires a specific space near a light source to stay air fails its own check, since ldl:light_block_* is a real custom block and isAir is false for it. Aether Legends is the case that surfaced it: its portal frame is built from minecraft:glowstone and its validator rejects the frame unless every cell of the 2x3 interior is air. Glowstone is a level-15 emitter in the catalog and the first entry of SEARCH_OFFSETS is the block directly above, so the light for a frame's bottom glowstone lands inside the portal and the portal can never be lit. Adding minecraft:glowstone to the list resolves it without giving up block lights everywhere else.

    Adding or removing an entry releases what is already lit immediately, rather than leaving it glowing until the next 100-tick block sweep. Eleven new translation strings ship in all 19 languages.

Changed

  • Block Light Precedence Now Matches Items and Mobs: LightResolver.getBlockLightInfo() resolved in the order component → tags → user config → vanilla tables, while items and mobs resolved user config first. A block carrying another creator's ldl:light component or ldl:* tags could therefore not be overridden from the Blocks menu, even though the equivalent item could — the entry was saved, the menu showed it, and nothing changed in the world. The block path is now user config → component → tags → vanilla, identical to the item and mob paths, so an entry added in the menu always wins.
  • Single Shared LIGHT_COLOR_SET: api/components.js, api/tags.js, modules/config.js and modules/color.js each imported LIGHT_COLOR_NAMES and built their own private new Set(...) from it, four copies of the same lookup table that had to stay in sync by hand. The set is now exported once from modules/catalog.js alongside the names array and imported where needed.

Fixed

  • Blank Menu Screen When Another Pack Ships Its Own _ui_defs.json: On a server with several resource packs the menu could open to nothing at all — the screen took over, the HUD disappeared, and no card was drawn. The custom menu was split across two files that are registered in different ways. ui/server_form.json replaces a vanilla file, so it applies by path alone; it hides the vanilla form whenever the title carries the ldl: prefix and inserts the factory that should draw the replacement. ui/ldl_screen.json was a new file, and existed only because ui/_ui_defs.json declared it. Bedrock does not merge _ui_defs.json across packs — the highest-priority one wins outright — so any other pack shipping its own list silently dropped ours. That left exactly the broken half applied: the vanilla form hidden, and the card meant to replace it never defined. Aether Legends is one such pack, listing nine files of its own and no server_form.json. Everything now lives in ui/server_form.json under the server_form namespace, which Bedrock loads through the vanilla path, and ui/ldl_screen.json and ui/_ui_defs.json are gone. There is no longer a file another pack can deregister, and no UI behavior changed for a world where it already worked.

  • 668 Missing Translation Strings Across 17 Languages: Two feature waves were never localized past en_US and es_ES. The Blocks menu (12 keys: ldl.blocks.*, ldl.addblock.title, ldl.msg.block*, ldl.placeholder.block, ldl.toggle.deleteBlock) was missing from 17 languages, es_MX included. Colored light and hand-swap mode (29 more keys: the 16 ldl.color.* plus ldl.color.auto, ldl.field.color, ldl.items.vibrant, ldl.msg.vibrant*, ldl.gesture.*, ldl.field.gestureMode, ldl.common.on/off) were missing from 16. A player on any of those languages saw literal keys such as ldl.color.magenta wherever those screens appeared. Every language now carries the full en_US key set, in the same order, with the wording derived from each file's own existing conventions, and the two new sections introduced by localized headers. The fourth Features toggle, ldl.feature.blockLights, was part of the same gap. The dead ldl.feature.gestures key, orphaned in every file since the gesture toggle became the ldl.field.gestureMode dropdown, was removed.

  • Owner Key Sets Leaked on Purge and Release: _ownerKeys() creates the owner's key set when it does not exist, and both _releasePosition() and _purgeEntriesInBox() called it purely to remove a key. For an owner that was already untracked — an entity removed moments earlier, or any owner of an entry cleared by /scriptevent ldl:fix — this created an empty set in _keysByOwner that no cleanup path ever deleted, since cleanupAll() only runs for owners the engine still knows about. Both call sites now use this._keysByOwner.get(ownerId)?.delete(key) and no longer create state as a side effect of removing it.

  • Stuck Block-Interaction Mark Swallowing Interact Swaps: In interact gesture mode, itemStartUseOn marks the player so that the itemUse that follows is treated as a block interaction rather than an offhand swap. The mark was a Set entry cleared only by that following itemUse, so any interaction that did not produce one — a cancelled placement, a failed use, an item consumed by another handler — left the player marked indefinitely and silently consumed their next intentional swap. The mark is now a Map holding the tick it was set, and only suppresses a swap within BLOCK_INTERACTION_WINDOW_TICKS (5 ticks); it is consumed on every attempt either way, so no residue builds up.

Removed

  • Light Registry Persistence and the Persistence Toggle (Breaking For the Features Menu): The engine mirrored its light registry into world dynamic properties every 200 ticks and on shutdown, hydrated it back on load, and exposed the whole thing as a Persistence Across Reloads toggle. The mirror never did anything: after a reload _entries is empty, so isPositionActive() is false for every surviving light block and the block's own minecraft:tick reverts it to its ldl:base within two ticks — roughly 98 ticks before sweepRestoredEntries() would have looked at the hydrated entry. The registry path also lost a second race it could not win: hydrate() ran only when the toggle was on, so turning it off and reloading left the saved snapshot unread while its blocks stayed in the world, tracked by nothing and removable only with /scriptevent ldl:fix. Both the write and the toggle are gone and api/storage.js is deleted; what remains is the passive self-destruct that was already doing the work. Worlds saved by 2.0.0 and earlier are migrated on load — purgeLegacyLightDatabase() in main.js clears ldl:lightdb_count and its ldl:lightdb_chunk_* parts once, so the old snapshot does not sit in the world file forever. Cleanup behavior is unchanged for players; persistence is no longer a key in FEATURE_KEYS and worlds that had it saved simply ignore it.
  • Hydration Timeout on the Pending-Restore Queue: _restoredEntries served two purposes: replaying the on-disk snapshot, and retrying restores whose chunk was not loaded (the 2.0.0 orphaned-light fix). With the first gone, every entry is queued by _releasePosition() with hydratedAtTick: -Infinity, which made the PENDING_RESTORE_TIMEOUT_TICKS comparison unconditionally true. The field, the constant and the currentTick parameter of sweepRestoredEntries() are removed; the queue is now a plain retry list of { dimensionId, pos } drained every 40 ticks until each write lands. The orphaned-light behavior it guards is unchanged.

This mod has no additional files