
ZombieApocalypseSSS
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
4.1
Compatibility
Changes
[4.1] โ 2026-03-14 ๐ง The Bug Purge Update
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.
๐ด Critical Fixes
๐ฐ Turrets lost on every server restart (BUG-02)
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.
๐ฆ Supply Drop loot table was never loaded (BUG-03)
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).
๐ High Severity Fixes
๐ฉธ Bleeding damage triggered every 40 seconds instead of 2 (BUG-06)
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.
โ ๏ธ No combined cap on zombie difficulty multipliers (BUG-07)
Three independent scaling systems stacked without a total ceiling:
- Global difficulty multiplier (capped at ร3.0 โ)
- Evolution tier multiplier (Aberrant ร2.5 โ)
- Per-player
daysSurvivedscaling (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.
๐ซ Gun reload lock persisted through player relog (BUG-08)
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.
๐ป Hallucination Villager stood completely still (BUG-09)
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.
๐๏ธ Players who joined late could see hallucination entities (BUG-10)
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.
๐ก Medium Severity Fixes
๐ช Weapon upgrade chain used hardcoded CustomModelData values (BUG-11)
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.
๐ฅ Campfire data corrupted if world name contained _ (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.
โ ๏ธ Corpse reanimation could not be disabled (BUG-15)
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).
๐ฌ Misleading comment in 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.
๐ Default Screamer particle "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.
๐ข Code Quality
| # | 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. |
โ๏ธ Config Changes
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)
๐งฉ API Changes
// โ
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
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle. To learn more about the Modrinth Maven API, click here.
Note: When available, you should use the creator's maven repo instead as it will have transitive dependency information that the Modrinth Maven API does not. You may also end up with duplicate dependencies if you use a mix of Modrinth and non-Modrinth Maven repositories for your dependencies, because the group identifier will be different when served through the Modrinth Maven API.
Maven coordinates:
Version ID:
build.gradle:
repositories {
exclusiveContent {
forRepository {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
// forRepositories(fg.repository) // Uncomment when using ForgeGradle
filter {
includeGroup "maven.modrinth"
}
}
}
// Standard Gradle dependency
dependencies {
implementation "maven.modrinth:pSQD27Eo:kdFB2q42"
}
// Legacy Loom dependency
dependencies {
modImplementation "maven.modrinth:pSQD27Eo:kdFB2q42"
}
