promotional bannermobile promotional banner

Admin instruments: Loot Spawn Manager

A powerful Minecraft Forge 1.16.5 mod for managing coordinate-based loot spawning with DayZ mechanics.
Back to Files

lootspawnmanager-0.2.4.jar

File namelootspawnmanager-0.2.4.jar
Uploader
Rayman_GCRayman_GC
Uploaded
Aug 30, 2026
Downloads
14
Size
498.9 KB
Mod Loaders
Forge
File ID
8771057
Type
R
Release
Supported game versions
  • 1.16.5

Curse Maven Snippet

Forge

implementation fg.deobf("curse.maven:loot-spawn-manager-1574953:8771057")

Learn more about Curse Maven

What's new

Changelog

All notable changes to the Loot Spawn Manager mod are documented in this file.

[0.2.4] - 2026-08-28 (Terrain Map, Item Browser, World-Folder Storage)

Three features and one structural change. The structural one matters most: every hit zone in the interface is now computed once, so the bug class that produced the whole 0.2.3 audit can no longer be written.

Upgrade note: the wire protocol moves "10" → "11". Four packets were added and the config packets grew fields, so a 0.2.3 client would decode the tail of a settings packet as garbage. Data on disk is unaffected. templates.json and points.json are unchanged from 0.2.3; the map's tile cache is new and lives beside them.

Added

  • Terrain map. The map screen drew a coordinate grid and dots; now it draws the world. The server samples the top block of loaded chunks (Heightmap.WORLD_SURFACE → getMapColor), shades land by the drop to the column to its north and water by depth — the same two rules vanilla maps use, and the reason the result reads as relief instead of colour blobs.
    • A tile is 64×64 samples, one byte each in vanilla's MaterialColor.id * 4 + brightness packing: 4 KiB regardless of terrain or zoom. Four LOD levels cover 64 to 512 blocks per tile, so zooming out makes packets rarer, not heavier.
    • Only loaded chunks are read. Reading an unloaded one means loading it, and panning a map means thousands of them. What was never loaded stays blank — but every sampled tile is written to disk, so the map accumulates as the server lives, and re-sampling never erases columns whose chunks went away.
    • Requests are queued and served on a per-tick budget; a client that pans quickly makes the map fill slower, never the server stutter.
    • Block placement and breaking mark their tile stale immediately; everything else (explosions, pistons, growth) is picked up by mapTileTtlSeconds.
  • Selection panel on the map. Clicking a point opens a panel in the legend column with its name and editable X / Y / Z, plus a jump into the full point editor. This closed a real hole: dragging a point only ever changed X and Z, the point screen showed coordinates read-only, and height could not be set anywhere in the interface — the only route was a bulk shift by dy, which moves everything.
  • Copy and paste points. Ctrl+C on a selected point, Ctrl+V elsewhere on the map. The copy carries binding, timer, schedule, particle radius and the point's own loot; statistics and next-spawn time deliberately do not come along, and the first spawn is scheduled a full interval out. The server does the copying — otherwise binding, timer, schedule and loot would be four separate packets each needing the id of a point that does not exist yet. The name is derived server-side, the only place that can guarantee it is free: loot_3 → loot_4, Base → Base 2, climbing until free.
  • Item browser. A grid of every registered item with search over both ID and display name, multi-select, and one shared chance / min / max / weight for the whole selection. Filling a pool of thirty items used to be thirty visits to AddItemScreen and thirty correctly typed IDs. Everything goes in one C2SAddItemsBatchPacket with one sync push at the end — sending one packet per item is the mistake that was fixed in the other direction back in 0.2.1.
  • Data can live in the world folder. storeInWorldFolder moves groups, templates, points and map tiles out of config/ and into the save. Existing data is copied, not moved, and only when the target is empty, so switching back is safe and fresher world data is never overwritten. Applies on the next server start. config.json and permissions.json stay in the instance — they configure the server, not the world.
  • Four more settings in the server settings screen: terrain on/off, tiles per tick, tile refresh interval, and the world-folder switch.

Changed

  • Every hit zone is computed once. New HitList: a rectangle is created in render() and the same object drives the highlight, the tooltip and the click. Seven screens migrated, joining the two that already worked this way — and those two were the only screens the 0.2.3 audit found nothing in. SpawnPointListScreen.mouseClicked went from 166 lines to 34, and what is left is the rename guard, the picker dispatch and the scrollbar. Three dead helpers went with it.
  • The interface is neutral translucent grey. The navy palette read as a separate application pasted over the game. Alpha now grows with depth — panel 0xC8, header 0xD0, hover 0xD8, input 0xE0 — so layers separate without a single border, and the accents are the only colour on screen instead of competing with a blue field.
  • GroupDetailScreen row actions are laid out from measured text instead of the fixed offsets -118/-76/-58/-26, which existed in three places and gave [R] eighteen pixels — enough for English and nothing else.
  • Glyphs the 1.16.5 font cannot draw were removed from the last places they hid: the pin, clock and revert arrow in point cards, and the NBT tick in the loot list.

Fixed

  • The map tile queue flooded itself. The client asked for every visible tile every 300 ms: roughly 330 requests a second against the 80 the server can serve. The queue grew to its ceiling, further requests were dropped silently, and the budget went on re-checking tiles that were already fresh. It now asks only for what is missing or locally stale, measured by the client's clock — comparing against a server timestamp would have been wrong on any two machines whose clocks differ.
  • The settings screen overflowed its own panel. Eleven rows at a fixed step against a height that modalH clamps to the screen: at GUI scale 4 the rows ran over the buttons. The step is now derived from the actual window height with a floor of 15 px, and the top of the rows is computed in one place instead of two.
  • An empty map tile was cached as fresh and not re-sampled until its TTL expired, so an area that loaded a minute later appeared two minutes late. Tiles with no readable column are no longer cached at all.
  • Map textures were held after the map closed. They are released on close now while the bytes stay, so coming back re-uploads from memory instead of re-downloading.
  • The item catalogue never rebuilt. It is static for the process, so reconnecting to a server with a different modpack listed items that were not there. It now rebuilds when the registry size changes.
  • The item browser's footer could push its fields past the panel in longer languages; fields shrink first, spacing second, and only then are labels clipped.
  • RenderSystem.enableBlend() in the terrain pass was never turned off.

[0.2.3] - 2026-08-28 (Loot Lifetime, Server Settings, Interface Audit)

Two rounds of work: an audit of the interface after tester feedback, and the functional gap that audit made visible — spawned loot despawned after five minutes no matter what respawn interval you set.

Upgrade note: the wire protocol moves "9" → "10". C2SUpdateTemplateSettingsPacket gained a field, and a 0.2.1 client would decode the tail of that packet as garbage rather than fail, so the bump is what makes the mismatch loud. Data on disk is unaffected: templates.json gains itemLifespanSeconds, and its default of 0 means "vanilla behaviour", so existing templates spawn exactly as before until you change them.

