23 bugs fixed across all severity levels. This release focuses entirely on correctness, stability, and developer experience — no new gameplay features, just a rock-solid foundation.
loadTurrets() and saveTurrets() were both empty // TODO stubs.
Every turret placed by players vanished after a restart.
Fix: Full persistence implemented via turrets_data.yml using atomic
write (.tmp → rename) to prevent data corruption on crashes.
spawnSupplyDrop() read supply-drop.loot from config.yml, but this section
is defined in events.yml. The config section was always null, so every
Supply Drop fell back to a hardcoded 5-item list, ignoring all admin configuration.
Fix: spawnSupplyDrop() and all related methods now read exclusively from
eventsConfig (events.yml). Admin-configured loot tables are now respected.
blood-moon.chance config had no effect (BUG-04)The bloodMoonChance field was loaded from config (default 5%) but never
referenced in checkBloodMoon(). Blood Moon triggered at 100% probability
every N days instead of the configured 5% chance.
Fix: Math.random() < bloodMoonChance check added in checkBloodMoon().
Blood Moon now correctly triggers by chance each eligible night.
NullPointerException when Molotov reached max range (BUG-05)ProjectileHitEvent fires when a projectile expires at max range, at which point
both getHitBlock() and getHitEntity() return null. The handler assumed at
least one was non-null, causing a server-crashing NPE.
Fix: Three-way null check: hit block → hit entity → projectile location (fallback).
PlayerStatusTask runs every 20 ticks (1 second). The bleeding check used
tickCount % 40 == 0, which fires every 40 runs = every 40 seconds.
Players with bleeding could regenerate faster than they took damage.
Fix: Changed to tickCount % 2 == 0 → bleeds every 2 seconds as intended.
Three independent scaling systems stacked without a total ceiling:
daysSurvived scaling (no cap ✗)On a 100-day server, the combined multiplier could reach ×26×, making zombies practically unkillable.
Fix: daysSurvived scaling is now capped — health ≤ ×3.0, speed ≤ ×2.0,
spawn count ≤ 5 per spawn event.
RELOAD_COOLDOWN_KEY and FIRE_COOLDOWN_KEY were stored in
player.getPersistentDataContainer() as Unix timestamps. If a player disconnected
mid-reload and reconnected, the stale future timestamp would lock them out of
firing/reloading until the original timer expired.
Fix: Both cooldowns moved to ConcurrentHashMap<UUID, Long> in-memory maps.
Cleared automatically when the player disconnects.
updateHallucinations() called mob.setTarget(player) to make hallucinations
chase their target. While Zombie entities respond to setTarget() by pathfinding
toward the player, Villager ignores it (Villagers don't attack).
Fix: Villager hallucinations now use getPathfinder().moveTo(player, 1.15)
with periodic re-pathfind if the current path becomes null.
spawnHallucination() called hideEntity() only for players online at spawn time.
Any player joining after a hallucination spawned would see a phantom zombie/villager
wandering around with no explanation.
Fix: The existing onPlayerJoin() handler correctly hides all active
hallucinations for new joiners. Spawn-time hiding loop now also reliably covers
all current online players.
FeaturesListener hardcoded CMDs like 3004, 3011, 3005 directly in Java.
Changing any custom-model-data in items.yml would silently break upgrade
chains with no error or warning.
Fix: CMDs are now read from items.yml and mechanics.yml at runtime.
Upgrade chains survive config customization.
_ (BUG-12)Save keys used worldName_x_y_z format. Loading split on _ and took parts[0]
as the world name. A world named my_world would produce 5 parts instead of 4,
causing parts[0] = "my" → Bukkit.getWorld("my") = null → all campfires lost.
Fix: Keys now use worldUUID_x_y_z (UUID with hyphens stripped = always 32
hex chars, no ambiguous underscores). Legacy world-name keys are still parsed as
a migration fallback.
blood-moon.enabled / supply-drop.enabled read from wrong config (BUG-14)Both keys are defined in events.yml, but WorldEventTask.run() read them from
config.yml. Since config.yml doesn't contain these keys, the fallback true
was always used — neither event could be disabled.
Fix: All WorldEventTask config reads now use eventsConfig consistently.
When a player died, items were cleared and a zombie "corpse" was spawned wearing their gear. There was no config toggle — servers running minigames or PvP arenas had no way to opt out of this mechanic.
Fix: Gated behind corpse-reanimation.enabled in mechanics.yml (default: true).
ZombieBehaviorTask (BUG-16)Comment said // Every 20 ticks = 1s next to tickCounter % 2 == 0. The actual
math (task runs every 10 ticks; %2 = every 2 runs = every 20 ticks = 1s) was
correct but the comment was confusing enough to invite accidental breakage.
Fix: Comment updated to explain the full calculation chain.
"PORTAL" is invalid in Paper 1.21 (BUG-17)Particle.valueOf("PORTAL") throws IllegalArgumentException in Paper 1.21+.
The exception was caught and logged, but the screamer ability silently failed every
time on unmodified configs.
Fix: Default changed to "ENTITY_EFFECT" in both code and zombies.yml.
| # | Change |
|---|---|
| 🗑️ BUG-18 | Deleted manager/ZombieManager.java — 532 lines of dead code never instantiated. Contained a severe anti-pattern (one BukkitTask per zombie). |
| 🗑️ BUG-19 | Deleted manager/EventManager.java — 739 lines of boilerplate never registered as a Listener in onEnable(). |
| 📐 BUG-20 | Added missing constants to ZombieConstants: AI ranges, noise radii, bleed values, day-scaling caps. Magic numbers reduced across codebase. |
| 🏷️ BUG-21 | Renamed trapKey / is_trap PDC key to engineeringItemKey / is_engineering_item in EngineeringTableManager. Ammo is not a trap. |
| 🏷️ BUG-22 | Renamed ZombieUtils.applyAI() → applyFollowRange(). The method only sets follow range to 40 blocks — the old name implied far more than it did. |
| 🧹 BUG-23 | ZombieCleanupListener now removes zombies from the tracking map on chunk unload, freeing strong references to de-activated entities and reducing memory pressure on long-running servers. |
mechanics.yml — new keys:
corpse-reanimation:
enabled: true # Set false to disable zombie corpse on player death
infection:
natural-decay:
enabled: true
chance-per-second: 0.033 # ~1 point per 30s; 3× faster in Campfire safe zones
events.yml — new key:
blood-moon:
cycle-days: 7 # (moved from config.yml) eligible cycle in days
zombies.yml — changed default:
screamer:
particle: ENTITY_EFFECT # was: PORTAL (invalid in Paper 1.21)
// ✅ Fixed
api.isInfected(player) // now correctly returns true when infected
// ✅ New
api.getInfectionLevel(player) // int 0–5
api.getInfectionPoints(player) // int 0–100
api.setInfectionPoints(player, n)
api.getThreatLevel() // double
api.getZombieTier(zombie) // ZombieTier enum
api.isInSafeZone(location) // boolean
api.spawnZombie(world, loc, type)
// ⚠️ Deprecated
api.infectPlayer(player, ticks) // use setInfectionPoints() instead

ZombieApocalypseSSS is a comprehensive Minecraft plugin that transforms your server into a thrilling zombie survival apocalypse. Featuring advanced zombie AI, evolving variants, player infection mechanics, psychological horror elements, and dynamic events