wavedefense-0.3.0.jar TESTED
Curse Maven Snippet
What's new
Changelog
[Unreleased] — gameplay depth: endless, modifiers, difficulty, progression
Four features that give a location a reason to be played more than once. All are opt-in per location and default to off, so existing arenas behave exactly as before. Existing saves load unchanged; the new keys simply take their defaults.
Added — endless mode
- A PvE location can now be marked endless: waves never run out, there is no victory, and the score becomes how far did you get.
- The wave list already cycled internally (
(currentWave - 1) % waves.size()), so this needed no new spawn logic — only the two victory gates now defer to the flag. - Mob health and damage scale by +N% per completed loop through the wave list, configurable (default 10%). Scaling is linear, not compounding: compounding turns loop 5 into an unplayable wall, which reads as a bug rather than a challenge. Mob counts already grow through each wave's existing
growthPerWave, so they are deliberately left out of the loop multiplier. - Endless runs get their own leaderboard (
PvE_ENDLESS) ranked by wave reached rather than points — the two are not comparable numbers. - The record is written on death, not on victory. Endless never reaches
triggerVictory, so without this an endless location would silently never produce a leaderboard entry at all. Non-endless deaths are still unranked: dying on wave 3 of 10 is not a result worth listing beside a finished run.
Added — wave modifiers
- Every N-th wave (default 3) rolls a random twist, announced in chat with a one-line explanation of what it does. The waves in between run clean, which is what makes a modifier feel like an event rather than ambient noise — and gives players a wave to recover on.
- Eight modifiers: Swift, Armored, Regenerating, Enraged, Tough, Phantom (invisible), Volatile (explodes on death), and Venomous (poisons on hit).
- Admins can restrict the pool per location; selecting none means all are eligible, and the editor label says so rather than leaving eight empty boxes looking like a misconfiguration.
- Volatile explosions never damage terrain (
ExplosionInteraction.NONE). Arenas are hand-built, and a modifier that quietly demolishes the map over a few runs would be worse than no modifier at all. The blast still hurts players, and it fires on any death — an environment kill is just as dangerous to be standing next to.
Added — difficulty presets
- Easy / Normal / Hard / Nightmare, set per location, scaling mob health, damage, count, and point rewards.
- Rewards scale with difficulty, or nobody would ever pick anything above Normal.
- Each tier ranks on its own leaderboard so a Nightmare clear never has to compete with an Easy one. Normal keeps the unsuffixed key, so existing records stay exactly where they are.
- This composes with, rather than replaces, the existing
WaveAutoScaler: the preset sets the baseline that the adaptive scaler then nudges around, so Nightmare stays harder than Easy even after the scaler settles.
Added — lifetime player progression
- A new per-player profile persists across runs, locations, and restarts: waves survived, best wave, kills, points, matches played/won, deaths, playtime, and XP.
- Level curve is quadratic (
1 + floor(sqrt(xp / 100))) — early levels arrive fast enough to notice, later ones stay meaningful. - Stored in
world/data/wavedefense_profiles.datthrough the same atomic, debounced write path as locations and leaderboards, so a crash mid-write cannot corrupt it. - Profiles are never pruned: a player returning after months keeps their level.
Fixed — the crash-safe write contract was only half-implemented
atomicWriteCompressedwrites through.tmp → .bak → ATOMIC_MOVEand documents the guarantee that "after any single crash, the reader can recover from either the main file or the.bak— never both missing." Only one of four readers actually honoured it.LeaderboardManager,PlayerProfileManagerandWaveManager.loadRuntimeStatecaught the read failure and silently started from an empty state — discarding a perfectly good backup sitting next to the corrupt file. A single bad shutdown wiped the leaderboard.- The read half now lives next to the write half as
NbtHelper.readWithBackup, and all four readers go through it. When both copies are unreadable the primary is renamed to.corruptedso the next save cannot destroy evidence. - An in-flight save could still be lost on shutdown.
flushPendingWrites()drained the queue but did not wait for a write that was already running: that task had taken its snapshot, so the drain found nothing, returned, and the JVM killed the daemon save thread mid-write. Combined with the above, that was unrecoverable. Flush now ends with a completion barrier — the save executor is single-threaded, so a no-op submitted at that point cannot start until the running write has finished. - The executor is deliberately not shut down: an integrated server can quit to title and open another world in the same JVM, and a terminated executor would make every later save throw. The barrier gives the same guarantee without that.
Fixed — filename handling in import/export
All six import/export packets built paths from client-supplied strings, and each did it differently. Consolidated into network/FilePathGuard:
ImportWavePackethad no containment check at all — the only one of the family without one.ImportLocationPacketandImportShopPacketcompared canonical paths without a trailing separator, so a sibling directory sharing a name prefix (shops_evilagainstshops) passed the check.ExportShopPacketconcatenated an unsanitised location name straight into a path.- Location names were validated by
DuplicateLocationPacketbut not byCreateLocationPacket— and that unvalidated name is what fed the export filename. The rule now lives once, inLocationManager.isValidName, and both packets use it. Invalid names are rejected with a translated message rather than silently ignored.
All of these required op level, so this is hardening and consistency work rather than a closed hole.
Added — tests
NbtHelperBackupTest— 9 tests: backup fallback for a corrupt and for a missing primary, fresh install treated as normal, quarantine when both copies are bad, and the flush barrier actually committing a debounced write before returning.FilePathGuardTest— 10 tests including the prefix-sibling bypass and the location-name rules.ProgressionTest— 18 tests covering difficulty parsing and monotonicity, endless loop arithmetic and its linear-not-compounding guarantee, modifier interval and pool rolling, the level curve, and profile NBT round-tripping.
Notes
- 35 new translation keys across all 8 supported locales.
- Version deliberately not bumped — 0.3.0 has not shipped yet, so this lands on top of it.
This mod has no additional files