Added

  • Loot lifetime, per template. LootDropper now sets ItemEntity.lifespan instead of silently leaving the vanilla 6000 ticks. A template can carry its own value; 0 falls back to a new server-wide default, and 0 there means vanilla. This closes the quietest failure the mod had: with a 30-minute respawn interval a point stood empty for 25 of those 30 minutes, ONLY_IF_EMPTY and REPLACE_OLD had nothing left to check by the time they ran, and nothing in the interface said so.
  • The field is in the template editor, third row of Spawn Behavior, with the effective value beside it — now 300 s — which turns amber when loot dies before the next spawn arrives. The problem is visible where it is created, not only in the health check.
  • Two health checks for it: the gap between lifetime and interval, and pile-up risk for ADDITIVE with a lifetime far longer than the interval.
  • Server Settings screen (Tools tab). Seven values that lived only in config.json and needed a restart: default loot lifetime, unloaded-chunk retry, out-of-schedule retry, point save interval, region cap, particle radius, sync chunk size. Writing them saves config.json and applies at once. Level ADMIN — these affect the whole server, not one object — and other online admins receive the new values so nobody edits a stale screen.
  • S2CConfigPacket / C2SSetConfigPacket. The config lives on the server, so without the first one the settings screen would be showing numbers it invented. Values are clamped twice: on construction, because a client can send anything, and again in ModConfig.clamp() before the write — the same path a hand-edited file goes through.
  • unloadedRetryMs. A point whose chunk is not loaded now retries soon instead of burning a whole respawn interval. LootDropper.spawnLoot returns null for "could not run" as distinct from an empty list for "ran, dropped nothing"; the two were indistinguishable before, so walking into a fresh region meant waiting a full cycle.
  • ClippedButton. Vanilla Widget.renderButton draws its label centred and unclipped, so any longer translation spilled out of the button on both sides. All 59 button sites use it now. It is a backstop, not a substitute for correct widths — a … in English still means the button is too narrow.
  • GuiHelper.clip() / drawHeadingClipped() — one pixel-width truncation used by text, buttons and headings alike.

Fixed

  • The Spawn Points panel was 150 px wide, always. SpawnPointListScreen computed its width from the uninitialised field panelW instead of the constant PANEL_W (520). The floor in contentW() made the wrong value stable, so it looked deliberate. Everything downstream is laid out from that width: the schedule badge landed left of its own card, statistics printed over the point name, six action icons marched into the name, and tile mode collapsed to one column.
  • Tile mode had no click handling at all — clicks fell through to the card branch, where the row index comes from (my − listY) / CARD_H and the icon zones from px + panelW. A click on the right of a tile opened the delete confirmation for a different point, and "clear loot" and "test spawn" fired at the wrong point with no confirmation at all.
  • Scroll units in tile mode. Render clamped by tile rows; all three mouse handlers clamped by point count, so the wheel scrolled far past the end and the scrollbar dragged on a different scale than it drew.
  • Labels drawn over their own fields. In the template editor the label x and the field x were two independent magic offsets tuned for English: "Max items:" fits 62 px, «Макс. предметів:» needs 84. Both rows are now laid out left-to-right from measured text, and if a row cannot fit, the labels shrink instead of the fields being covered. Verified against all nine languages.
  • Statistics badges sat under the search field on the main menu — 9 px of overlap that hid two thirds of them.
  • Five coordinates left absolute after the tab-bar refactor, so the tab bar painted over them: the point and template counters, the simulator's iteration field, a note in the point-loot screen, and the back button's tooltip zone, which was registered 22 px above its own button.
  • Toasts were a solid blue block. The border fill ran after the background and covered it; two later one-pixel strips were all that remained of the panel.
  • Three modals drew at raw W/H while positioning themselves with the clamped helpers, so they ran off the screen at GUI scale 3–4. The bind picker went further and used different formulas in render and in click handling — the row under the cursor was not the row that opened.
  • The map legend truncated silently and toggled the wrong template: the render skipped a deleted template without advancing the cursor, the click loop counted it. Both now read one list, and the legend scrolls.
  • Confirmation text was clipped to one line with an ellipsis — the only warning before an irreversible action. It wraps now, and the dialog grows to fit.
  • A corrupt JSON file killed server startup. loadList caught IOException but Gson throws JsonSyntaxException, a RuntimeException. One truncated points.json and you could not get in to restore a backup. The file is skipped with a log line naming the backup folder.
  • Command permissions ignored delegation. The whole /lootmanager tree required OP 2, so a player with delegated EDIT could open the GUI with the keybind but could not run a single command — including /lm menu. Commands now read the same level the packets do; perm stays ADMIN-only.
  • Emoji rendered as empty squares. 34 keys in every language file opened with glyphs the 1.16.5 font has no coverage for — section headers, tool names, the grid toggle. Removed; the mod's own font-independent drawIcon remains for icons.
  • Hardcoded English in five places that every other string had left behind: the region generator's parameter label and estimate, the shift estimate, UNBOUND in the map popup, and the loot-grid tooltip.

Changed

  • Tooltip coverage is now complete. Every button and field in the mod explains itself, including the single-letter action icons where [C] means both Copy and Clear depending on the screen. Screens whose rows scroll use the runtime hoverTooltipKey path; fixed widgets use TooltipZone with the same coordinates passed to the widget. Language files went from 366 to 486 keys, all nine complete.
  • defaultParticleRadius actually applies. The client used a local constant and ignored the server value entirely.
  • README corrected. It claimed in one section that the client mod is optional and in another that "every client needs it — Forge refuses a connection without it". The code is unambiguous: nothing is registered, IGNORESERVERONLY is declared, and the channel accepts clients that lack it. The second claim was stale and would have made admins force an unnecessary download on every player.

Verified

  • All 52 packets have symmetric encode/decode byte sequences; every C2S handler checks permissions and runs inside enqueueWork.
  • Every scheduler mutation path keeps the priority queue consistent — per-object operations pass the same instance, bulk operations rebuild.
  • Language files: no missing or extra keys in any of the nine, and format specifiers match English everywhere.

[0.2.1] - 2026-08-25 (Interface Rebuild — Fixes)

A review pass over the code 0.2.0 itself introduced. Three of the findings made the two new screens unusable, so this is a corrective release rather than a feature one.

Upgrade note: the wire protocol moves "8" → "9". This is not optional housekeeping — S2CEntityBatchPacket was registered ahead of two existing packets, which shifts their discriminator IDs. A 0.2.0 client would pass the version handshake and then decode those packets as the wrong type, so the bump is what makes the mismatch fail loudly instead of silently. Data on disk is unaffected.

