promotional banner

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.8.jar

File namelootspawnmanager-0.2.8.jar
Uploader
Rayman_GCRayman_GC
Uploaded
Sep 19, 2026
Downloads
4
Size
655.2 KB
Mod Loaders
Forge
File ID
8919860
Type
R
Release
Supported game versions
  • 1.16.5

Curse Maven Snippet

Forge

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

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.8] - 2026-09-18 (Loot While Nobody Is Around)

One report drove this release: a tester with thirty-second timers came back to find every point empty except the ones near where he had logged out. Points spawn only in loaded chunks, and a chunk is loaded only while a player is near it — so on a survival map loot appeared only around players, the opposite of what the mod is for. This release makes a point spawn when its timer says so, wherever it is, on a map of any size, without keeping anything permanently loaded.

Upgrade note: the wire protocol moves "13" → "17". Two packets were added (mass spawn/clear/wave, chunk-load queue state) and three changed shape (both settings packets gained five fields; template settings gained keep chunks loaded), so a 0.2.6 client would decode the tail of a settings packet as garbage. Data files load unchanged; the new template field defaults to off. New templates default to the top up fill policy; existing ones keep what they had.

Added — loot while nobody is around

The tester's decisive report: every point with a 30-second timer was empty when he came back, except the ones near where he had logged out. Points only spawn in loaded chunks, and a chunk is loaded only while a player is near it. Four mechanisms now cover that. The first is the one that keeps the mod's promise:

  • Load the chunk when the timer fires (on by default, Load chunk on timer in Server Settings). A point that comes due in an unloaded chunk no longer waits for a player, a retry or a wave: the scheduler asks the world for a temporary chunk ticket, the chunk loads asynchronously within a few ticks, the point spawns, and ten seconds later the chunk unloads itself with the loot inside. Delay is a second or two instead of a minute. Each such load reads a chunk from disk, so Max timer loads / s (default 20; a walking player loads about seven a second) caps the rate — and beyond the cap points queue: a point that finds the second's budget spent is given the next free moment as its slot and comes back exactly then, in arrival order, with no retry limit. Points in the same chunk share a slot, so a base of twenty points in two chunks costs two loads, not twenty. After a mass respawn the herd drains at the configured rate and, because every point's next timer counts from the moment it was actually served, it stays spread out afterwards. Loot therefore appears on a map of any size: not everywhere at once, but everywhere, in order. The queue is visible in Tools ("Chunk-load queue: 340 points, ~42 s") and via /lm queue; the log notes it once a minute when the wait exceeds half a minute. Changing the limit re-spaces the queue immediately. A chunk that fails to load three times falls back to the old retry. A point whose zone spans several chunks polls once a second while its tickets are alive, so a wide radius placement is not counted as a failed attempt for every chunk that comes in. Kept chunks, the wave and the catch-up below are unchanged; this simply makes the common case not need them. Three refinements keep the cost proportional to what actually needs loading: a point whose loot is still lying there untouched from the last cycle (the fill policy would add nothing) and whose chunk nobody has loaded since skips the load entirely until someone visits, so demand tracks player activity rather than point count; a template with the always add policy loads only the point's centre chunk plus the chunk the loot lands in — the radius position is rolled when the ticket is requested — instead of the whole zone (at most two chunks instead of 25–36 for radius 40); and a slot that turns out not to be needed, because a player loaded the chunk first, is handed back to the queue. Health Check now warns when the worst-case demand at current timers exceeds 70 % of the limit, and when a template's radius and fill policy make each timed spawn load more than nine chunks.
  • Catch-up on chunk load (always on). A point that came due while its chunk was unloaded used to be retried after unloadedRetryMs — a full minute. The player walked in, looked at an empty point and left before the retry fired. Now loading a chunk puts its waiting points on "now" and the next scheduler tick serves them. Indistinguishable from "it spawned while I was away", and it costs nothing.
  • Loot wave (/lm wave, the Tools button, or automatically every N minutes via Loot wave every in Server Settings). Loads each point's chunk in turn, from 0/0 outwards, for about ten seconds, so overdue points spawn as if someone were there — then the chunk unloads by itself. It uses vanilla chunk tickets with a timeout: asynchronous, self-releasing, nothing to book-keep, nothing left loaded afterwards. The summary reports chunks visited and points that dropped loot.
  • Keep chunks loaded (per template, Chunks: kept loaded). Real background spawning via ForgeChunkManager, for the few templates that need it. Forge forces at distance 2, so every kept chunk keeps a 5×5 area loaded with the centre fully ticking — which is why the server caps the total (Max kept chunks, default 64) and tells you when the cap is hit. Chunks are forced a few per second rather than all at once, because Forge loads a forced chunk synchronously in the tick that forces it.

