## [4.4.0] - 2026-04-17 - Bug Purge & Correctness Update
### Fixed
- **`SurvivalGuideListener.PAGE_KEY` NPE on startup** — `static final NamespacedKey PAGE_KEY`
was initialised at class-load time before the plugin instance existed, causing a
`NullPointerException` on every server start. `PAGE_KEY` is now an instance field
initialised in the constructor. *(BF-01)*
- **`InfectionListener` ignored `infection.enabled` config** — infection spread on every
zombie hit regardless of `infection.enabled: false` in `config.yml`. `ZombieBehaviorService`
already checked the flag but `InfectionListener` did not, making the config option
partially ineffective. *(BF-02a)*
- **`InfectionListener` ignored `zapoc.immune` permission** — players with `zapoc.immune`
could still receive infection points. The permission is now checked in both
`InfectionListener` and `ZombieBehaviorService`. *(BF-02b)*
- **Day-based spawn-rate (`shouldSpawn()`) was completely bypassed** — `ZombieBehaviorService`
runs at `EventPriority.HIGH` and cancelled natural spawns before `SpawnListener` ran,
so the phase-based rates (40% / 70% / 90% / 100%) never took effect — zombies always
spawned at 100% regardless of server age. `shouldSpawn()` is now called directly inside
`ZombieBehaviorService.onCreatureSpawn`. *(BF-03)*
- **`TurretManager` could permanently delete `turrets_data.yml`** — the save code called
`dataFile.delete()` before `renameTo()`. If the rename failed the data file was gone
permanently. Replaced with `Files.move(ATOMIC_MOVE)` and a non-atomic fallback. *(BF-04)*
- **Sanity config multipliers loaded but never applied** — `sanityDrainBase`,
`nightMultiplier`, `infectedMultiplier`, `lowHealthMultiplier`, and
`nearbyZombiesMultiplier` were read from `mechanics.yml` but `updateSanity()` used
hardcoded constants. Editing these values in config had no visible effect. All five
multipliers are now applied correctly. *(BF-05)*
- **`whisperPhrases` config key loaded but never used** — `handleWhispers()` used an
internal hardcoded array instead of the phrases from `mechanics.yml`. Player-configured
phrases including `phrases-vi` / `phrases-en` are now displayed correctly. *(BF-06)*
- **`hg.no-netherite-drop` bypass via corpse zombie** — the death handler stripped netherite
from `event.getDrops()` but then re-equipped the corpse zombie with raw armour including
netherite. A player could kill the corpse to recover the items. Netherite is now stripped
from the corpse's equipment when the option is enabled. *(BF-07)*
- **Tank zombie `ENTITY_RAVAGER_STEP` sound spam** — `handleTank()` fired a full-volume step
sound every 0.5 s with no throttle, flooding clients when multiple Tank zombies were
active. A 10% per-call chance now limits the frequency. *(BF-08)*
- **`panicCooldowns` HashMap memory leak** — UUID entries were added on `/zapoc panic` but
never removed on disconnect. Entries are now removed in `onPlayerQuit`. *(BF-09)*
- **Stalker spawn used zombie's world for player's Y lookup** — `getHighestBlockYAt()` was
called on the zombie's world; if the target player had recently changed worlds the Y
could be wrong. Player's own world is now used for the lookup. *(BF-10)*
### Performance
- **`handleSunEffect()` YAML reads inside per-zombie loop eliminated** — seven
`FileConfiguration.get*()` calls per zombie per tick removed. All sun-effect config
values are now cached at task level and refreshed every 5 s. Eliminates ~1,400
redundant map lookups per second at 200 tracked zombies. *(BF-11)*
- **`Math.random()` replaced with `ThreadLocalRandom` throughout** — 20+ usages replaced
across `CampfireManager`, `ZombieApoc`, `ZombieBehaviorService`, `ZombieBehaviorTask`,
`PlayerStatusTask`, `WorldEventTask`, `ZombieEvolutionService`, and
`SpecialZombieListener`. *(BF-12)*
### Notes
- No config changes required. All existing `data.yml`, `campfires.yml`, and
`turrets_data.yml` are fully compatible.
---
Developer information
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle.
maven { name = "Modrinth"; url = "https://api.modrinth.com/maven" }