Fixed

  • Scrolling pushed content off the panel instead of scrolling it. Both new screens walked a manual draw cursor, and both advanced it for rows that were skipped by the scroll window. With the list scrolled by five rows the first visible row was drawn five row-heights too low. PointDetailScreen and ToolsScreen were unusable the moment you scrolled — which is to say, the headline results of the last two releases did not work.
  • Visible-row count was a division by one constant while rows are 12, 16 and 18 px tall, so ToolsScreen drew more rows than fit and they spilled past the panel.
  • The point detail scrollbar was driven by the wrong quantity — the number of clickable zones (about 8) rather than the number of rows (about 18) — and its mouse wheel was capped at a hardcoded 20 rows.
  • "Change template" opened the point list. The template picker lives inside SpawnPointListScreen and is driven by a private field, so there was no way to reach it from another screen; the button simply did nothing useful. PointDetailScreen now has its own picker with type-to-filter, an empty-template warning and item counts.
  • Creating an entity whose name already existed pushed the wrong object. The delta looked the new object up by name, and getPointByName returns the first match while names are not unique anywhere. The new entity stayed invisible until the next full sync. The wand names points after their coordinates, so two clicks on one block hit this exactly. All five call sites now use the object create* / duplicate* already returns.
  • ImportScreen drew more rows than its dialog holds — the row count came from the full screen height rather than the clamped modal height, giving 23 rows in a box that fits 8.

Changed

  • Mass operations no longer force a full resync. Enable/disable a selection, delete a selection and set a group-wide timer now send S2CEntityBatchPacket — one JSON array cut by the same byte budget the full sync uses. Sending one delta per object was not an option: on a 500-point selection that is the very mistake fixed in the client→server direction earlier. Nine full syncs remain, and each genuinely replaces everything: login, open menu, reload, undo, restore, import, region generate, shift points, group/template delete.
  • In-flight feedback extended from 2 screens to 8, covering every per-object mutation that has a row to dim: templates, points, schedule, particles, map drag, tiles. Tiles dim and refuse repeat clicks the same way cards do.
  • Pending marks are cleared when the mod GUI closes. tick() only runs while a screen is open, so a mark left in flight used to hang until the next time you opened the interface.
  • Clearing a point's loot no longer broadcasts a delta. It removes world entities and changes no field on the point, so an unchanged object was being sent to every admin.

Added

  • RowList — a scrolling list of variable-height rows. Heights are declared with the row and the cursor only advances for rows actually drawn, so neither of the two bugs above can be written a third time. Both scrolling screens use it.
  • S2CEntityBatchPacket — a multi-object delta for mass operations.

Known limitations

  • TemplateDetailScreen and ToolsScreen clamp their position but not their internal column offsets, so at the narrowest widths the right-hand fields clip.
  • Still verified by compilation and static review only. This release exists because that is not enough: the three critical findings were in code the previous release reported as done.

[0.2.0] - 2026-08-24 (Interface Rebuild)

A UX review of all 16 screens found that most usability problems came from three structural causes rather than from individual screens, so they were fixed at the root rather than patched screen by screen.

Upgrade note: the wire protocol moves "7" → "8"; clients must be updated with the server. Data on disk is unaffected.

Changed — three foundations

1. Delta synchronization instead of a full resync. The server used to push the entire data set after every single action — 34 call sites. The client answered that by re-running init(), which is why scroll position, search text and any open dialog were thrown away on every click; some of that state had to be preserved by hand, which treated the symptom.

A new S2CEntityPacket (kind + op + id + json) now carries one changed object, and LootManagerBaseScreen.onDataChanged() only marks caches stale — init() is not called. 22 handlers moved to SyncHelper.push*; the 13 remaining full syncs are the ones that really do change everything (login, open menu, undo, restore, import, region generate, shift, batches, group/template delete, mass timer). Every one of the 26 mutating handlers was cross-checked against its broadcast — no gaps.

This also fixed a related bug: the resync used to reach only the player who acted, so a second admin's screen went quietly stale. Deltas go to everyone with the mod and VIEW or above.

One consequence had to be handled explicitly. The scheduler mutates spawn timers every second and deliberately does not push deltas — that would be hundreds of packets per tick. Timers used to refresh as a side effect of the constant resyncs; without them a card would have been stuck on "Ready" forever. The spawn event already carries the point id and timestamp, so the client now recomputes exactly what the server computed, at no extra traffic.

2. GuiLayout — one shared geometry. Twelve screens hard-centred a fixed width (380…540 px) with no check at all. At GUI scale 4 the logical screen is 480 px wide while the point list panel is 520 px plus a scrollbar: the result was negative and the panel hung off both edges. contentW/contentX/modalW/modalX never return an off-screen position, and the list panels now narrow their contents too. Verified across widths 960 / 640 / 480 / 400 / 341.

GuiLayout.Rect addresses the second half of the problem: coordinates used to be computed twice, independently, in render and in mouseClicked with magic numbers. That is where the icons that highlighted two pixels away from where they clicked came from.

3. A persistent tab bar. MainMenuScreen was the only hub, so getting from a point to the map meant Back → Main → Map. Groups / Points / Map / Tools are now one click from anywhere; ten screens declare their section. The map's own dimension tabs were moved below the new strip.

Added — Point detail screen, tiles, and pending state

  • PointDetailScreen — everything about one point in one place: position, binding, timing, loot, statistics, actions. Replaces the nine single-letter icons crammed into a 48 px card, which were dense already and left nowhere to put a new action. Clicking a card opens it; before this, clicking a card did nothing at all unless the timer field happened to be filled. Its rows are drawn manually against GuiLayout.Rect rather than as Minecraft widgets: widgets are positioned in init() and do not move with the scroll, and this content is taller than the screen at GUI scale 3–4.
  • Tile view for the point list (⊞ Tiles / ≡ List). A card fills a row and shows 6–9 points per screen; a tile carries the same key information in a fifth of the area, so dozens fit. With several hundred points that is the difference between a minute of scrolling and seeing everything at once.
  • ClientPending — an action in flight dims its card and ignores further clicks until the delta arrives, with a 5 s timeout that reports the failure instead of staying silent. On a laggy server people pressed twice, and the second press sometimes undid the first.
  • Type-to-filter in the template picker — a picker is an overlay that appears and disappears while a widget lives from init() to init(), so typing filters directly.
  • ToolsScreen rebuilt as one scrolling column. It used to be five fixed-height sections stacked vertically, each truncating its own content: backups showed 5 of the 20 kept, the event feed 20 of 50, the health check as many warnings as fit, and the item lookup only the first 3 templates. On a short screen the lower sections did not fit at all. Nothing is cut off now, and a lookup result row is clickable to teleport to its first point.