Also from the same report:

  • Timer changes take effect on points immediately. Changing a template, group or point timer now recomputes each point's next spawn from its last spawn. Set 30 s on a point that spawned ten minutes ago and it is overdue at once. Before, a point scheduled two hours out stayed two hours out, which the tester rightly called untestable.
  • Respawn all / clear all — /lm spawn all, /lm spawn template <name>, /lm clear all, /lm clear template <name>, plus buttons in Tools and in a template's To all points dialog. Work runs in slices of 100 points per scheduler pass (once a second, so a thousand points take about ten seconds) and the summary breaks the result down by reason: loot dropped, nothing rolled, count rolled zero, blocked by policy, unknown item ids, chunk unloaded. With load chunk on timer on, the unloaded ones are put on "now" and go through the chunk-load queue, so the button really does reach the whole map. That breakdown is also the answer to "with 0..2 everywhere was 0": it says which of those it was.

Fixed — from the same report

  • New templates re-migrated on every restart. The schemaVersion fix above made the field default to 0, but the constructor did not set the current version, so a template created by this build would pass through the legacy migration on the next load and lose its fill policy and item range. The constructor now sets it, and the legacy mapping only runs for records that actually carry the old spawnMode.
  • Deleting a template or group changed nextSpawnEpochMs on points still inside the scheduler's priority queue, corrupting the heap; the scheduler is now rebuilt after either deletion.
  • Shifting points moved their coordinates without re-indexing the scheduler's chunk index, so the catch-up looked for them in the wrong chunk. Shift also synced only the admin who did it.
  • TOP_UP reported "already at the maximum" when the item-count roll itself was zero and the zone was empty. The two causes are now told apart.
  • The "chunks kept" state was static and survived Save & Quit on an integrated server, so reopening a world forced nothing while reporting the old count.
  • Keeping chunks loaded only forced a point's centre chunk; a radius placement that landed in the next chunk over still got "chunk not loaded". The whole effect zone is forced now.
  • Every "is the chunk loaded" check used World.isLoaded, which only looks at the chunk's ticket level — it is true from the moment a chunk is requested, while it is still being read from disk. Spawning, counting or clearing loot in that window blocked the server thread until the load finished. All checks now use getChunkNow, which answers without blocking; a chunk in flight simply counts as not ready and is retried a second later.
  • Chunk tickets (wave, and now the timer load) were issued at distance 1, which is ticket level 32: the centre chunk ticking plus eight BORDER neighbours — nine chunks read for one spawn. Distance 0 gives level 33, a single full non-ticking chunk, which is all a spawn needs.

Fixed — third tester round

  • Region generator saw only the first or the last floor. The floor pattern put every candidate on the bottom of the box and the snapper searched downward from the candidate, so intermediate floors of a building could never be hit; the random pattern mostly landed on the roof for the same reason. The snapper now collects every floor of a column inside the box and picks one at random — with indoors it requires a roof over that floor.
  • Minimum gap silently threw points away. Candidates closer than the gap were rejected with no replacement, so 15 requested points came out as 3. Counted patterns now generate in rounds: each round asks for the shortfall with a margin and keeps its distance from the points already accepted, until the count is reached, the region is exhausted, or the read budget runs out. The reply says how many were requested, generated, and rejected.
  • The brush kept the previous region. After generating, both corners stayed on the item, so the next click set a second corner against the old first one and reopened the old selection. Generating now clears the brush, and a click with both corners already set starts a new one.
  • Server time-outs during the first logins. Two causes. Keep chunks loaded forced chunks through Forge, which loads each one synchronously in the server tick — on ungenerated terrain that is world generation in the middle of a tick, eight times a second. Chunks are now pre-loaded with an asynchronous ticket and forced only once they are in memory, so the synchronous call is a cache hit. And the scheduler's per-pass limit was a point count only; a burst of a few hundred spawns near a player who just logged in could hold the tick for a quarter of a second. Both the scheduler pass and the mass job now also stop at a time budget (15 ms and 10 ms) and continue next pass.

