
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
ZombieApocalypseSSS 4.3.1
release9 апреля 2026 г.ZombieApocalypseSSS – Bug Fix Report (v4.3 → v4.3.1)
Custom Items Retained
- Bandage – stops bleeding
- Antivirus – cures infection
- Adrenaline – speed/strength boost (now with 60s cooldown)
- ZombieCamo – zombies ignore you for 60s
- Radio – detect next supply drop location
Removed Items
Gun system (commented out), Ammo, Landmine, Grenade, Flashbang, MedKit — stubs removed from ZombieUtils.
Bug Fixes
| # | Severity | File | Description |
|---|---|---|---|
| 1 | 🔴 | WorldEventTask | Blood Moon now rolls once per night, not every 5 s (was ~99% effective chance) |
| 2 | 🔴 | ZombieApoc | /zapoc infect & /zapoc cure now use infectionPointsKey (new system) |
| 3 | 🟠 | PlayerStatusTask | Removed legacy infectKey migration block that caused same-tick inconsistency |
| 4 | 🟠 | WorldEventTask | Supply drop location uses getHighestBlockYAt on main thread safely |
| 5 | 🟠 | WorldEventTask | Loot chance uses nextDouble()*100 (was nextInt(100) >= double) |
| 6 | 🟡 | PlayerStatusTask | tickCount changed to long (no overflow) |
| 7 | 🟡 | ZombieApoc | isPlayerInfected() now checks infectionPointsKey |
| 8 | 🟡 | WorldEventTask | Blood Moon messages routed through i18n system |
| 9 | 🔴 | ZombieCleanupListener | Zombie tracking restored on chunk reload (ChunkLoadEvent) |
| 10 | 🔴 | ZombieApoc | /zapoc reload unregisters old StructureManager listener (was multiplying on each reload) |
| 11 | 🟠 | StructureManager | Structures skip ocean/river biomes |
| 12 | 🟠 | ZombieBehaviorTask | Screamer config now reads from correct file (zombies.yml) and correct path |
| 13 | 🟡 | WorldEventTask | Supply drop rotates among random online player (not always first in list) |
| 14 | 🟡 | PlayerStatusTask | Bleeding uses System.currentTimeMillis() for stable 2 s interval under lag |
| 15 | 🔴 | AntivirusItem | Now clears infectionPointsKey — previously never worked |
| 16 | 🔴 | ZombieUtils | All getItemByKey calls are null-safe with log warnings |
| 17 | 🟠 | AdrenalineItem | 60-second cooldown added (was infinite-spam) |
| 18 | 🟠 | ItemListener | EquipmentSlot.HAND guard prevents items firing twice per click |
| 19 | 🟡 | PsychologicalHorrorTask | Hallucination zombie spawned as PLUGIN reason, not intercepted by SpawnListener |
| 20 | 🟡 | RadioItem | Cooldown lore update checks both main hand and offhand |
| 21 | 🟡 | SpecialZombieListener | NamespacedKey for ability cooldown cached (not created per-hit) |
| 22 | 🔴 | ZombieCamoItem | Now writes to plugin.getCamoKey() ("apoc-camo") — previously never worked |
| 23 | 🔴 | BandageItem | Checks PersistentDataType.INTEGER (was BYTE) — previously never worked |
| 24 | 🔴 | ZombieBehaviorService | onPlayerDeath uses correct key names — bleeding/infect now cleared on death |
| 25 | 🟠 | ZombieUtils | Removed dead stubs for unregistered item keys (gun, ammo, landmine, etc.) |
| 26 | 🟠 | ZombieBehaviorService | Bleeding config reads from zombiesConfig (was pluginConfig) |
| 27 | 🔴 | ZombieApoc | /zapoc give <gun> no longer NPE-crashes; shows friendly message |
| 28 | 🟠 | ZombieBehaviorTask | moans, mutation, zombie-breaking now read from zombiesConfig |
| 29 | 🟠 | ItemProtectionListener | CMD validation uses getItemsConfig() (was getConfig()) |
| 30 | 🟠 | ZombieApoc | /zapoc stats shows actual infection points (was always "NO") |
| 31 | 🟠 | ZombieBehaviorTask | Screamer config path fixed: zombies.types.screamer.* |
| 32 | 🟡 | SurvivalGuideListener | GuidePage.valueOf() wrapped in try/catch |
4.3
release5 апреля 2026 г.[4.3] — 2026-04-05 🏗️ Structure System Refactor
Complete overhaul of all structure generation code for maintainability and safety.
🏗️ Structure System Overhaul
MilitaryCheckpoint.java
- Refactored rotation system: Unified
transform()andapplyRotation()methods - Variant enum system: Converted from integers to proper
Variantenum (ABANDONED, OVERRUN, ACTIVE) - Constants extraction: Added 50+ named constants:
SPAWN_RADIUS,MAX_TERRAIN_HEIGHT_DIFFROAD_LENGTH,ROAD_HALF_WIDTHBARRIER_LENGTH_BASE,TOWER_HEIGHT_BASESHULKER_CHANCE,CHEST_SEARCH_ATTEMPTSHEAVY_SPAWN_CHANCE,BOSS_HEALTH_BASE, etc.
- Improved setBlockSafe(): Added comprehensive
isReplaceable()helper - Cached tower position: Pre-calculate for sniper spawning
CrashedSupplyPlane.java
- Fixed setBlockSafe(): Properly handles replaceable blocks
- Mob spawn safety: Added
isValidMobSpawn() - Location-based random: Unique randomness per structure
- Performance optimization: Reduced
Location.clone()calls - Improved canSpawn(): Height variation checks
AbandonedHospital.java
- Added constants:
SPAWN_CHECK_RADIUS,MAX_HEIGHT_VARIATION,ZOMBIE_COUNT - Safe block placement:
setBlockSafe()withisReplaceable() - Mob spawn safety:
isValidMobSpawn() - Safe chest placement:
placeChestSafely()
SurvivorCamp.java
- Complete refactor: Extracted all magic numbers to constants
- Tent validation:
buildTent()checks space before building - Safe methods:
setBlockSafe(),placeBlockSafely(),placeChestSafely()
AbandonedOutpostPro.java
- Added constants:
FENCE_BROKEN_CHANCE,STRUCTURE_RADIUS - Improved canSpawn(): Height check + replaceable block validation
- Mob spawn safety:
spawnOutpostZombies()with validation
🧹 Code Cleanup
Removed Unused Files (11 files)
- EngineeringTableManager.java — No longer used
- items/impl/*.java (10 files) — Unused items:
- ChainsawItem.java, FlashbangItem.java, GrenadeItem.java
- KatanaItem.java, LandmineItem.java, MedKitItem.java
- MeleeWeaponItem.java, MolotovItem.java, SimpleItem.java
- ZGunWeapon.java
Updated Files
- StructureManager.java — Changed
AbandonedOutpost→AbandonedOutpostPro
📊 Statistics
- Files refactored: 5 structure files
- Files deleted: 11 unused files
- Constants added: 75+
- Helper methods added: 15+
🔒 Safety Improvements
All structures now have:
- ✅
setBlockSafe()withisReplaceable() - ✅
isValidMobSpawn()for safe spawning - ✅ Height-aware
canSpawn()validation - ✅ Location-based random seed
- ✅ Constants replacing magic numbers
4.2
release5 апреля 2026 г.[4.2] — 2026-04-05 🧹 The Streamlining Update
Plugin refactored to focus on core survival mechanics. Guns and weapons moved to separate plugin.
🔥 Major Changes
🗑️ Removed Custom Items (except 5 core items)
All custom items have been removed except:
- Bandage — Paper ×2 + String
- Antivirus — Golden Apple + Potion + Fermented Spider Eye
- Adrenaline — Sugar + Glowstone Dust + Spider Eye
- Zombie Camo — 8× Rotten Flesh + Slime Ball
- Radio — 9× Compass
Removed: medkit, molotov, grenade, flashbang, landmine, goggles, all guns (pistol, smg, ak47, sniper, shotgun), all melee weapons (bat, katana, chainsaw, axes), and ammo.
🔨 Removed Engineering Table
The EngineeringTableManager has been completely removed. All remaining items now use vanilla crafting table recipes.
🔫 CombatGunSSS Integration
For guns, ammo, and advanced weaponry, install the companion plugin:
- CombatGunSSS — 30+ guns with ballistics engine
- Full API compatibility between plugins
- Seamless infection and zombie damage integration
4.1
release14 марта 2026 г.[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
ZombieApocalypseSSS 4.0
release12 марта 2026 г.[4.0] - 2026-03-12 - Update Checker + Version Bump
✨ New Features
- Modrinth Update Checker — The plugin now checks for new versions on Modrinth during server startup.
- Runs fully async; won't lag the main thread even if the network is slow.
- If an update is available: Prints a clear console notification showing current vs. latest version.
- Admins with the
zapoc.adminpermission receive a chat notification upon joining (delayed by 2 seconds to avoid being buried by other login messages). - Can be completely disabled via
update-checker: falseinconfig.yml. - Class:
UpdateChecker.java— implementsListenerto hook intoPlayerJoinEvent.
🐛 Bug Fixes
-
Messages never loaded (Critical) —
loadConfig()calledsaveResource("messages_vi.yml"), but the file is located atlanguage/messages_vi.ymlinside the JAR.saveResourcethrew a startup exception, leavingmessagesConfigempty—resulting in everygetMessage()call returning§cMissing key: .... Fixed by using the correct sub-path and ensuring parent directories are created if missing. -
/zapoc panicspawned vanilla zombies — The command calledZombieSpawnService.transformZombie(z, "random"), but"random"is not a valid key in thezombieTypesmap. This causeddata == null, making the method return immediately without applying stats, AI, or types. All 15 panic zombies were plain vanilla. Fixed by callingZombieSpawnService.randomizeZombie(z). -
Supply drop interval ignored config —
WorldEventTask.calculateNextDrop()attempted to readplugin.getPluginConfig().getLong("supply-drop.interval")fromconfig.yml, but the key is actually defined inevents.yml. The interval always defaulted to the hardcoded 18,000 ticks. Fixed by reading fromplugin.getEventsConfig(). -
Sun effect settings ignored config —
ZombieBehaviorTask.handleSunEffect()attempted to readsun-effect.mode,sun-effect.light-threshold, and related keys fromplugin.getPluginConfig()(config.yml), but those keys reside insun-effect.yml. Sun slow/burn modes always used hardcoded defaults. Fixed by reading fromplugin.getSunEffectConfig().
⚠️ Minor Fixes
-
Bleeding tick check unreliable —
PlayerStatusTaskusedBukkit.getCurrentTick() % 40 == 0to throttle bleeding damage to every 2 seconds. Because the server tick counter and task invocation aren't perfectly aligned, the check fired inconsistently. Replaced with a localtickCountfield incremented on each run. -
canSpawnMutated()allowed 2 Mutated per chunk — The method returnedcount < 2despite a comment explicitly stating the design cap is 1 per chunk. Adjusted tocount < 1. -
ZombieApocRefactored.javaremoved — Cleaned up a dead-code class from a previous refactor attempt. It was never used as the main class and contained inverted command logic. Deleted entirely.