Fixed — interface

  • A click on a point card did nothing. The handler ended in a bare return true whenever the timer field was empty, which is the normal state — while everywhere else in the mod a row click drills down.
  • A hidden mode silently overwrote timers. With a value left in the "Timer override" field, the next click on any card rewrote that point's timer; only small grey footer text said so, the field was never cleared, and the card gave no signal. Now the hint turns amber and names the value, the hovered card gets an amber stripe, and the field clears after applying.
  • Three full scans of every point, every frame. MainMenuScreen counted its three badges with three separate stream().count() passes over all points — 9 000 iterations 60 times a second at 3 000 points, for labels that change every few minutes. The map legend did the same. Both now use one cached pass, the pattern ToolsScreen already used for its health check.
  • A point bound to an empty template looked healthy — green status, spawning nothing. Only Tools → Health Check revealed it. It now shows amber with an EMPTY badge.
  • The inline rename field could not be clicked into — any click committed the rename, so the cursor could never be placed with the mouse.
  • Icon highlight was 2 px off from the clickable area on the point card's second row.
  • Enter did not confirm creation of a group, template or point.
  • Long names overflowed ConfirmDialog, which has a fixed width and drew unclipped.
  • The revert-bind icon had no hover state — rvHov was computed and never used, so it did not read as clickable. The test-spawn icon showed the wrong tooltip.
  • Scrollable lists with no scrollbar — ImportScreen now has a real one; both template pickers show a count and a "more below" hint; the map's picker no longer runs off-screen.
  • Shift Points ran on one click — the only mass operation without a confirmation dialog.
  • The region generator capped at a hardcoded 1000 while the server read ModConfig.maxRegionPoints; raising the config did nothing. The limit is sent to the client.
  • Batch bar labels on the map (Enable, Disable, Delete, Bind All) were hardcoded English — they sit in a String[], which the localization scan did not reach.

Known limitations

  • TemplateDetailScreen and ToolsScreen clamp their position but not their internal column offsets, so at the very smallest widths their right-hand fields clip. A fluid re-flow of those rows is a design task, not a mechanical one.
  • Verified by compilation and static review only — this release has not been run in-game, and a rebuilt interface is exactly the kind of change where a few pixels only show up on screen.

[0.1.0] - 2026-08-23 (Server-Side, Hardening & Full Localization)

Two review passes over all 105 source files, followed by fixes: first the code, then the localization. Four findings were server-crash or data-loss class. The network layer and the whole GUI text pipeline were reworked as a result, and the mod no longer needs to be installed on clients.

Upgrade note: the mod no longer registers any items, so existing Loot Spawn Wands and Region Brushes in inventories or chests will disappear on world load. Re-create them with /lm tool wand / /lm tool brush on any item. The wire protocol changed ("6" → "7") and two packets were removed, so clients that do have the mod must be updated alongside the server. On first start after upgrading, any point whose respawn timer already elapsed while the server was down is spread randomly across its own respawn window instead of firing all at once — expect the first minutes to look calmer than before.

Changed — the client mod is now optional

The mod used to be mandatory on every client. Three things forced that, and all three are gone:

  • Two registered items. loot_spawn_wand and region_brush were real registry entries, and Forge synchronizes a registry snapshot at login — entries the client does not have drop the connection. This was the hard blocker; no flag works around it. Both items are removed. Their behaviour now lives on a marker in the item's NBT: /lm tool wand or /lm tool brush turns whatever you are holding into that tool (with a lore line so you can tell it apart), and /lm tool clear gives the item back. The marker travels with the stack, so a tool can be stored in a chest or handed to another admin, and holding an ordinary item keeps working as usual. Right-click behaviour moved from the two Item subclasses into ToolInteractHandler (PlayerInteractEvent.RightClickBlock).
  • Exact protocol match. NetworkRegistry.newSimpleChannel(..., PROTOCOL::equals, PROTOCOL::equals) rejected a client that reports ABSENT. Both predicates are now NetworkRegistry.acceptMissingOr(PROTOCOL).
  • No server-only declaration. Added the ExtensionPoint.DISPLAYTEST registration with FMLNetworkConstants.IGNORESERVERONLY, so Forge stops comparing mod lists for this mod.

The server now checks PacketHandler.hasMod(player) before sending anything, so a modless client is never handed packets it cannot decode; SyncHelper.reply() falls back to a plain chat message for those players. What a modless client loses: the admin GUI, the map, the simulator and particle visualization — commands cover the rest. The loot itself is plain vanilla ItemEntity, so ordinary players notice nothing at all.

Security

  • Remote OOM before any permission check — C2SBatchSetActivePacket.decode() read the list length as a raw int and immediately did new ArrayList<>(size). decode runs on the netty thread before enqueueWork, i.e. before PermissionCheck — so any connected player, with no permissions at all, could send Integer.MAX_VALUE and exhaust server memory. New PacketUtil.readSize() clamps every length; readBlob() additionally rejects a declared size larger than 512 KiB or larger than the bytes actually remaining in the buffer. Audited across every decode() in the mod — no raw length-driven allocation left.
  • Server crash from a non-ASCII point name — PacketBuffer.writeUtf(s, max) compares bytes, while TextFieldWidget.setMaxLength limits characters. A point name of 33+ Cyrillic characters (or 17+ CJK) overflowed the 64-byte cap in S2CSpawnEventPacket. Encoding runs synchronously on the server thread inside SpawnScheduler.tick(), and that broadcast block had no try/catch, so the EncoderException propagated through ServerTickEvent and took the server down. All 48 packets now go through PacketUtil.writeString/readString, which sizes the write cap as maxChars × 4; the broadcast is wrapped defensively.
  • Permanent save failure from a crafted move packet — C2SMovePointPacket accepted any double. Gson refuses to serialize NaN/Infinity, so one poisoned point made every subsequent save() throw — silently, because the exception died inside the CompletableFuture from enqueueWork. Nothing reached disk until restart. Coordinates are now rejected unless finite and clamped to world bounds, and writeAsync logs a serialization failure instead of swallowing it.
  • Item flood from a zero respawn timer — C2SCreateTemplatePacket passed respawnSeconds through unvalidated. At 0 a bound point became due on every scheduler tick, i.e. once per second. Clamped to 1 … 30 days, matching the check C2SSetTimerPacket already had.
  • Server thread freeze from unbounded template settings — C2SUpdateTemplateSettingsPacket validated none of its six numeric fields. LootDropper.pickItems loops exactly maxWeightedPicks times per spawn, so Integer.MAX_VALUE meant a hung thread and an OutOfMemoryError. Now clamped: picks 1–64, max items 1–256, radii 0–128, priority ±100.