Added — server operations

  • /lm status — points, scheduled, unbound, templates; due within 5 minutes, waiting for a chunk, skipped as untouched; the chunk-load queue; kept chunks, wave and mass job. Everything a console admin on a dedicated server could not see without the GUI.
  • Loot lifetime by the clock, on any server core. With an explicit lifetime (template or Default item lifetime), each dropped item carries its expiry time and is made immortal for the core (age −32768, which vanilla never advances). The mod then removes it itself: on the second it expires if its chunk is in memory, when its chunk next loads, or when its zone is counted. This is what the tester on Mohist needed: hybrid cores replace both vanilla's item age and Forge's lifespan with their own item-despawn-rate and age even inactive items far from players, so loot vanished after five minutes whatever the template said. A point whose loot has expired is served again on its next timer instead of being skipped. Without an explicit lifetime nothing changes: loot waits for the player and the core's own despawn rule applies once they arrive. If the world or a plugin refuses an item spawn (Bukkit ItemSpawnEvent), the log says so once a minute with a count.
  • Max loot items per zone (Server Settings, default 48, 0 = off) for the always add policy: once that many items lie in the point's zone the point stops adding until they are picked up or expire. Counted in the centre chunk only, so it costs no extra chunk loads.
  • New templates default to top up. Always add remains available and is now bounded by the cap above.
  • Load guard. The mod reads the server's average tick time once a second and backs off on its own: from 40 ms the chunk-load limit is halved and the per-pass time budgets cut to a third; from 50 ms chunk loading for loot stops entirely — no timer tickets, no forcing, no wave — while spawning in chunks that are loaded anyway continues. It returns to full rate after ten seconds below 35 ms, and logs each transition. It also measures its own longest piece of work per tick (scheduler pass, points save, chunk keeper) and logs a warning once a minute when that exceeds 25 ms, naming the culprit — so the next "the server lags" report comes with a number. /lm status shows the current level and the longest work this minute.
  • The chunk-watch handler caches dimension names instead of building a string per event.

Fixed — introduced by this release and caught in review

The queue was reviewed adversarially before the first build; these were real and are fixed:

  • The "loot lies untouched" skip was set even when a player had the chunk loaded at spawn time. That player could take the loot and leave, and no chunk-load event would ever clear the flag — the point stayed silent until someone else walked in. The flag is now set only when the mod itself loaded the chunk, a skipped point stays marked waiting so the next visit re-checks it at once, and clear loot clears the flag.
  • A queue slot from the past bypassed the rate limit. After an integrated-server pause or a long stall every queued point came due together and each issued its tickets without a budget check. A slot older than 1.5 s is now re-queued, and any external change to a point's time or position (timer edit, move, shift, rebind) drops its slot and its pre-rolled radius target.
  • A pre-rolled radius position survived moving the point: the next spawn would have loaded and dropped loot at the old location.
  • Points in one chunk shared a slot regardless of zone size: a wide top up zone could ride a one-chunk reservation and load 36 chunks in a second meant for one. Only single-chunk zones share now.
  • Fill policies that count loot in the zone (only if empty, top up, replace old) could count through a chunk that was still being read from disk, which blocks the server thread until the read finishes. They now require the whole zone to be in memory and otherwise wait a second. The zone itself now uses the same bounds as the entity scan (the box grown by two blocks), which it did not before, so a point two blocks from a chunk border no longer scans a chunk the zone never covered.
  • The "loot lies untouched" skip could still stick when a player was within view at spawn time (the chunk was already loaded, so no load event would ever clear the flag). It is now set only when no player is within view distance, and a player coming into view of a chunk (ChunkWatchEvent) clears it and catches up waiting points, exactly like a chunk load.
  • Respawn everything skipped points in unloaded chunks entirely: they were only flagged for catch-up and nothing appeared until each point's own timer fired. They now go through the chunk-load queue.
  • Server Settings did not fit at GUI scale 4. Fifteen rows at the minimum row height need 225 px and the window has about 200, so the last rows were painted under Save/Cancel or off the screen. The list scrolls now; rows outside the window are hidden and inactive. Typed but unsaved values also survive a full resync (which rebuilds the screen) — untouched fields still pick up the value another admin just saved.
  • The To all points dialog counted disabled points in "Respawn N points now?" while the server acts on active ones only; the counts match now.
  • A malformed config.json or permissions.json (a stray comma from hand editing) aborted server start; both now log the error and fall back to defaults, as points.json already did.

