wavedefense-0.6.0.jar
Curse Maven Snippet
What's new
Changelog
[0.6.0] - 2026-08-08 — a shop that prices itself
Stocking a shop was the job that did not scale. A modern gun pack ships hundreds of weapons whose strength varies tenfold within a single class, and the mod asked an admin to price every one of them by hand, one screen at a time, with a bulk tool that filed the whole batch under one category. The result was an evening's work that produced a mess.
The mod now reads what an item actually does — a gunpack ships its own ballistics, and armour carries its protection in vanilla attributes — ranks it against its own kind, and prices it from that. An admin tunes about a dozen numbers instead of several hundred.
Around that sits the machinery to use it: an item browser with prices in each row, four presets that describe a shop as roles rather than goods, and duplicate control so the same offer cannot land twice.
Upgrading: the network protocol moves to version 13, so client and server must be updated together. Compile targets are unchanged — Forge 47.2.0, Minecraft 1.20.1, Java 17, official mappings.
Added — prices derived from what an item does
/wda shop autoprice <location> [sellRatio]reads a shop and prices it. Weapons are ranked against their own class rather than an absolute scale, which is what makes it work on a collection nobody has seen: the best rifle here sits at the top of the rifle band whatever its numbers are. Measured against a real 268-gun, six-pack arsenal, rifles spread 1485 to 4995 and the most expensive was the one with 25 damage at 923 rpm and full armour bypass — found without being told about it.- Armour needs no compatibility layer at all: defence and toughness are vanilla attributes that any modded armour inherits.
- Both buy and sell prices, from one resale fraction.
- Anything unmeasurable is reported as such rather than guessed at, and left alone by default.
Added — item browser and shop presets
- Browser (editor → Огляд): everything installed, searchable, filterable by mod, with buy and sell boxes in each row and prices already filled in. "Add all" applies to the filtered view, each entry with its own category — which is what the old bulk add should have been.
- Presets (editor → Пресет): Classic buy menu, Deathmatch, Wave defence, Basics only. A preset is a list of roles, not items — "three pistols, cheap to mid" — so it fills from whatever is installed and skips what is not. A preset naming specific gun ids would have quietly made this a mod that requires a 300 MB gun collection.
- Presets replace the shop, and say how many entries that will delete before doing it.
- Attachments are priced too — scopes, muzzles, grips, stocks, lasers, extended magazines. Type carries most of the value, and weight counts against it, because in Tacz a heavier attachment slows the weapon.
- Economy travels with the preset. A buy menu without money around it is a shop nobody can afford.
Fixed — the statistics and rating did not work
Four separate defects, which together made the whole progression system look inert.
- The lifetime profile was never sent to the client. It was written on every kill, wave and match end, and saved to disk — but no packet carried it and no screen mentioned the class, so the level a player was accumulating existed only in a file they could not read. It now syncs on join and after anything that changes it, and appears at the top of the stats screen.
- Every run on a non-Normal difficulty was invisible. Records are filed under the mode key plus the difficulty's suffix (
PvE_hard,PvE_nightmare), and the leaderboard only ever asked for the bare key. There is now a difficulty selector. - Endless mode had no tab. Its records were written faithfully to
PvE_ENDLESSand had nowhere to be shown. - The two PvE boards ranked on different quantities. A cleared run recorded (points, waves) while an endless run recorded (waves, points) — the board sorts on the first and labels the second, so one of the two had its columns swapped under the headings. Both now rank on waves, which is what the run was about.
Fixed — point shops received nothing from any mass operation
Every bulk path assumed the global shop and hardcoded an empty point name. On a location in point mode that list is not the shop players buy from, so a bulk add filled something nobody would ever see and left the real shop empty — it looked stocked in the editor and sold nothing in game. The browser, the presets and the upload queue now carry an explicit target, and offer a picker for it.
Fixed — duplicate entries
The same offer could be added any number of times. It is now refused with a reason on a single add, skipped silently on a bulk upload (re-running one over an existing shop is ordinary, not an error), cleaned on import, and /wda shop dedupe <location> clears out a shop that already accumulated them. Identity is what a player can tell apart: price is not part of it, stack size and availability trigger are, and NBT is read only in the part that names the item — so an arsenal does not collapse into one entry.
Fixed — the category row ran off the screen
The shop item editor laid its categories out as one row of fixed-width buttons. That fitted the four that existed when it was written; with the gun buckets there are twelve, so the row ran some 740 px from its left edge and the fixed width clipped the longer labels. Both faults were the same mistake — a layout that assumed its content. It now sizes to the text and wraps.
Internal
- Tacz remains entirely optional and is reached only by reflection: no hard imports, every entry point guarded, no hardcoded gun ids or pack names. Verified, not assumed.
- Lang keys: 1578 per locale across 8 locales, parity checked.
- Test suite: 109 tests, all passing.
[0.5.3-dev] — audit fixes
Follow-up work on the findings of the full read-only audit, plus three problems found in play. No version bump yet.
Upgrading: the network protocol moved to version 12 (RenameLocationPacket was appended, and BulkAddShopItemsPacket gained a shop-point field), so client and server must be updated together.
Fixed — a player with 973 points was told 900 was too expensive
Two separate defects, both rooted in the same thing: a location's identity is its name, but several places held a reference to the object instead.
- The editor's Save wiped live points.
UpdateLocationPacketrebuilds the whole location from the tag the client sends — and that tag includesplayerPointsas the editor client saw it when it opened. Anything players earned since was overwritten.MergeLocationPackethas always protected these runtime keys; this path never did. It does now. - Everyone kept reading the old object.
LocationManager.updateLocationswaps the entry in its map, but everyPlayerWaveDatastill pointed at the instance the player joined with. The HUD read its points from there; the shop read them from the map. The two numbers drifted apart, which is exactly what the report described.updateLocationnow rebinds live players onto the new instance and pushes a corrected figure immediately.
New test RuntimeStateProtectionTest fails if either handler stops consulting the runtime classification, or if playerPoints ever leaks into an editor section.
Fixed — info panels stood at 0 0 0 and outlived the location they belonged to
- The panels teleported themselves to the world origin. Their appearance was applied by calling
Entity.load(tag)— the whole-entity deserializer — on a tag holding only display fields.loadreads position out of"Pos", and an absent list reads as zero for every component, so one tick after a panel was placed it was moved to 0 0 0. The same call also resetNoGravity,InvulnerableandSilentback to their defaults, every second, undoing what creation had just set. Verified against the 1.20.1 jar rather than assumed. The display fields now go throughreadAdditionalSaveData, which reads only what the display itself owns. - A panel could outlive everything that knew about it. The mod's record of a panel is a UUID inside a
LocationSession— runtime state that dies with the process. After a restart, or when a location was deleted between runs, the entity was still standing there and nothing could reach it. Panels are now stamped with their location's name, the same way spawned mobs are, so they can be found without our bookkeeping: deleting a location removes them, and a sweep every 30 seconds clears any left over from an earlier session. - Deleting a location now cleans up its panels from
LocationManager.removeLocationitself, so all three delete paths — the menu,/wda location delete, and an import replacing an existing location — get it without any of them having to remember.
Fixed — deleting a shop item did nothing, and an imported shop looked like it had not arrived
Three defects stacked on top of each other here; each alone was enough to make shop editing feel broken.
- The chunked list channel was throttling itself to death.
ReplaceLocationListPacketcarried a 50 ms per-packet rate limit, but it is one slice of a burst:sendListemits every chunk back to back, then the editor does the same for the next list. So chunk 0 went through and everything after it was dropped — a shop small enough for one chunk saved but lost its shop points, and a shop needing two chunks never completed at all, so the whole save was discarded 30 s later. The limit is gone; a cap on concurrent transfers per player and on chunks per transfer bounds the ingress instead, which is the right shape for a stream. - Deleting was the only shop operation that was not sent to the server. Adding and editing have gone through
ShopItemOpPacketsince the shop got its own per-item protocol; deleting only mutated the client's copy and depended on that broken save. It is now the same kind of immediate server operation as the other two. - The shop editor read a stale location for its whole lifetime. A shop is stripped out of the location broadcast, so the editor asks for one on open — and the reply arrives as a different object, which replaces the entry in the client cache while the screen keeps the original. That is why importing a shop from another location appeared to do nothing: the editor was showing the pre-import snapshot, and saving from it pushed that snapshot back, undoing the import. The editor now follows the sync.
Added — shop tiles can be sorted
- A cycle button in both the player's shop and the admin's editor: as configured, by name, by mod, by type, or by price. A generated Tacz pack arrives in the generator's order, which is no order at all at several hundred entries.
- "As configured" stays the default, because a hand-built shop is usually arranged deliberately and that intent is worth keeping until someone asks otherwise.
- There is no separate "by datapack" mode and none is missing: a datapack's items carry its own namespace exactly as a mod's do, so by mod covers both. Nothing on the item records which datapack loaded it.
- Only the view is reordered. Buy, sell, edit and delete all address an entry by its index in the real list, so sorting can never make a tile act on the wrong item —
ShopSortTestfails if a sort ever drops, duplicates or reorders anything but the view.
Added — locations can be renamed
- A rename could not ride on the normal save: that handler resolves its target by the name inside the payload, so an edited name matched nothing and the save was dropped without a word. It now has its own packet, its own button on the editor's General tab, and
/wda location rename <old> <new>. - Leaderboard history moves with the name instead of being stranded.
- Refused while a run is in progress, with the reason stated. The name is the session key and the string stamped on every spawned mob; swapping it mid-run would orphan all of them at once — those mobs would stop paying points and never be cleaned up.
- The editor closes after a rename, because every other change it sends is addressed by the old name and would silently stop applying.
Added — admin commands for the things that had none
/wda points give|set|get <players> <amount> [location]— there was no way to hand out points for testing, compensate a player after a crash, or even see what the server thinks someone has. With nolocationit uses the arena the player is standing in./wda location list— names, mode, and whether a run is live; click a row to fill in the info command./wda location create <name>and/wda location delete <name>(delete asks to confirm and sends everyone out first, so nobody is left holding a location that no longer exists)./wdanow exposes the whole tree. It offered three of sixteen subcommands; everything else lived only under/wavedefense-admin, which is long enough that nobody finds it by accident. Each subcommand still checks its own permission level — this widens discoverability, not access.- Every
<location>argument now tab-completes from the locations that actually exist, including on the subcommands that already existed.
Fixed — items no longer vanish into a full inventory
Inventory.addreturns a boolean saying whether the stack fitted, and mutates the stack it is given so a partial fit leaves the remainder behind. Five places called it and dropped that answer: shop purchases, completion rewards, starting kits (PvE and PvP) and per-team starting items. With a full pack the item simply ceased to exist — you paid the points and got nothing, or finished a run and never saw the reward, which is exactly the moment a pack is most likely to be full.- All five now go through one helper,
util/ItemGrant: whatever does not fit lands at the player's feet, the way vanilla behaves everywhere else.
Fixed — a location with totalWaves = 0 handed out the completion reward on entry
- Victory is decided by
currentWave > totalWaves, andcurrentWavestarts at 1. At zero the very first tick counted as "all waves cleared": players walked in, instantly "completed" the location and collected the completion points and reward items without a single mob spawning. The wave-config validator never looked at the field, so nothing caught it. setTotalWavesnow clamps to 1–9999,setTimeBetweenWavesto 0–86400 (zero stays legal — it means "next wave immediately"), andsetCompletionPointsRewardcannot go negative.LocationSerializer.loadroutes these three through the setters instead of assigning the fields directly, so a file saved by an older build or edited by hand is repaired on load.- The validator now also rejects
totalWaves < 1outright, as a second line of defence.
Fixed — shop packets trusted whatever the client sent
- Neither
PurchaseItemPacketnorSellItemPacketchecked that the sender was actually in the location, or anywhere near the shop point they were trading with. The client's shop screen enforces both, but the packet alone did not — so a hand-sent packet could trade in any location, from anywhere in the world, without ever entering. - Both now go through
network/ShopAccess, with a 3-block grace on the point radius so a step or a lag spike does not reject a purchase the player considers legitimate.
Changed — silence removed from three places that hid real misconfigurations
- Config warnings were logged at DEBUG, i.e. seen by nobody. They are the list that explains "why is my location behaving strangely" — unknown entity types, waves with no mobs, absurd timers. They now log at WARN, once per run rather than once per player.
PLAYER_HAS_ITEMtriggers swallowed bad item ids. A typo likemincraft:diamondmeant the trigger silently never fired, with nothing in the log. Each unresolvable id is now reported once. The same loop existed in three copies and is now one method.- Trigger waves swallowed spawn failures. Picking an entity type that is not a mob (an arrow, a dropped item) made the wave quietly not happen. Failures are now collected per mob type and reported once per wave, instead of per attempted spawn.
- Five subsystems in the main tick loop caught their own errors and said nothing — HUD refresh, teammate sync, bbox outline, mob retargeting and autosave.
WaveManageralready hadsafeTick, which contains a failure and reports it at most once per 10 s per subsystem; those five now use it. A frozen HUD or an autosave that has been failing for an hour now leaves a trace.
Changed — dead code removed from the wave-config validator
- The class carried eight fluent threshold setters that nothing called, and a whole adaptive-performance subsystem — tick sampling, an overload flag, a scale factor — whose result no code ever read. Real auto-scaling is done by
WaveAutoScaler. All of it is gone; the thresholds are constants. - Three rules that were declared and never enforced now actually run: duplicate wave numbers, mob types per wave against
maxMobTypes, and spawn points againstmaxMobSpawns.maxBossMobsPerWavewas dropped — there is no boss flag in the data model for it to check.
Performance
BoundaryManager.tickBorderParticlesran a filtered stream over every online player once per boundary location, every second — O(locations × online). It now groups players in a single pass and allocates nothing when nobody is in a location.
Internal
- Fifty-four new lang keys across all 8 locales (1573 each, parity checked).
- Test suite: 109 tests, all passing.
This mod has no additional files