Fixed

  • Mass spawn burst after downtime — respawn times are stored as absolute wall-clock, so after any outage every point was overdue and the first tick tried to spawn all of them. DataManager.normalizeOverdue() now spreads overdue points randomly across their own respawn interval on load, and the scheduler caps work at 200 spawns and 20 event broadcasts per tick.
  • Deleting a group left points bound to nothing — deleteTemplate cleared templateId on affected points, deleteGroup did not. Those points kept a templateId pointing at a deleted template: the GUI showed them as bound, countActive() counted them, and the scheduler silently dropped them from the queue forever. Both paths now clear the binding and reset the timer.
  • Backup restore failures were invisible — SyncHelper.notifyAdmins starts with if (except == null …) return;, and both error paths in C2SRestoreBackupPacket called it with null, so nobody was told anything. The helper now falls back to ServerLifecycleHooks.getCurrentServer(), and the requesting player gets an explicit success or failure message.
  • Silent server-side rejections — every handler answered a failed check with a bare return, while the client had already closed the screen and looked like the action succeeded. 37 handlers moved to PermissionCheck.requireEdit/requireAdmin/requireView, which explain the refusal; add-item, test-spawn, teleport, simulate and restore now reply with the specific reason.
  • Toasts were invisible outside the mod GUI — Toast.renderAll was only called from LootManagerBaseScreen.render, so a notification sent during normal play (another admin deleting a group, a rejected packet) expired unseen after 3 s. New ClientToastOverlay draws them on the HUD whenever no mod screen is open.
  • 0 % chance items could still drop — the roll used nextFloat() * 100f <= chance, and nextFloat() can return exactly 0.0. Now strict <.
  • Item counts above the stack size were truncated to a byte — ItemStack.save() writes Count as a byte, so a stack of 9999 arrived at the client as 15. placeItem clamps to item.getMaxStackSize().
  • Simulator numbers did not match reality — simulate() reimplemented item picking and ignored maxItemsPerSpawn, over-reporting drops for templates with more items than the limit. Both paths now share LootDropper.roll() and apply the same cap.
  • Wand and Region Brush ignored delegated permissions — both checked player.hasPermissions(2) directly, so an admin granted ADMIN via /lm perm grant got the tool and could not use it. Both use PermissionCheck now, as does the scheduler's event broadcast filter.
  • Chinese localization was completely broken — zh_cn.json had unescaped quotes inside 15 values ("点击"+ 新建组"创建"), which made the whole file unparseable. Chinese players saw raw keys for the entire interface, not just those lines. Inner quotes replaced with 「」.
  • tooltip.btn.map existed in no language file — the Map button's tooltip rendered as a raw key for everyone. Added to all nine.
  • 21 keys missing from 7 languages — including the item names themselves and the whole Tools tab. All nine files now carry an identical key set.
  • Scroll position and search text were wiped after every action — init() ran scroll.reset() and rebuilt the search field, and init() re-runs on every data sync, which the server sends after every mutation. Editing one point in a list of 300 threw you back to the top with the filter cleared. Scroll is now kept (clampTo in render keeps it valid) and the search text is restored before the responder is attached.
  • The inline rename field could not be clicked into — all three screens with inline rename began mouseClicked with an unconditional commit, so a click meant to place the cursor ended the rename instead. Clicks inside the field now reach it.
  • Icon highlight was 2 px off from the clickable area — on the point card, row-2 icons are drawn at row2Y - 2 and the click test uses y + 16, but the hover test used row2Y. The top 2 px clicked without lighting up; the bottom 2 px lit up without clicking.
  • Long names overflowed the confirmation dialog — ConfirmDialog has a fixed 290 px width and drew title and body unclipped.
  • Particle radius was unvalidated — the field accepts four digits and neither client, packet, nor server checked the value; 9999 meant drawing particles for points thousands of blocks away. Clamped to 1–256, the same bound defaultParticleRadius already had. The client also caps visualization at 64 points and now checks vertical distance — the range test was 2D, so a point 200 blocks above counted as "nearby".
  • GROUND appeared under two different abbreviations — the list printed name().substring(0, 3) (GRO) but fell back to a hardcoded "GRD".
  • /lm reload lost up to 30 s of state — point saves are debounced, and reload re-read the older file. It now flushes points first.
  • Import accepted malformed files — missing or null names caused an NPE in exportGroup and in name lookup. Defaults are substituted and null entries skipped.
  • Backup filenames collided — the timestamp has second resolution, and deleting a group writes an undo snapshot and a backup in the same moment, so the second overwrote the first. Colliding names get a _2, _3… suffix. Wand-created points had the same problem and are now named wand_X_Y_Z.

Added

  • /lm tool wand | brush | clear and AdminTool — the NBT marker described above.
  • ToolInteractHandler — right-click behaviour for marked items, replacing the two removed Item subclasses.
  • PacketHandler.hasMod(player) — guard before sending to a possibly-modless client.
  • PacketUtil — one place for every packet bound: string caps in characters, list-length and blob-length limits, coordinate clamping, finiteness checks.
  • C2SBatchDeletePointsPacket + DataManager.batchDeletePoints() — one undo snapshot, one disk write and one log line for a whole selection.
  • S2CSyncChunkPacket — a single chunk type carrying groups, templates or points.
  • ClientToastOverlay — HUD rendering for notifications outside the mod GUI.
  • Dynamic tooltips — LootManagerBaseScreen.hoverTooltipKey lets a screen set a tooltip during render() for an element that has no fixed position. TooltipZone is anchored to coordinates fixed in init(), which is why the point card's single-letter action icons (X, C, B, R, T, S, I) never had explanations — the keys existed but were unused. All nine are wired up now.
  • GuiHelper.tr(key, args…) and drawCenteredClipped().
  • PermissionCheck.requireView/requireEdit/requireAdmin — check and explain the refusal.
  • DataManager.saveGroups() / saveTemplates() — targeted writes.
  • 191 new translation keys, each translated into all nine languages.

Changed

  • Sync reworked around a byte budget. Chunking counted items — but one template with two hundred entries weighs as much as a hundred points, which is exactly how oversized packets appeared; groups and templates were not chunked at all and rode along in the begin packet, where a few hundred templates could reach the vanilla 1 MiB payload limit and drop the client. S2CSyncBeginPacket now carries only counters, and the sender fills each chunk until it hits the budget. Packet size no longer depends on content.
  • config.json: pointsPerPacket replaced by syncChunkBytes (default 32768, clamped 4096–262144). The old key is ignored if present.
  • save() split into saveGroups() / saveTemplates() / savePointsOnly(). It used to serialize every group, template and point on the server thread on every click; 26 call sites now write only the file they touched. Full save() remains for undo, restore, import and group deletion.
  • AdminLogger writes on its own daemon thread instead of doing blocking file I/O inside the tick.
  • Map batch operations (enable / disable / delete) send batch packets instead of one packet per point — a 500-point selection meant 500 full saves and 500 full resyncs. Selections are split into packets of 512 ids, and mass delete now asks for confirmation like every other delete path.
  • C2SBatchSetActivePacket requires EDIT instead of ADMIN, matching the per-point packet it replaces.
  • All GUI text moved into the language files — 205 hardcoded English strings across 15 files. What remains are two %.0f number formats. GuiHelper.formatTimer and formatTimestamp were returning "Ready" and "5m ago" straight from the method body; time units come from the language file now, since h/m/s are not universal.
  • Network protocol: "6" → "7" (incompatible with v0.0.9 clients).