Known limitations

  • Verified by static review and adversarial re-review only; nothing in this release has been run in game. The queue's numbers (drain time, wait) come from a model of the scheduler, not from a server.
  • A template whose fill policy counts loot in the zone still loads the whole zone per timed spawn: radius 40 is 25–36 chunks. Health Check warns; the fix is a smaller radius or always add.
  • Loot dropped into a chunk nobody visits never despawns (the chunk does not tick), so always add accumulates until someone arrives. That is intended for a survival economy, but a template with a short timer and always add on a rarely visited map fills its chunk with entities.

[0.2.6] - 2026-09-14 (Tester Feedback, Eight Features, and the Bugs They Uncovered)

Everything a tester asked for, eight more improvements on top, and the defects that the review of all that turned up — including three that had been quietly broken for several releases.

Upgrade note: the wire protocol moves "11" → "13". Ten packets were added and four changed shape, so a 0.2.4 client would decode the tail of a settings packet as garbage. templates.json and points.json from 0.2.4 load unchanged and are migrated in place on first load; the migration is behaviour-preserving, described under Changed below.

Fixed — long-standing

  • Template migration had never run. LootTemplate.schemaVersion defaulted to the current schema, and Gson executes field initialisers before it reads the JSON, so a record with no schemaVersion arrived already looking migrated. Every legacy field the migration exists to convert was therefore ignored. Anyone upgrading would have silently lost their spawn mode and item counts. The default is now 0, which is what "this record predates the field" actually looks like.
  • Four command messages crashed the server when used. TranslationTextComponent has its own format parser that accepts only %s and %%; cmd.point_created used %.1f, and cmd.timer_set, cmd.timer_set_group and cmd.point_list_entry used %d. Each threw TranslationTextParsingException at render time. Three more keys carried a bare % and rendered as Format error: Ch%.
  • Translated messages put values in the wrong slots. Positional %s is filled in order, so a translation that reorders the sentence swaps the arguments. /lm timer printed the template name where the seconds go in eight of nine languages, including Ukrainian. Six keys are now written with explicit %1$s indices in the languages that reorder.
  • An item id with a space or a capital letter killed the spawn. new ResourceLocation(id) throws for those, and nothing validated ids added through /lm template additem or written into the JSON by hand. The exception escaped the whole spawn, so the Test spawn button answered with nothing at all and a scheduled point silently stopped producing — losing the valid items that came after the bad one. Parsing is now defensive and a bad id is reported as a skipped entry.
  • Undo of a mass edit did nothing. The snapshot was taken after the points were mutated, so the stack held the already-changed state.
  • /lm ... test reported success when nothing spawned, and 44 server-side messages were hardcoded English (one was hardcoded Ukrainian) in a mod that ships nine languages.
  • Restoring a backup, generating a region and undoing all resynced only the admin who acted. Everyone else kept templates and points that no longer existed on the server.
  • duplicatePoint did not copy the schedule fields; duplicateTemplate lost priority, itemLifespanSeconds and the schema version.
  • The backup list restored the wrong backup. Rows were 14 px tall and their Restore buttons 16 px, so each button's top two pixels belonged to the row above.

