## [4.5.1] - 2026-05-20 - Critical Bug Purge III
### Fixed
- **Turret system completely non-functional — Adventure vs. Legacy API mismatch (BF-16)** —
The crafting recipe assigned the "Sentry Turret" display name via the Adventure API
(`meta.displayName(Component.text(...))`). `TurretListener.isTurretItem()` read the name
with the deprecated Legacy API (`meta.getDisplayName()`), which always returns an empty
string when the name was set via Adventure — so the check never matched and
`TurretManager.createTurret()` was never called. Placing a crafted turret had no effect.
`isTurretItem()` now reads the Adventure `displayName()` component first, serialises it to
plain text, and falls back to the legacy accessor for pre-migration items.
- **`onDisable()` never called `turretManager.cleanup()` — turret data loss on restart (BF-17)** —
`CampfireManager.shutdown()` was called in `onDisable()` but the analogous
`TurretManager.cleanup()` call was absent. `cleanup()` is responsible for cancelling the
internal firing task and flushing `turrets_data.yml`. Turrets placed during a session
without a prior auto-save were silently lost on server restart.
`turretManager.cleanup()` is now called from `onDisable()`.
- **`/zapoc reload` wiped all zombie custom AI — missing `scanExistingZombies()` call (BF-19)** —
`loadConfig()` (called by `loadAllConfigs()` at the start of every reload) called
`trackedZombies.clear()`. `ZombieBehaviorTask` iterates that collection each tick; after
a reload it was empty, so every living custom zombie lost its AI (no moans, no screaming,
no sun effect, no mutation) until the zombie died or new ones spawned.
`scanExistingZombies()` is now called at the end of the reload handler to repopulate
the map immediately.
- **`/zapoc reload` did not rebuild `TurretManager` — config changes had no effect (BF-18)** —
`TurretManager` reads `range`, `damage`, and `fire-rate-ticks` only in its constructor.
After a reload the old instance kept running with stale values. The reload handler now
calls `turretManager.cleanup()` and recreates a fresh `TurretManager`, matching the
pattern already applied to `StructureManager`.
- **Feign-Death zombie permanently invulnerable and AI-less after server restart (BF-20)** —
When a Feign-Death zombie "died", the code set `invulnerable=true` and `AI=false`, then
scheduled a `BukkitRunnable` to revive it after 3–5 s. If the server restarted during
that window, the task was discarded but the entity flags persisted in NBT — leaving an
immortal, motionless zombie that could never be killed. A new PDC key `feign_active` is
set at the start of feign-death and cleared on revive. `ZombieCleanupListener.onChunkLoad`
now detects this key and immediately invokes the shared `SpecialZombieListener.reviveZombie()`
method, which also handles the scheduled revive path.
- **`CampfireManager.damageCampfire()` triggered a full YAML write on every zombie hit (BF-21)** —
`saveCampfires()` (atomic file I/O on the main thread) was called each time a zombie
attacked a campfire. With multiple zombies attacking multiple campfires simultaneously
during a Blood Moon, this caused significant main-thread stalls. Replaced with a dirty
flag: damage marks the campfire data as dirty, and the periodic task flushes to disk at
most once every ~5 seconds. Immediate saves are still performed when a campfire is
destroyed.
- **`ZombieBehaviorTask.handleScreamer()` read 6 YAML values per zombie per 0.5 s (BF-23)** —
Every invocation of `handleScreamer()` called `getZombiesConfig().getDouble/getString/getInt()`
six times. With 20 Screamer zombies that is 120 YAML map lookups per 500 ms. All six
values (`scream-chance`, `particle`, `particle-count`, `scream-range`, `darkness-duration`,
`message`) are now cached in the same 5-second refresh cycle used by sun-effect config.
- **`ZombieBehaviorTask.handleBreaker()` read `success-chance` from YAML per zombie per 2 s (BF-24)** —
`zombie-breaking.success-chance` was fetched from `FileConfiguration` inside the per-zombie
loop every 40 ticks. Now cached alongside the other task-level values.
- **`PsychologicalHorrorTask.activeHallucinations` used entity ID (`int`) as map key (BF-25)** —
Entity IDs are 32-bit integers reused by the server after an entity despawns. Using
`entity.getEntityId()` as the map key could cause stale `HallucinationData` entries to
collide with or shadow new entries sharing the same recycled ID.
The key type is now `UUID` (`entity.getUniqueId()`), which is guaranteed unique per entity.
- **Campfire heal task used 3×3 chunk scan instead of 5×5 (BF-22)** —
`isInsideSafeZone()` and `onSpawn()` were already corrected to 5×5 in a prior release,
but the heal loop inside `startTask()` still searched only 3×3 chunks (~32 block reach).
Tier-3 campfires have a 40-block radius, so players near the edge of the safe zone
were not receiving the Regeneration effect. The heal loop now uses 5×5, consistent with
all other campfire range checks.
### Notes
- No config file changes are required. All existing `data.yml`, `campfires.yml`, and
`turrets_data.yml` files are fully compatible with 4.5.1.
- Any turrets placed **before this update** that were saved in `turrets_data.yml` will
load and function normally. Turrets placed during a session that ended with a 4.5.0
server stop without an intermediate save may have been lost — this is the bug being fixed.
---
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" }