Removed

  • S2CDataSyncPacket — superseded by chunked sync and never sent, though still registered, with an unreachable client handler and a redundant import in 25 files.
  • S2CSyncPointsChunkPacket — replaced by S2CSyncChunkPacket.
  • LootSpawnWand, RegionBrush, ModItems and both item models — replaced by the NBT marker. Their item.lootspawnmanager.* language keys are gone too.

Known limitations

  • Spawn modes remain mutually exclusive: RADIUS cannot be combined with ONLY_IF_EMPTY or REPLACE_OLD, and the occupancy check radius is still hardcoded to 2 blocks regardless of maxSpawnRadius. Splitting placement from occupancy needs a data migration.
  • Region generation has no command equivalent, so the Region Brush needs the client mod to finish the job — without it the brush reports the selected corners and stops there. Spawn mode, weights, schedules, per-point loot and point deletion are likewise GUI-only.
  • Still no automated tests. The refactor made the pure logic easier to cover: LootDropper.roll(), normalizeOverdue(), PacketUtil, SyncHelper.chunkify(), isInScheduleWindow().
  • Verified by compilation and static review only — not run on a live server.

[0.0.9] - 2026-06-16 (Performance & Admin Tools)

Added

  • Visual loot grid editor — [⊞ Grid] toggle in Template Detail's Loot Pool section header switches from the item list to a 9-column icon grid (20×20 px slots). Hover any slot to see a tooltip with item name, count range, chance%, and weight. Click the [×] icon that appears in the top-right corner of a hovered slot to remove the item. [≡ List] reverts to the table view. Toggle persists within a session.
  • Per-template export — [📤 Export] button in Template Detail footer. Sends C2SExportTemplatePacket to server (requires ADMIN); writes the template's JSON (items + settings) to config/lootmanager/exports/template_NAME_TIMESTAMP.json. Server sends a chat message with the filename. Can be shared between servers or used as a backup of specific templates.

Changed

  • Async disk I/O — DataManager.save() and savePointsOnly() now serialize data in-memory on the server thread (~1 ms) then hand off file writes to a dedicated single-thread LootSpawnManager-Save executor (~5-50 ms, off server thread). Eliminates disk stalls during admin actions on busy servers. On shutdown, a final save is submitted last in the queue and awaitTermination(30 s) ensures all writes complete before the JVM exits.
  • Network protocol: "5" → "6" (incompatible with v0.0.8 clients)

[0.0.8] - 2026-06-16 (Security & Backup Restore)

Added

  • Backup Restore UI — Tools tab now shows up to 5 recent backups with timestamps. Click [Restore] → ConfirmDialog → server atomically replaces all data from the snapshot, rebuilds the scheduler, notifies other admins, and resyncs all clients. No server restart required.
    • BackupManager.listBackups() / readBackup(filename) with path-traversal protection
    • DataManager.restoreFromSnapshot(json) — full field migration for old snapshots
    • Packets: C2SRequestBackupListPacket, S2CBackupListPacket, C2SRestoreBackupPacket
    • ClientState.backupList stores the latest list from server
  • Input validation on item packets — C2SAddItemPacket and C2SAddPointItemPacket now reject: empty item ID, minCount < 1, maxCount < minCount, chance outside 0–100, negative weight. Malformed packets are silently dropped server-side.

Fixed

  • Path traversal in ImportExportManager — importFile() now rejects filenames containing .., /, \ and verifies the resolved path stays inside the import directory. Prevents directory escape via crafted filenames.
  • String.isBlank() → trim().isEmpty() — two packets used a Java 11+ API (isBlank) causing compile failure under the required JDK 8.
  • AdminLogger race condition — log() is now synchronized; prevents log-line interleaving when two admins trigger actions simultaneously.

Changed

  • Network protocol: "4" → "5" (incompatible with v0.0.7 clients)

[0.0.7] - 2026-06-16 (Map & Multi-Admin)

Added

  • Interactive Map screen — [Map] button in SpawnPointListScreen opens a top-down coordinate grid of all spawn points in the current dimension:
    • Zoom with scroll wheel, pan by left-drag on empty space
    • Each point rendered as a colored dot (green = active, red = inactive)
    • Click a dot to open its SpawnPointListScreen row; Shift+click to select
    • Drag to reposition — drag a dot to new coordinates → sends C2SMovePointPacket
    • Template legend: click a template chip to toggle visibility of its points
    • Grid lines every 100 blocks, coordinate labels, crosshair at origin
  • ConfirmDialog — universal modal overlay for destructive operations:
    • Delete group (shows template count warning)
    • Delete template (shows bound point count warning)
    • Delete point
    • Restore backup
    • Enter = confirm, Escape = cancel; isPauseScreen() returns false so the game keeps ticking while the dialog is open
  • Multi-admin notifications — when any admin mutates data, all other connected admins with ≥ VIEW permission receive a toast notification:
    • Group delete, template delete → warning toast naming the deleted object
    • Backup restore → warning toast naming the filename
    • S2CAdminNotifyPacket (TYPE_INFO / TYPE_WARNING) + SyncHelper.notifyAdmins()

Fixed

  • TemplateDetailScreen per-frame GC — ForgeRegistries.ITEMS.getValue(new ResourceLocation(...)) was called every render frame per item. Replaced with itemStackCache (Map rebuilt once per template change). Eliminates allocation spike at 60 fps with large loot pools.
  • ToolsScreen O(T+P) per frame — collectWarnings() walked all templates and points on every render call. Now guarded by warningsDirty flag; only recomputed on data sync or screen init.
  • Dead code in ShiftPointsScreen.cycleTmpl() — removed redundant else if (tmplIdx == -1) tmplIdx = -1 branch.

Changed

  • Network protocol: "3" → "4" (incompatible with v0.0.6 clients)

[0.0.6] - Unreleased (Performance & Permissions Hardening)

Changed

  • SpawnScheduler — усунено lock contention. tick() більше не synchronized цілком; розбито на три фази: drain (під локом, мікросекунди) → spawn/world-I/O
    • broadcast (ПОЗА локом) → requeue (під локом). Мережеві потоки (addOrUpdate/remove з packet-handler-ів) більше не блокуються на час спавну (раніше — до 50мс/спавн фрізу GUI при високій частоті).
  • Чистий comparator черги — додано SpawnPoint.cachedPriority (transient), заповнюється при add/requeue; comparator більше не звертається до DataManager під час порівнянь (прибрано side-lookup і потенційний O(N) при rebuild).
  • Config-файл config/lootmanager/config.json (ModConfig) — винесено хардкоди в tunables: maxRegionPoints, pointsPerPacket, pointsFlushIntervalMs, scheduleSkipRetryMs, defaultParticleRadius. Створюється з дефолтами, значення clamp-ляться у безпечні межі.