Fixed — introduced by this release and caught in review

  • The map's "To me" button was registered as a click zone before the point popup, and HitList gives the click to the first zone that matches — so the popup's Delete, Toggle, Bind and Teleport icons did nothing while it was open.
  • REPLACE_OLD cleared the ground and could then report "nothing happened" and leave the cached loot count stale. There is now a distinct status for "old loot cleared, nothing new rolled".
  • The floor-snapping mode rejected every candidate on a flat selection, because it required a solid block strictly below the selection's lowest level.
  • Loot-count zone checking tested four corners, which leaves unloaded chunks in the middle of a wide radius — and "0 items" and "unknown" are opposite answers, so it now checks every chunk the zone touches, with the radius capped at 32 blocks.
  • "Apply to points" said "no points are bound to that template" when points existed but already held the requested values.
  • The card's third row mixed absolute and right-edge offsets; timers and the schedule badge overlapped by 36 px at the default width. TemplateDetailScreen never narrowed its 460 px panel, so at GUI scale 3 on a 1280×720 screen it ran off both edges.
  • The point-loot toggle captured a SpawnPoint that a delta later replaced, so it stuck after the first click. Fields in the mass-apply dialog were cleared by any full resync.

Added — from tester feedback

  • Spawn behaviour is two independent axes. SpawnPlacement (on ground / in a radius) and FillPolicy (always / only if empty / replace old / top up) replace the single five-value mode, which conflated "where does loot appear" with "what happens to loot already lying there" and made half the sensible combinations unreachable.
  • Items per spawn is a range. 0–n instead of a fixed count, so a point may legitimately produce nothing. The two former fields that both answered "how many items" are now one.
  • Pool items are editable in place — chance, weight, counts and NBT — instead of delete-and-re-add, which also moved the entry to the end of the pool.
  • The map draws you, with facing, an edge marker when you are off-screen, and a "To me" button bound to G. The dimension you are standing in always has a tab, even with no points in it.

Added — further improvements

  • Apply a timer, an active-hours window, or a reset to every point of a template at once.
  • Test spawn reports what dropped and why nothing did. Eight distinct outcomes: the roll came up empty, the policy blocked it, the count rolled zero, the chunk is not loaded, the ids are unknown.
  • Copy a loot pool between templates, appending (duplicates skipped) or replacing.
  • See how much loot is lying at a point right now — the one number that says whether a point is working or being cleared faster than it respawns. "0 items" and "chunk not loaded" are shown as different answers.
  • Schedule by in-game time and by weekday. The server's timezone offset is sent to the client so "is it active now" is answered by the server's clock, not the viewer's.
  • Reverse item search: which templates and which points contain a given item, with its chance or weight. Entirely client-side. A point whose own pool overrides its template is not counted, because it does not roll the template's pool.
  • Undo shows who made the change and when, confirms before reverting someone else's work, and refuses outright if the stack moved between your screen and your click.
  • The region generator snaps points to surfaces — top block, floors, or indoors only — and can enforce a minimum gap. The snapping is a pass over the generated candidates, so "no snapping" behaves exactly as it did before.

Changed

  • templates.json and points.json migrate on load, on backup restore, on import and on undo, each through one entry point instead of the three divergent copies there used to be. Migration preserves behaviour exactly: the old fixed item count becomes a range whose bounds are equal. One deliberate exception — a schedule window whose start equals its end used to mean "never active" and is normalised to "always active"; it was not reachable through the interface.
  • Chance mode shuffles before truncating to the item limit. The cut used to follow pool order, so the first entries always won and the last never appeared.
  • S2CAdminNotifyPacket gained the TYPE_SUCCESS and TYPE_ERROR constants that the whole codebase had been passing as bare 2 and 3.

Known limitations

  • Still verified by compilation and static review only. Nothing in this release has been run in game. The three worst bugs above were in code that earlier releases reported as working, which is the argument for reading the diff rather than trusting the summary.
  • Undo remains whole-dataset snapshots. It is now attributed and guarded, not scoped.

This mod has no related projects