
VillageAI
Intelligent village defense system. Villages centered on bells automatically detect nearby villagers. Friendly players gain reputation by trading. Hostile players lose reputation by attacking villagers. When hostile players are nearby reputation drops belo
Список изменений
[3.3.1] - 2026-05-16 — Critical Bug Fixes
🐛 Bug Fixes
🔴 Critical — Thread Safety Violations
-
Autosave called
buildVillagesConfig()on async thread —startAutosaveTask()usedrunTaskTimerAsynchronously, which meantbuildVillagesConfig()(and theVillageSnapshotconstructor it invokes) accessedVillage.getCenter().getWorld()— a Bukkit API call — off the main thread. This could cause race conditions, corrupted snapshots, orNullPointerExceptionunder load. Fixed: the scheduler is now changed torunTaskTimerso the config snapshot is always built on the main thread; the file write still runs asynchronously viasaveVillagesAsync(). -
loadVillages()restored villages on async thread — ThethenAccept()callback ofloadVillagesAsync()ran on the common ForkJoinPool thread. Inside it,snap.restore(getServer())calledserver.getWorld(UUID)— a Bukkit API that is not thread-safe off the main thread. Fixed: the restore logic is now scheduled back onto the main thread viaBukkit.getScheduler().runTask(), while the file read remains async.
🟡 Medium — Incorrect Logic
-
Raid reward silently capped at 64 emeralds —
RaidManager.onRaidComplete()passedMath.min(totalEmeralds, 64)toInventory.addItem(), so players completing a 5-wave raid (320 emeralds) received only 64 while the chat message correctly announced the full amount. Fixed: the full reward is now distributed across multiple 64-stack inventory adds, with any overflow dropped naturally at the player's feet. -
decayReputation(boolean passiveGain)ignored its parameter — ThepassiveGainargument was accepted but the method body never read it. Whenpassive_gain: falsein config, positive reputation was supposed to decay toward 0 symmetrically with negative decay, but instead stayed frozen forever. Fixed: whenpassiveGainisfalse, positive reputation now decrements by 1 per decay cycle (matching the existing negative-decay behaviour). -
VillageEconomy.analyzeVillageNeeds()read file from disk on every economy update —updateEconomy()calledanalyzeVillageNeeds(), which calledYamlConfiguration.loadConfiguration(new File(...))— synchronous disk I/O on the main tick thread — once every 24 hours. While rare, this blocked the server thread for the duration of the file read. Fixed:economy_config.ymlis now loaded once into acachedEcoConfigfield at construction and can be refreshed via the newreloadEconomyConfig()method (called automatically by/villageai reload). No disk I/O occurs during tick.
🟢 Low — Defensive Code Quality
openGuisinEconomyCommandused a plainHashMap— Event handlers (GuiListener) and command handlers both access this map; usingConcurrentHashMapis consistent with every other map in the plugin and eliminates any theoretical ordering hazard. Changed fromHashMaptoConcurrentHashMap.
🔧 Technical Details
Thread Safety
VillageAIPlugin.startAutosaveTask():runTaskTimerAsynchronously→runTaskTimer; file write remains asyncVillageAIPlugin.loadVillages(): restore logic wrapped inBukkit.getScheduler().runTask()to guarantee main-thread executionEconomyCommand.openGuis:HashMap→ConcurrentHashMap
Economy Config Caching
VillageEconomynow holds acachedEcoConfigfield populated once in the constructor- New public method
reloadEconomyConfig()allows hot-reload without restarting the server analyzeVillageNeeds()readscachedEcoConfigin memory — zero disk I/O during tick
Reputation System
decayReputation(false): positive reputation now correctly decays by 1 per decay intervaldecayReputation(true)(default): behaviour unchanged — positive reputation is stable
📊 Performance Impact
- Autosave: eliminates risk of main-thread lag from
VillageSnapshotconstruction racing with async writes - Economy tick: removes all disk I/O from the tick path — measurable improvement on servers with slow storage
- No performance regression on any other path