Fixed

  • Permission delegation працює всюди — 20 застарілих C2S-пакетів переведено з player.hasPermissions(2) на PermissionCheck.canEdit/canAdmin. Тепер EDIT-модератор без OP (через /lm perm grant <player> 2) може робити CRUD; небезпечні/файлові/масові операції (region-генерація, import/export, batch) вимагають ADMIN.

[0.0.5] - Unreleased (Scale + Loot Flexibility)

Added

  • Chunked синхронізація — замість одного гігантського snapshot-пакета (упирався в ~1МБ ліміт Forge при ~2-3к точок і дисконектив гравця) дані тепер летять як: 1× S2CSyncBeginPacket (групи+шаблони+meta) + N× S2CSyncPointsChunkPacket по ≤10 точок на пакет. Клієнт збирає у ClientSyncAssembler (BitSet від дублікатів). Стеля піднята з ~3к до десятків тисяч точок.
    • SyncHelper.sendFull(player, openGui) замінив усі 27 прямих send-ів S2CDataSyncPacket
  • Per-point кастомний лут — кожна точка має власний customItems список
    • прапорець useCustomLoot. Коли увімкнено й не порожній — LootDropper бере предмети звідси замість template.items (режим/picks беруться з шаблону). Дозволяє унікальний лут (босовий ящик) без окремого шаблону.
    • Нова іконка [I] на row 2 картки точки → PointLootScreen
    • Зелена крапка на [I] коли custom loot активний
    • Toggle "Custom loot: ON/OFF", список з видаленням, порожній стан
    • Пакети: C2SAddPointItemPacket, C2SRemovePointItemPacket, C2SSetPointCustomLootPacket
  • Вибір предмета з руки — кнопка "From hand" у AddItemScreen бере item ID
    • повний NBT з основної руки гравця одним кліком
  • Live пошук-підказки — при введенні в поле Item ID показуються до 6 збігів з ForgeRegistries.ITEMS; клік на підказку підставляє повний ID
  • Універсальний AddItemScreen — один екран працює і для шаблонів (C2SAddItemPacket), і для точок (C2SAddPointItemPacket) через окремі конструктори

Changed

  • LootDropper.pickItems() приймає явний список предметів (підтримка custom-луту)
  • DataManager.load() мігрує customItems/pastTemplateIds для старих збережень
  • Debounced збереження точок — scheduler більше не пише points.json щосекунди; позначає dirty, фактичний запис раз на ~30с (flushPointsIfDue) + повний save на кожній адмін-дії та при зупинці сервера. ~90% менше disk I/O при 1000+ точок
  • HashMap-індекси в ClientDataCache — getTemplate/getGroup тепер O(1) замість O(N) лінійного пошуку (важливо для render-циклів 60 fps при 1000+ точок)
  • Версія мода → 0.0.5

Fixed

  • C2SCreateGroupPacket досі слав raw S2CDataSyncPacket (оминав chunking) — переведено на SyncHelper.sendFull + PermissionCheck.canEdit
  • LootDropper.spawnLoot() повертає порожній список якщо template == null (захист від unbound/видаленого шаблону)

[0.0.4] - Unreleased (Power Tools)

Added

  • Test Spawn button — [?] фіолетова іконка на картці точки в SpawnPointListScreen. Одинарний клік → разовий спавн ігноруючи таймер. Server повідомляє кількість заспавнених предметів через actionbar.
  • Template priority — нове поле priority (default 0) на шаблонах. SpawnScheduler PriorityQueue має tie-break: коли дві точки готові в одному тіку, перевагу отримує шаблон з вищим priority. Поле "Prio" доступне у TemplateDetailScreen → Section "Item Selection".
  • Schedule windows — кожна точка може мати activeHourStart/activeHourEnd (24h, real-time hours). Поза вікном точка не спавнить (планується на +1 хв). Підтримується wrap-around (22 → 6 = 22,23,0,1,2,3,4,5). Badge ⏰ 22:00-06:00 на картці точки (фіолетовий якщо зараз у вікні, сірий якщо ні).
  • Mass shift points — Tools tab → 📐 Shift Points… модал:
    • dx / dy / dz поля
    • dim filter (cycle через всі виміри + ALL)
    • template filter (ALL / Unbound only / окремий шаблон)
    • Real-time estimate "~ N points"
    • Snapshot для undo перед операцією
  • C2STestSpawnPacket / C2SShiftPointsPacket / C2SSetPointSchedulePacket — 3 нові C2S пакети, всі через PermissionCheck

Changed

  • C2SUpdateTemplateSettingsPacket тепер містить поле priority (back-compat конструктор без priority лишається)
  • DataManager.updateTemplateSettings() — overload з priority параметром

[0.0.3] - Unreleased (Admin Experience)

Added

  • Region Brush — окремий item для масового створення точок:
    • RMB на блок 1 → corner1 (NBT в ItemStack)
    • RMB на блок 2 → corner2 + відкриває GUI генерації
    • Shift+RMB → скидає виділення
    • 3 паттерни: Grid (з spacing), Random (випадково в об'ємі), Floor (по нижній грані)
    • Естимейт кількості точок з кольоровим попередженням (>1000 заблоковано)
    • Авто prefix-name + опційне прив'язування до шаблону
  • Permission system — config/lootmanager/permissions.json:
    • 4 рівні: NONE / VIEW / EDIT / ADMIN
    • OP level 2+ автоматично отримує ADMIN
    • Команди: /lm perm grant <player> <level>, /lm perm revoke, /lm perm list
    • Model: модератор без OP може бути EDIT (керує точками без shutdown сервера)
  • Per-point bind rollback:
    • Кожна точка тримає історію 3 останніх прив'язок шаблонів
    • Іконка [↶] з'являється праворуч від stats badge коли є що відкатити
    • Один клік відновлює попередню прив'язку
  • /lm perm команди — управління permissions з консолі / чату
  • Loot Spawn Wand — special admin item for fast point creation
    • Right-click block: creates spawn point at top face
    • Shift+RMB: removes nearest point within 5 blocks
    • Available via Tools tab → "Give me a Wand"
  • Teleport to Point — [T] icon on every point card; closes GUI and teleports to point's dimension and coordinates
  • Multi-level Undo — stack of last 10 mass operations with labels ("Set timer 300s for group", "Delete group X", "Batch enable 12 points")
    • Server sends back remaining undo count via S2CUndoStatusPacket
  • Live Event Feed — all admins receive S2CSpawnEventPacket broadcasts when points successfully spawn loot; ToolsScreen shows last 20 events
  • Per-point Statistics — totalSpawns, spawnsLast24h, auto-resetting 24h window; shown as badge on point cards
  • Reverse Item Lookup — Tools tab "Find by Item": type item ID, see which templates contain it + how many points are bound; click a result to teleport
  • Health Check — Tools tab lists data issues: unbound points, empty templates, invalid radius, broken template references
  • Card-based GUI — SpawnPointListScreen redesigned with 3-row cards (status dot, name, badges, action icons) replacing 12-column table
  • Section-based Editor — TemplateDetailScreen split into 3 logical sections: ⚡ Spawn Behavior / 🎲 Item Selection / 📦 Loot Pool
  • Design System v2 — 4-level typography, semantic color palette, 15-icon enum, drawSection/drawBadge/drawCard/drawStatusDot primitives
  • Toast notifications — popup queue (success/warning/error/info) instead of inline error text; auto-fade in 3s

Changed

  • ParticleType resolution: ClientTickHandler.resolveParticleType() actually reads template.particleType instead of always rendering END_ROD
  • ParticleSettingsScreen: replaced 1.17-only types (ELECTRIC_SPARK, GLOW) with 1.16.5-compatible types (PORTAL, SMOKE, HAPPY_VILLAGER)
  • SpawnPoint.nextSpawnEpochMs/lastSpawnEpochMs → volatile for safe GUI reads
  • DataManager.load() now migrates old saves (fills missing particleType, spawnMode, items, dimension)
  • LootDropper.spawnLoot() now returns List<String> of spawned item IDs
  • applySettings() (TemplateDetailScreen): per-field validation with toast feedback ("Max radius must be ≥ MinR (N)") instead of silent ignore

Fixed

  • setHint() calls removed (method doesn't exist in 1.16.5 TextFieldWidget)
  • var keyword replaced with explicit Stream<Path> for Java 8 compatibility
  • Simulator iteration count clamped to 1–10000 client-side
  • AddItemScreen: live green/red border around item ID field based on validity
  • C2SSetTimerPacket rejects seconds < 1 server-side

[0.0.1] - 2026-05-30

Added

  • Core Spawn Management System

    • Create unlimited loot groups to organize templates
    • Define templates with configurable respawn timers (in seconds)
    • Bind spawn points to templates for loot generation
    • Individual timer overrides per spawn point
  • 5 Spawn Modes

    • GROUND: Spawn at exact coordinates
    • RADIUS: Randomize within min/max radius (in blocks)
    • ONLY_IF_EMPTY: Check 2-block radius before spawning
    • REPLACE_OLD: Clear area then spawn
    • ADDITIVE: Cumulative spawning up to maxItemsPerSpawn limit
  • Flexible Item Selection

    • Chance-based mode: Each item checked independently (0-100% per item)
    • Weighted pool mode: Select items by relative weight with configurable pick count
    • Per-item NBT tag support for enchantments and custom properties
    • Min/Max stack count per item
  • Admin Tools

    • Audit logging: All operations logged to logs/adminlog_YYYY-MM-DD.txt with timestamps
    • Automatic backups: Snapshots before mass operations, keeps 20 most recent
    • Single-level undo: Revert last bulk operation (timer set, group delete, etc.)
    • Particle visualization: Render spawn points with configurable particle types and radius
  • Full GUI Interface

    • Main menu with group list and search
    • Group detail view with template management
    • Template editor with spawn mode, radius, and item list
    • Spawn point list with dimension filter and search
    • Add item dialog with live icon preview
    • Spawn point simulator: Test drop probabilities across N iterations
    • Particle settings dialog for visualization customization
    • Scrollable lists with proper UI layout (no overlap)
    • Context tooltips on all buttons and fields in selected language
  • Data Persistence

    • JSON-based storage in config/lootmanager/
    • Atomic writes (temp file → rename) for corruption prevention
    • Import/Export groups to share configurations
    • Backup and restore functionality
  • Network & Server

    • SimpleChannel-based packet synchronization
    • Priority queue scheduler with 1-second tick granularity
    • Server-side spawn processing, client-side admin visualization only
    • Chunk loading verification before spawning
  • Localization (9 Languages)

    • 🇬🇧 English (en_us)
    • 🇺🇦 Ukrainian (uk_ua)
    • 🇩🇪 German (de_de)
    • 🇫🇷 French (fr_fr)
    • 🇪🇸 Spanish (es_es)
    • 🇧🇷 Portuguese Brazil (pt_br)
    • 🇨🇳 Chinese Simplified (zh_cn)
    • 🇯🇵 Japanese (ja_jp)
    • 🇵🇱 Polish (pl_pl)
    • All UI strings translated with context-aware hover tooltips
  • Command Interface

    • /lootmanager menu — Open management GUI
    • /lootmanager reload — Reload from disk
    • /lootmanager group create <name> — Create loot group
    • /lootmanager template create <group> <name> <secs> — Create template
    • /lootmanager template additem <template> <item> <min> <max> <chance> — Add item
    • /lootmanager point create <name> — Create point at your position
    • /lootmanager point bind <point> <template> — Bind template
    • /lootmanager point clear <name> — Clear spawned items
    • /lootmanager timer set <template> <secs> — Set template timer
    • /lootmanager timer setall <group> <secs> — Set group timer
    • /lootmanager show all / /lootmanager hide — Toggle visualization
    • /lootmanager spawn test <point> — Test spawn at point
    • Alias: /lm for all commands

Technical Specifications

  • Minecraft Version: Forge 1.16.5
  • Java: JDK 8 (Java 8)
  • Gradle: 8.11 with wrapper
  • Network Protocol: SimpleChannel v2
  • Scheduler: Priority queue-based, 1-second resolution
  • Storage: Atomic JSON writes to prevent corruption
  • Permissions: Requires OP level 2

Known Limitations

  • Single-level undo (one revert per operation)
  • Spawn radius uses circular calculation (not rectangular)
  • Chunk must be loaded for spawning to occur
  • Import files must be manually placed in config/lootmanager/imports/

Future Roadmap (Planned)

  • <input class="task-list-item-checkbox" disabled="disabled" type="checkbox" />Save fully async
  • <input class="task-list-item-checkbox" disabled="disabled" type="checkbox" />DataManager split into focused services; unit test coverage
  • <input class="task-list-item-checkbox" disabled="disabled" type="checkbox" />Visual chest-grid loot editor
  • <input class="task-list-item-checkbox" disabled="disabled" type="checkbox" />Cross-dimension template binding
  • <input class="task-list-item-checkbox" disabled="disabled" type="checkbox" />Vanilla datapack loot-table import
  • <input class="task-list-item-checkbox" disabled="disabled" type="checkbox" />API for other mods to integrate custom loot sources

This mod has no related projects