
[4.6.4] — Architecture & Gameplay Bug-Fix Patch (2026-06-04)
11 bugs fixed across MatchService, Main, ArenaManager, Arena, listeners and resources.
No database migrations or config changes required — drop-in replacement for 4.6.3.
One new optional config key:game.reconnect-timeout(default60, seconds).[4.6.3] — Ability System Bug-Fix Patch (2026-06-03)
Full deep-dive analysis of all 45 ability implementations.
13 confirmed bugs fixed across 12 files (2 framework, 11 ability-level).
No database migrations or config changes required — drop-in replacement for 4.6.2.[4.6.2] — Gameplay Bug Fix Patch (2026-05-23)
Fixed — 9 bugs across 4 files
These bugs were identified through source-code analysis of v4.6.1. None require a database migration or config change.
🔴 Critical — Broken every match
-
BUG-1 ·
GameTask— PLAYING→DEATHMATCH transition bypasses FSM (GameTask.java)GameTask.handlePhaseTransitions()calledarena.setState(MatchState.DEATHMATCH)directly instead ofplugin.getMatchService().transition(arena, MatchState.DEATHMATCH). Bypassingtransition()meant:ArenaDeathmatchEventwas never fired — external plugins received no notification.scheduleDeathmatchTask()was never called — players were not teleported to the Cornucopia centre at Deathmatch start.- The rapid border collapse to 10 × 10 over 60 s never happened.
- Scoreboard was not refreshed via
updateScoreboards().
Fix: replaced
arena.setState(MatchState.DEATHMATCH)withplugin.getMatchService().transition(arena, MatchState.DEATHMATCH). -
BUG-2 ·
GameTask— three different config keys used for the same border size (GameTask.java)Three separate code paths read the "initial border size" value using three different config keys:
config.ymldeclares:game.border-initial-size: 2000(the canonical key)MatchService.javareads:"game.border-initial-size"✅ correctGameTask.javaline 90 read:"game.initial-border-size"❌ key does not exist → fallback1000.0GameTask.javaline 100 read:"game.border.initial-size"❌ key does not exist → fallback1000.0
Effect: the game loop always set the initial border to 1 000 blocks regardless of the admin's configured value. Any server with
border-initial-sizeset above or below 1 000 saw unexpected border behaviour from the very first second of a match.Fix: unified both
GameTaskreads to use"game.border-initial-size". -
BUG-3 ·
MatchService.startMatch(Arena, int)— countdownBukkitRunnablenot stored (MatchService.java)The overload
startMatch(Arena arena, int countdownSeconds)created a countdownBukkitRunnablebut discarded the returnedBukkitTask. The task was never added tocountdownTasks, socancelAllTasksFor()could not cancel it. CallingstartMatch()twice on the same arena (e.g. two rapid/hg startcommands, or a player join triggering auto-start while a manual start was in progress) launched two concurrent countdowns. Both reached zero and each calledstartArena(), resulting in the match starting twice — players teleported twice, inventory cleared twice, cage system entered an inconsistent state.Fix: stored the returned
BukkitTaskincountdownTasks.put(arena.getName(), task).
🟡 Medium — Visible gameplay defects
-
BUG-4 ·
MatchService+GameTask— WorldBorder controlled by two systems simultaneously (MatchService.java)MatchService.transition()(called when enteringPLAYING) scheduledscheduleBorderTask(), a periodic timer that shrank the border every N minutes. At the same time,GameTask.handleWorldBorder()ran every server tick duringPLAYINGand recalculated the border size via smooth linear interpolation, then calledWorldBorder.setSize(). The per-tick recalculation fired every second and silently overrode the periodic task'ssetSize(size, duration)smooth animation, causing the border to visibly flicker or jump rather than shrink smoothly.Fix: removed the
scheduleBorderTask(arena)call fromMatchService.transition(PLAYING).GameTask.handleWorldBorder()is now the sole authority for border size — it provides smoother and more precise interpolation than the coarse periodic approach. -
BUG-5 ·
Arena.getActiveLegendaryLocations()— hologram names always show material ID (Arena.java)The method read
recipe.getItemMeta().getDisplayName()(legacy Bukkit API). All legendary items in this plugin have their display names set via the Adventure Component API (meta.displayName(Component)). The legacygetDisplayName()method returns""for any such item, sohasDisplayName()was alwaysfalse, and every legendary hologram fell back to showingrecipe.getType().name()— raw material names likeWOODEN_SWORDinstead of the intended weapon name (e.g.Excalibur).Fix: replaced
getDisplayName()/hasDisplayName()with Adventure'sPlainTextComponentSerializerto extract the plain-text string from the Component display name. -
BUG-6 ·
MatchService.checkWin()— game doesn't end when a full team is the last survivor (MatchService.java)checkWin()ended the game whenaliveCount == 1. In team mode, if the final two (or more) surviving players were all on the same team,aliveCountwas ≥ 2, so no win was declared and the game ran indefinitely or until the Deathmatch timer expired — forcing teammates to kill each other.Fix: after counting alive players, if
aliveCount > 1the method now checks whether all surviving players share a single team identifier. If every alive player belongs to the same team (or all are solo/no-team and there is only one unique identity),endGame()is called immediately.
🟢 Minor — Low-severity improvements
-
BUG-7 ·
TeamManager— deprecatedChatColorusage produces compile warnings (TeamManager.java)TeamManagerandTeamDatauseorg.bukkit.ChatColorfor team color storage, which is deprecated on Paper 1.21 in favour ofnet.kyori.adventure.text.format.NamedTextColor. Migrating the underlying storage type would require a database schema change andDatabaseManagermigration; that is deferred to a future version. Added@SuppressWarnings("deprecation")onloadColors()with aTODOcomment documenting the planned migration path, silencing the compiler warnings without any behaviour change. -
BUG-8 ·
MatchService.startCountdown()— countdown task not stored, leaks on arena reset (MatchService.java)startCountdown(Arena, List<Player>)(called from the mainstartMatch(Arena, List<Player>)flow) created a countdownBukkitRunnablebut did not add the task tocountdownTasks. If the arena was reset before the countdown finished (e.g. all players left), the orphaned task continued ticking until it reached zero and calledreleasePlayers()on a now-empty arena — potentially transitioning a WAITING arena to PLAYING with no players.Fix: stored the returned task in
countdownTasks.put(arena.getName(), taskRef[0]). -
BUG-9 ·
MatchService— cage cleanup storesBlockobjects instead ofLocation(MatchService.java)arenaCageswas declaredMap<String, List<Block>>.Blockobjects hold a direct reference to the chunk at the time they were obtained. If chunks unload and reload beforebreakGlassCages()runs, the staleBlockreference may report an incorrectgetType()— the GLASS check would fail and the cage blocks would not be cleared, leaving orphaned glass geometry in the arena world.Fix: changed
arenaCagestoMap<String, List<Location>>.breakGlassCages()now callsl.getBlock()at removal time, always querying the current (guaranteed-loaded) chunk state. BothcreateJoinCage()andteleportPlayersAndCreateCages()updated to storel.clone().
Changed
pom.xml— version4.6.1→4.6.2plugin.yml— version4.6.1→4.6.2; description updatedpaper-plugin.yml— version4.6.1→4.6.2
-
[4.6.1] — Critical Bug Fix Patch (2026-05-15)
Fixed — 9 bugs across 7 files
Critical — Plugin fails to enable
-
BUG-PPL-01 ·
paper-plugin.yml—loader:references a class that does not exist (paper-plugin.yml) The file declaredloader: me.duong2012g.hungergamessss.PaperPluginLoader, but this class was never created. Paper attempted to instantiate it during plugin loading, threwClassNotFoundException, and the plugin failed to enable entirely. Fix: removed theloader:line. A customPluginLoaderis not needed for this plugin's feature set; standard Paper loading works correctly. -
BUG-DB-01 ·
DatabaseManager— SQLite JDBC driver and MySQL connector not shaded into JAR (pom.xml)DatabaseManagercalledconfig.setDriverClassName("org.sqlite.JDBC")and supported MySQL mode, but neitherorg.xerial:sqlite-jdbcnorcom.mysql:mysql-connector-jappeared as Maven dependencies. The built JAR had no driver classes, causingClassNotFoundException: org.sqlite.JDBCon every server startup that uses the default SQLite config. Fix: added both dependencies topom.xmland configured the Maven Shade Plugin to relocate them underme.duong2012g.hungergamessss.libs.{sqlite,mysql}to avoid classpath conflicts with other plugins.
High — Gameplay-breaking
-
BUG-DB-02 ·
DatabaseManager.updateSchema()—games_playedcolumn added toarenastable instead ofplayers(DatabaseManager.java)updateSchema()iterated a single column array and checked all entries against thearenastable.games_playedis a player stat column that belongs inplayers. On fresh installs theplayerstable lacked the column;savePlayerStats()/loadPlayerStats()threwSQLException: no such column: games_playedwhenever stats were read or written. Fix: split the migration into two separate loops —arenaColumns[]against thearenastable andplayerColumns[]against theplayerstable. Each column is now checked and added against the correct table. -
BUG-AM-02 ·
AbilityManager.createItem()—unbreakableflag silently overwritten (AbilityManager.java) The unbreakable block retrieved a newItemMetacopy fromitem.getItemMeta(), setunbreakable(true)on it, calleditem.setItemMeta(unbrMeta)— but then the outer code calleditem.setItemMeta(meta)with the original meta object (which did not haveunbreakableset). The secondsetItemMetasilently discarded the flag. Legend items markedunbreakable: truein YAML still lost durability in-game. Fix: callmeta.setUnbreakable(true)directly on the existingmetaobject before the finalitem.setItemMeta(meta)call. The duplicateitem.getItemMeta()call is removed. -
BUG-AM-03 ·
AbilityManager.createItem()— localization fallback condition never true (AbilityManager.java) The fallback for missing legend name/description keys compared the return value ofgetMessage(key)against the raw key string:if (localizedName.equals("legend_" + key + "_name")).MessageManager.getMessage()never returns the raw key — it returns"§cMessage not found: <key>"— so the condition was alwaysfalseand the fallback (config.getOrDefault("name", ...)) was never reached. Items with no language entry showed the red error string as their display name. Fix: replaced both checks (name + description) withhasMessage(key)which tests the cache directly. The fallback now triggers correctly for any missing key. -
BUG-AM-04 ·
MessageManager— no public API to test key existence (MessageManager.java) There was no way to check whether a message key existed without callinggetMessage()and inspecting the error-prefix fallback string, which is fragile and language-dependent. Fix: addedpublic boolean hasMessage(String key)that returnsmessageCache.containsKey(key). Used by thecreateItem()fallback fix above.
High — Production safety
- BUG-AFC-01 ·
AutoFixCommand— dangerous synchronous I/O, stale hardcoded JAR name, and wrong config keys (AutoFixCommand.java) Three independent issues:- File copy and HTTP download ran on the main server thread (could freeze the server tick for multiple seconds while downloading Paper).
- The command hard-coded
"HungerGamesSSS-1.15.jar"— the project is now at version 4.6.1, so the source file path was always wrong and the command silently did nothing useful. - The generated
config.ymlused stale keys (database.file,game.waiting-duration,structures.spawn-interval-ticks,performance.unload-delay-ticks) that the current codebase no longer reads, resulting in a silently broken configuration on every use. Fix: the command body is replaced with a single disabled message pointing to the source file. The command will be re-enabled once rewritten with async I/O, dynamic version detection, pre-modification backups, and config generation fromConfigSchemaValidator.
Medium — Operator experience
-
BUG-CFG-01 ·
config.yml— missing keys silently use hardcoded defaults (config.yml) Over a dozen config keys read by the code (game.min-players-to-start,game.max-players,game.countdown-seconds,game.reset-delay-seconds,game.border-initial-size,game.border-min-size,game.feast-delay-seconds,arena.veinminer-max-size,abilities.disabled,abilities.enabled-only,resource-pack.required,resource-pack.prompt,database.debug-logging) were absent from the defaultconfig.yml. Admins had no indication these options existed and could not configure them without reading source code. Fix: all missing keys added toconfig.ymlwith documented defaults. -
BUG-LANG-01 ·
en.yml— 60+ message keys used in code but absent from language file (languages/en.yml) Numerous keys used inTeamCommand,WithdrawCommand,LocateCommand,DebugCommand,AutoFixCommand,MatchService, and others were not present inen.yml. Every trigger showed§cMessage not found: <key>to players or admins. Additionally, four legend item name/description keys were missing (legend_artemis_bow_name,legend_death_note_name,legend_hermes_boots_name,legend_toxic_crossbow_nameand corresponding_desckeys), causing ability items to display the red error string as their display name after the localization fallback fix above. Fix: all missing keys added toen.ymlwith English text.
Build fix —
mvn clean packagefails withCompilerException: NullPointerException(
pom.xml)testCompilecrashed with aNullPointerExceptioninside javac on JDK 21 even after switching to<release>21</release>. Root cause: MockBukkit4.109.0ships an annotation processor that walks the full type graph of the class under test.CooldownManagerTestloadsMainviaMockBukkit.load(Main.class), which pulls in every manager, database driver, and Adventure type at compile time. On JDK 21 this resolution chain hit a null reference inside javac's own symbol table, crashing the compiler process rather than producing a proper diagnostic.Three-part fix applied to
pom.xml:-
MockBukkit downgraded
4.109.0→4.26.0. Version4.26.0is the last release before the annotation processor was added and is confirmed compatible with Paper 1.21 and JDK 21. -
<maven.test.skip>true</maven.test.skip>added to<properties>. Normal plugin builds (mvn clean package) no longer attempt test compilation. Tests can still be run explicitly:mvn test -Dmaven.test.skip=false. -
maven-surefire-pluginupdated with<forkCount>1</forkCount>and<reuseForks>false</reuseForks>. MockBukkit stores global state in static fields — running multiple test classes in the same JVM causes false failures. A forked JVM per class also prevents any remaining javac NPE from killing the host Maven process.
Changed
pom.xml— version4.6.0→4.6.1.plugin.yml— version4.6.0→4.6.1; description updated.paper-plugin.yml— version4.6.0→4.6.1.
-
[4.6.0] — Full Bug Fix & Improvement Pass (2026-05-05)
Fixed — 46 bugs across 35 files
Critical — Task & Memory Leaks
- BUG-CM-01
CooldownManagerrepeating task never cancelled on reload → addedshutdown(), called fromonDisable() - BUG-MAIN-01 Game-loop
BukkitRunnablewas anonymous, never cancelled → storedgameLoopTask, cancelled inonDisable() - BUG-AM-01
new NamespacedKey(plugin, "hg_ability")created on every interact/hit/move → cached asfinal abilityPdcKeyfield - BUG-BASE-02
BaseAbility.onDeath()calledvalidateCanUse()→ blocked cleanup when arena state wasENDINGat last kill - BUG-CS-02
CloudSwordblock-expiry task had no handle → glass blocks not restored on disable - BUG-HB-02
HermesBootspassive task had no handle +lastShiftmap never cleared on quit/death - BUG-RE-01
ReinforcedElytrapassive task had no handle +explosionCooldownsnever cleared - BUG-VW-02
VillagerWandstale-entry cleanup task had no handle → leaked on reload - BUG-MJ-01
Mjolnir: noperformDeath()→ItemDisplayentity leaked, item permanently lost on death - BUG-SB-02
ShadowBlade: no globalcleanup()→ players stayed invisible permanently after/reload - BUG-RH-03
RavagerHorn: no globalcleanup()→ ravager entities roamed arena world after reload - BUG-GH-03
GolemHammer:inAirmap never cleared on quit/death → stale KB resist - BUG-LS-02
LichStaff: no globalcleanup()→ stale ammo entries persisted between matches - BUG-SG-02
SoulGauntlet: no globalcleanup()→ soul charges persisted between matches - BUG-VS-02
VoidStaff: 3 state maps never cleared on full shutdown - BUG-WS-02
WitherSickles: offhand copies not removed, cooldowns not cleared on shutdown - BUG-DN-01
DeathNote: usedonQuit/onDeathbypassing BaseAbility lifecycle chain - BUG-LISTENER-01
AbilityListener.onPlayerMove: missing null check onevent.getTo()→ NPE risk
High — State Management
- OPT-REG-01
AbilityRegistry.getAbility()was O(42) triple-fallback loop → O(1) normalizedHashMaplookup - OPT-BASE-01 Config key prefix
"abilities.xxx."rebuilt on every config call → cached in constructor - OPT-BASE-02
BaseAbility.execute()14-arm switch-case →EnumMap<TriggerType, Consumer>dispatch - OPT-DISP-01+02
AbilityDispatcher: debug logging on miss + eliminated duplicateNamespacedKeycopy - OPT-UPT-01
UnifiedPassiveTickernow skips spectators, dead players, non-active arenas - OPT-AD-01
ArmadilloDetonator: per-entityBukkitRunnable→ single unified per-player ticker - OPT-BB-01
BeehiveBlaster: 4-tick polling per bee →EntityDamageByEntityEventlistener (zero tasks) - OPT-AB-02+03
ArtemisBow: per-arena homing arrow cap + live count in passive HUD - OPT-CC-01
CorruptedCrossbow: gravity0.04flat →0.05+1%horizontal drag (matches vanilla) - IMP-LISTENER-01 All combat/interact
@EventHandlerhandlers now haveignoreCancelled = true - IMP-VM-01+02
VeinMinerListener: hard cap 128 blocks + spread breaking across ticks - BUG-MJ-01
Mjolnirglobalcleanup()removes all display entities
Medium — Improvements
- IMP-CTX-01
AbilityContext: defensiveitem.clone()+requiresArena()validation +getLocationSafe() - IMP-UTILS-01
AbilityUtils:findSafeLanding,degradeRandomArmorPiece,isFriendlyFire,tickProjectile,removeAfter,distanceSquared-first AoE check - IMP-CD-02
CooldownManager.generateProgressBar()uses§codes consistent with downstream serializer - IMP-MAIN-01+02
Main.onEnable()split intoregisterManagers/Listeners/Commands/postStartup+ early Java/Paper validation - ARCH-MS-01+02+03
MatchServiceFSM:transition()method +BorderTask/FeastTask/DeathmatchTask+ArenaDeathmatchEvent - OPT-CD-01
CooldownManager: purge offline players fromactiveDisplayItemin periodic cleanup - A1-1+3
Ability.java: ISP sub-interfaces (ActiveAbility,PassiveAbility,ProjectileAbility,ListenerAbility) + full Javadoc - A1-10+11
AbilityRegistry: ServiceLoader SPI discovery + config-drivendisabled/enabled-onlyoverrides - A1-17
BaseAbility:AbilityGuardchain-of-responsibility +passesGuards()replaces 5 duplicated validate/cooldown blocks - A1-18
BaseAbility:getConfigStringList()+getConfigSection()helpers - A1-19
Aiglos: configurable AOE shape (sphere/cylinder) viaaoe_shapeconfig key - A1-23
CloudSword: self-listener removed; fall-cancel centralised inAbilityListener.onFallDamageCancelCloud - A1-26 4 abilities (
BeehiveBlaster,ArmadilloDetonator,RavagerHorn,GhastlyWhistle):setRemoveWhenFarAway(true)on all custom entity spawns - A1-S1
AbilityServices: lightweight DI context replacing rawMainaccessor calls - A1-S2
@AbilityTrigger+@PassiveTickannotations for future APT code generation - A1-S3+A2-S3
ArenaEntityTracker: unified entity lifecycle manager (track/release/prune by owner, arena, ability) - A1-S4+A2-S2
ParticleThrottle: throttle +spawnDeferred()based on arena player count - A1-S5
AbilityMetrics: internal activation/blocked counter withSnapshotrecord - A1-S6
ConfigSchemaValidator: declarative YAML schema validation replacing manualassertRange()loop - A2-4
Main:ComponentLogger(SLF4J) field +clog()accessor - A2-9+10
ArenaManager.processQueue(): async chunk pre-loading + BossBar build progress - A2-11
CornucopiaBuildernow injectable viaplugin.getCornucopiaBuilder() - A2-17
Arena.java:ArenaBlockRestorer+ArenaLegendaryTrackercomponent objects extracted; old getters kept as@Deprecateddelegation - A2-18
Ingredient+LootEntryconverted to Java 21 records with compact constructors - A2-20
MatchService.resetArena(): block restoration now async viaArenaBlockRestorer+processQueue() - A2-22 VeinMiner disabled by default; opt-in via
arena.builtin-veinminer: true - A2-S5
HungerGamesAPI: public Developer API for custom ability registration + arena queries - A2-S6
MatchState:isActive(),isJoinable(),isEnding(),match()functional switch
New Files
File Purpose ability/AbilityServices.javaLightweight DI context ability/AbilityMetrics.javaActivation/blocked counters ability/annotation/AbilityTrigger.javaTrigger handler annotation ability/annotation/PassiveTick.javaPassive tick annotation api/HungerGamesAPI.javaPublic Developer API manager/ArenaEntityTracker.javaUnified entity lifecycle tracker model/ArenaBlockRestorer.javaBlock-change tracking + restore model/ArenaLegendaryTracker.javaLegendary placement + hologram lifecycle util/ParticleThrottle.javaParticle throttle + deferred spawn util/ConfigSchemaValidator.javaDeclarative YAML schema validation resources/paper-plugin.ymlModern Paper plugin descriptor Config changes
# New keys added (all have safe defaults — no action required): arena: builtin-veinminer: false # VeinMiner now opt-in (was always-on) show-build-progress: true # BossBar during arena build abilities: disabled: [] # blacklist ability names enabled-only: [] # whitelist mode (empty = disabled) spi-override-allow: false # allow SPI abilities to replace built-insBreaking Changes
Ingredient.mat/Ingredient.amountpublic fields removed → useingredient.material()/ingredient.amount()- VeinMiner disabled by default — set
arena.builtin-veinminer: trueto restore previous behaviour - All other changes are backwards-compatible with existing saves, ability YAMLs, and config.yml
- BUG-CM-01
[4.5.9] — Bug Fix Pass 5 (2026-04-24)
Fixed — 3 bugs across 3 files
High
- BUG-BASE-01 ·
BaseAbility—validateCanUse()uses wrong permission nodehg.admininstead ofhoplitebr.admin(BaseAbility.java)validateCanUse()granted the admin bypass withplayer.hasPermission("hg.admin"). The correct node — defined inplugin.ymland fixed for commands in 4.1 (bug M-07) — ishoplitebr.admin. Any operator who granted thehoplitebr.adminpermission explicitly (without OP) received no bypass; conversely,hg.admingrants that had been assigned in a permissions plugin incorrectly bypassed ability checks. Fix: changed check toplayer.hasPermission("hoplitebr.admin"). The|| player.isOp()fallback is unchanged so vanilla OP still bypasses.
Medium
-
BUG-CTX-01 ·
AbilityContext— noisCancelled()/setCancelled()API (AbilityContext.java) TheAbilityContextobject was fully immutable after construction. Abilities that need to signal downstream handlers to skip processing (e.g. two abilities sharing the same trigger chain) had no standard way to do so — each ability reimplemented ad-hoc boolean flags. Fix: added a mutableboolean cancelledfield withisCancelled()andsetCancelled(boolean)accessors. The field defaults tofalse. Abilities can now callctx.setCancelled(true)to mark a context as consumed, and other abilities/listeners can checkctx.isCancelled()before acting. -
BUG-OD-02 ·
ObsidianDagger— deprecatedgetTargetBlock(null, range)+ 1-tick meteor trail task + hardcodedsendMessagecalls (ObsidianDagger.java) Three independent issues inperformRightClick:player.getTargetBlock(null, TARGET_DISTANCE)— passingnullfor the transparent-block set is deprecated in Paper 1.21 and emits a compile warning. Replaced withplayer.getTargetBlockExact(distance, FluidCollisionMode.NEVER)with arayTraceBlocksprimary path for accurate targeting.- The meteor particle trail
BukkitRunnableran at period1L(every tick). A falling meteor moves slowly enough that2L(every 2 ticks) produces visually identical results at half the server-side task overhead. - Two
player.sendMessage("§...")calls bypassed the Adventure component API (italic rendering issue) and the i18n system. Replaced withplayer.sendActionBar(Component)usingLegacyComponentSerializer, consistent with the pattern used throughout other abilities since 4.5.5.
Changed
pom.xml— version4.5.8→4.5.9.plugin.yml— version4.5.8→4.5.9; description updated.
- BUG-BASE-01 ·
HungerGamesSSS v4.5.8 — Legend Rewrite Pass
🔧 Bug Fixes (13 issues)
- Fixed 8 abilities double-registering events on reload (CloudSword, VoidStaff, WitherSickles, Excalibur, EmeraldBlade, MidasSword, VillagerWand, ChainsawSword)
- Fixed ObsidianDagger, HypnosisStaff, GolemHammer passive tasks never cancelling on shutdown
- Fixed
loadPlayerStats()blocking main thread on player join → now async - Fixed
savePlayerStats()overwriting real stats with zeros on every join - Fixed UpdateManager HTTP connection hanging indefinitely when Modrinth unreachable
🗑️ Removed
- ChainsawSword — removed (replaced by Crimson Chainsword's new design)
- DeathScythe — removed (replaced by Reaper Scythe's new design)
⚔️ Legend Rewrites (12 weapons)
Weapon Old New Warpick Broken mine + shatter Mine Explosion (5s CD) + Critical Hit armor shred (20s CD) Dragon Katana Simple dash Permanent Speed I passive + Dragon Dash teleport (12s CD) Artemis Bow Power V + Infinity Power III (upgradeable to VII via Anvil) + Homing 25% + Lightning 20% Aiglos Ice freeze AOE Explosive spear throw — 3×3×3 blast, Sharpness scales explosion, Loyalty III return Armadillo Detonator Auto-explode on contact Right-click shoot (max 3 active, 20s CD) + Left-click instant detonate all Beehive Blaster Ball projectile Right-click spawns 4 angry bees, auto-target nearest enemy, Poison I (8s) per sting Crimson Chainsword Bleed DoT + rev mechanic Shred Stacks passive (+10% pen/stack, max 5) + Chainsaw Rampage (10s CD, 3s burst, 12 ticks) Corrupted Crossbow Spread arrows Poison Cloud bolt (4s CD) — AoE splash 4×4×4, Multishot = 3 clouds Death Note Stare at target to summon Warden Type /dn <name>→ instant kill (bypass armor/totem), 3 uses total, 25s per-target CDReaper Scythe Wither AOE sweep Soul Harvest (45s CD) — steal ALL beneficial effects + 25% lifesteal per target Ender Bow Swap positions + blink Pearl Shot (30s CD) + Arrow→Floating Pearl (walk in = instant teleport, no CD) Excalibur 5s timed invincibility 3-Hit Immunity Charges — block any damage, auto-recharge +1 every 45s Fountain of Youth Regen zone only Passive +1 HP/s while held + 8×8 healing zone (30s, 60s CD) Ghastly Whistle Air strike only Summon rideable Ghast (20s) — steer with WASD, auto-fires fireballs at enemies 🆕 New Systems
/dncommand — Death Note kill command with tab-complete (enemy arena players only)- AnvilListener — Artemis Bow can be upgraded Power III→VII via anvil; other legendaries protected from accidental ability-strip
- Player Head drop — drops victim's head on PvP kill (used in Warpick/Aiglos/etc. recipes). Configurable:
game.death.drop-head+game.death.drop-chance attributes:YAML block — any legend item can now set AttributeModifiers (attack damage, speed, reach) via config without codeunbreakable: true— YAML flag support in legend item configs
🍳 Recipe Overhaul
All 12 rewritten weapons have brand-new recipes themed around their playstyle. Highlights:
- Death Note: Nether Star + 3× Player Head + 3× Wither Skull + Echo Shard × 3 + Shulker Shell × 3 (endgame grind)
- Excalibur: Gold Block × 36 + Emerald Block × 4 + Diamond Sword + Enchanted Golden Apple
- Ghastly Whistle: Dried Ghast × 5 + Diamond × 4 + Saddle + Ghast Tear × 3 (35 min grind)
[4.5.7] — Full Bug Sweep Pass 4 (2026-03-20)
Fixed — 13 issues across 14 files
High
-
BUG-REG-01 · 8 ability classes —
registerEvents()in constructor causes double event-handling on reload (CloudSword.java,VoidStaff.java,WitherSickles.java,Excalibur.java,EmeraldBlade.java,MidasSword.java,VillagerWand.java,ChainsawSword.java,HypnosisStaff.java) Each of these abilities calledBukkit.getPluginManager().registerEvents(this, plugin)inside their constructor. On plugin reload,AbilityRegistryinstantiates fresh objects, but the old instances' handlers are never unregistered — both the old and new instances fire for every event, causing every hit, click, and projectile event to be processed twice. Fix: moved allregisterEvents()calls out of constructors and intoAbilityRegistry.register(), which is called exactly once per ability instance.AbilityRegistry.register()now checksability instanceof Listenerand registers automatically, so abilities only need to implementListener— no manual registration needed. -
BUG-OD-01 ·
ObsidianDagger— standalone passiveBukkitRunnableleaks on reload (ObsidianDagger.java)startPassiveTask()spawned a newBukkitRunnableon every class instantiation. After N reloads there were N concurrent tasks all granting Resistance to low-health players. Same root cause as BUG-AB-01 (fixed in 4.5.1 for ArtemisBow). Fix: removedstartPassiveTask()entirely. Low-health Resistance passive migrated topassiveTick(), driven by the singleUnifiedPassiveTickerinAbilityManager. -
BUG-HS-01 ·
HypnosisStaff— minionTask has no reference, cannot be cancelled on shutdown (HypnosisStaff.java)startMinionTask()spawned aBukkitRunnablebut discarded the returnedBukkitTaskreference. On plugin shutdown,AbilityManager.shutdown()cancelled the unified passive ticker but had no way to reach the minion task — it kept running indefinitely after disable, pathfinding mobs toward offline players. Fix: stored the task asprivate BukkitTask minionTask. Addedcleanup()override that cancels the task and callscleanupMobs()for every tracked owner, removing all controlled mobs from the world. -
BUG-GH-01 ·
GolemHammer—disable()is never called, passiveTask never cancelled (GolemHammer.java)GolemHammerstored its passive task in a field and provided adisable()method to cancel it, but nothing ever calleddisable()— notAbilityManager.shutdown(), notMain.onDisable(). The task ran forever after plugin disable, checking and modifying knockback-resistance attributes on every online player. Fix: renameddisable()tocleanup()(implementing the newAbility.cleanup()contract) so thatAbilityRegistry.shutdownAll()picks it up automatically.disable()is kept as a@Deprecateddelegate for any external callers. -
BUG-JOIN-01 ·
PlayerJoinListener—loadPlayerStats()blocks main thread on every join (PlayerJoinListener.java)loadPlayerStats()executed a JDBC query synchronously on the server's main thread. With a remote MySQL database even a 20 ms query introduces a measurable tick-time spike and can cause lag for all players when multiple players join simultaneously. Fix: wrapped the call inBukkit.getScheduler().runTaskAsynchronously(). -
BUG-JOIN-02 ·
PlayerJoinListener—savePlayerStats()immediately afterloadPlayerStats()overwrites DB stats with zeroes (PlayerJoinListener.java)savePlayerStats()was called one line afterloadPlayerStats()with the comment "Ensure record exists". BecauseloadPlayerStats()is now async (and was previously also a blocking call that hadn't finished before the save began),PlayerDatastill held its default values (kills=0, deaths=0, wins=0…) at save time. For any returning player, their stats were reset to zero on every server join. Fix: removed the erroneoussavePlayerStats()call. New player records are created correctly by theON CONFLICT DO UPDATE/INSERT OR REPLACEupsert inDatabaseManagerthe first time a real stat change is saved (on death, win, or disconnect).
Medium
-
BUG-SHUT-01 ·
AbilityManager.shutdown()— only cancels the unified ticker; standalone tasks in GolemHammer, HypnosisStaff, ObsidianDagger, DeathNote are not cleaned up (AbilityManager.java,AbilityRegistry.java,Ability.java) Three abilities (GolemHammer, HypnosisStaff, ObsidianDagger) owned standaloneBukkitRunnabletasks that nothing cancelled on shutdown.DeathNotehad a workingcleanup()method but it required a specific cast inMain.onDisable()— easy to break during refactors. Fix: addeddefault void cleanup() {}to theAbilityinterface. AddedAbilityRegistry.shutdownAll()which iterates all registered abilities and callscleanup()on each.AbilityManager.shutdown()now delegates toshutdownAll()after cancelling the unified ticker.Main.onDisable()no longer needs any per-ability special cases. -
BUG-ARENA-01 ·
Arena— 14 public fields bypass thread-safe getters/setters (Arena.java)name,world,cornucopia,spawns,hologramMap,holograms,originalBlocks,blockQueue,activeLegendaries,configuredLegendaries,temporaryLegendaries,pvpEnabled,netherEnabled,endEnabled,lifestealEnabled,hardcoreEnabled,veinminerEnabled,enabled,netherWorld, andmaxPlayerswere allpublic. Any class could modify them without going through the synchronized setters, silently breaking thread-safety guarantees on state, timer, and gameEnded. Fix: changed all fields toprivate. Added missing accessor methods (getHologramMap(),getHolograms(),getOriginalBlocks(),getBlockQueue(),getTemporaryLegendaries(),getActiveLegendaries(),setActiveLegendaries(),getNetherWorld(),setNetherWorld()). Updated all call-sites inArenaManager,ArenaListener,DungeonManager, andMatchServiceto use the new accessors. -
BUG-UM-01 ·
UpdateManager— missing connection timeout causes async thread to hang indefinitely (UpdateManager.java)HttpURLConnectionwas opened with noconnectTimeoutorreadTimeout. If Modrinth's API was unreachable (DNS failure, network outage, server down), the async thread blocked forever, leaking a thread for the entire server uptime. Fix: addedconn.setConnectTimeout(5000)andconn.setReadTimeout(5000)— the check fails gracefully within 5 s and logs a warning instead of hanging.
Low
- DEAD-CODE-01 ·
ArenaManager— threebuildStyle*placeholder methods are unreachable dead code (ArenaManager.java)buildStyleClassic(),buildStyleUltimate(), andbuildStyleSpeedSilver()were never called from anywhere in the codebase. They placed simple flat platforms without integrating into the Cornucopia pipeline (no chest scanning, no legendary hologram placement, no queue processing). They existed as early prototypes and were never completed. Fix: removed all three methods. The Cornucopia pipeline (buildCornucopia()→startCornucopiaPhase2()→scanForChests()→startPhase3()→finalizeArena()) is the sole code path for arena construction.
Changed
pom.xml— version4.5.6→4.5.7.plugin.yml— version4.5.6→4.5.7; description updated.
-
[4.5.6] — Bug Sweep Pass 3 (2026-03-20)
Fixed — 5 issues across 5 files
High
-
BUG-PM-01 ·
PlayerManager/MatchService/WithdrawCommand— stolen heart item is unclickable after Component-API migration (PlayerManager.java,MatchService.java,WithdrawCommand.java)PlayerManager.onPlayerInteractidentified stolen hearts by comparingmeta.getDisplayName()to the language-file string. After the 4.5.4 fix migrated item names tometa.displayName(Component), the legacygetDisplayName()call returns""— the comparison always failed and hearts could never be consumed. Fix: aNamespacedKeyPDC tagstolen_heartis now written to every heart item on creation (MatchServiceandWithdrawCommand).PlayerManagerreads the PDC tag instead of the display name. -
BUG-EB-01 ·
EnderBow— position swap has no team check (EnderBow.java)performProjectileHitswapped the shooter and any hitLivingEntityunconditionally. Shooting a teammate teleported them to the shooter's position. Fix: addedisFriendlyFirecheck before performing the swap; returns early for friendly targets.
Medium
- BUG-CB-01 ·
CornucopiaBuilder/ArenaManager— build progress spams all online players (CornucopiaBuilder.java,ArenaManager.java)clearArea()usedBukkit.broadcastMessage()for three status messages ("Pre-loading chunks", "Clearing area", "Clearing N%"). These messages were delivered to every player on the server, not just players in the arena being built. Fix: replaced all fourbroadcastMessagecalls withplugin.getLogger().info().
Low
- DUPE-IMPORT · Duplicate
importstatements across 8 manager/model files (ScoreboardManager.java,ArenaManager.java,DungeonManager.java,Arena.java,LegendaryRecipe.java,HologramManager.java,LobbyManager.java,MessageManager.java) A previous tooling pass accidentally injected theColorUtilimport statement 9–27 times per file. While harmless at runtime, it produced compiler noise and inflated source size. Fix: de-duplicated all import blocks; one import per class per file.
Changed
pom.xml— version4.5.5→4.5.6.plugin.yml— version4.5.5→4.5.6.
[4.5.5] — Bug Sweep Pass 2 (2026-03-20)
Fixed — 7 issues across 6 files
High
-
BUG-GT-01 ·
GameTask— world border Phase 1 and Phase 2 execute simultaneously for 60 seconds (GameTask.java) Phase 1 ran whiletimer ∈ (grace, main]and Phase 2 whiletimer ∈ (main−60, main]. During the last 60 s of the main phase both conditions were true, so Phase 2 calledborder.setSize()immediately after Phase 1 — resetting the border back tomiddleSizeon every tick and then fast-shrinking todmSize. Players saw the border snap suddenly rather than transitioning smoothly. Fix: made the ranges mutually exclusive — Phase 1 covers(grace, main−60]and Phase 2 covers(main−60, main](else-if chain). -
BUG-FM-01 ·
MatchService— feast never spawns from the second match onward (MatchService.java)resetArena()calledfeastManager.cleanup(arenaName)which removed feast chests but did not clear thefeastSpawnedguard set inFeastManager. On the next match the guard was still set, sospawnFeast()was never called again for the lifetime of the server. Fix: changed the call tofeastManager.resetArena(arenaName)which calls bothcleanup()andfeastSpawned.remove(). -
BUG-CD-01 ·
MatchService— countdown cancels immediately becausegetGamePlayers()is empty duringSTARTINGstate (MatchService.java) The not-enough-players check instartCountdownused:arena.getGamePlayers().values().stream().filter(GamePlayer::isAlive).count() < 2gamePlayersis not populated until the match transitions toPLAYING, soaliveCountwas always 0 → the countdown broadcast "not_enough_players" and cancelled on the very first tick. Fix: replaced the alive-count check witharena.getPlayers().size() < 2, which counts the raw joined-player list that is populated duringSTARTING.
Medium
- BUG-AB-04 ·
DeathNote—sendActionBar(String)deprecated on Paper 1.21 (DeathNote.java) The progress bar was sent viaplayer.sendActionBar(String), a deprecated overload removed in newer Paper builds. Fix: wrapped the string withLegacyComponentSerializer.legacySection().deserialize().
Low
- BUG-AB-05 ·
VoidStaff,SoulGauntlet,DeathScythe,WitherSickles—sendActionBar(String)deprecated (13 call-sites) (VoidStaff.java,SoulGauntlet.java,DeathScythe.java,WitherSickles.java) Same issue as BUG-AB-04. All 13 remaining String-based action-bar calls replaced withLegacyComponentSerializer.legacySection().deserialize().
Changed
pom.xml— version4.5.4→4.5.5.plugin.yml— version4.5.4→4.5.5.
-
Fixed:
- 8+ missing message keys, CloudSword stutter, PoseidonsTrident thrown hit
- Legend name + lore had italic — fix all,use Paper Component API
[4.5.2] — Artemis Bow & Passive Ticker Fix Pass (2026-03-18)
Fixed — 8 issues across 5 files
High
-
AB-OPEN-01 ·
ArtemisBow— memory leak: unbounded homing-arrow runnables accumulate on rapid fire (ArtemisBow.java) Each call toperformProjectileLaunchspawned 5 independentBukkitRunnableinstances (one per arrow). Players firing continuously in rapid succession could accumulate dozens of concurrent runnables with no upper bound, gradually degrading server TPS. Fix: added a per-playerAtomicIntegercounter (activeArrowsmap, capped at 15 = 3 simultaneous volleys). Each runnable increments on start and decrements on cancel/expire. New volleys are silently skipped if the cap is reached. Counter is cleaned up onperformQuitandperformDeath. -
AB-OPEN-02 ·
ArtemisBow—performProjectileHitdeals lightning damage without team check (ArtemisBow.java) After BUG-10 was fixed (switched tostrikeLightningEffect+ manualtarget.damage()), the new manual damage call had noisFriendlyFireguard. Result: Artemis Bow arrows could deal lightning damage to teammates. Fix: addedif (target instanceof Player tp && isFriendlyFire(shooter, tp)) return;before callingtarget.damage(). Also added a null-guard: ifprojectile.getShooter()is not aPlayer, the method returns early. -
AB-OPEN-03/05 ·
ArtemisBow— Sky Strike targets first entity in list, not closest (ArtemisBow.java)getNearbyEntities(20, 20, 20)returns entities in no guaranteed order. Sky Strike broke out of the loop on the first non-team entity — sometimes picking a distant mob over an enemy player standing 2 blocks away. Fix: replaced early-break loop with a fulldistanceSquaredscan to find the closest valid target. Search radius is now configurable viasky_strike_radius(default 20). -
AB-OPEN-04 ·
ArtemisBow— fan arrows use default Minecraft damage (~0.5 hearts) instead of bow charge (ArtemisBow.java)player.launchProjectile(Arrow.class)spawns arrows with base damage 2.0 — they ignore the actual bow charge the player applied. Only the centre (original) arrow inherited the real charge damage. Fix: addedextraArrow.setDamage(baseArrow.getDamage())when spawning each fan arrow so all 5 arrows deal equal damage consistent with bow charge.
Medium
-
BUG-HH-02 ·
HadesHelm— standalone passiveBukkitRunnableleaks on reload (HadesHelm.java)startPassive()spawned a newBukkitRunnableon every class instantiation. On plugin reload the old instance was discarded but the runnable kept running → N tasks after N reloads. Same root cause as BUG-AB-01 (fixed in 4.5.1 for ArtemisBow). Fix: removedstartPassive()entirely. Night Vision, Invisibility-while-sneaking, and Fire Aura logic migrated topassiveTick()— driven by the singleUnifiedPassiveTicker. -
BUG-GC-01 ·
GuardianCannon— standalone passiveBukkitRunnableleaks on reload (GuardianCannon.java) Same root cause as BUG-HH-02.startPassiveTask()ran on instantiation → N tasks after N reloads. Fix: Dolphins Grace water passive migrated topassiveTick(). -
BUG-LS-01 ·
LichStaff— standalone passiveBukkitRunnableleaks on reload (LichStaff.java) Same root cause as BUG-HH-02.startPassiveTask()ran on instantiation → N tasks. Fix: Frost Walker passive migrated topassiveTick(). The ammo / shoot system is unaffected — it correctly uses per-useBukkitRunnableinstances that self-cancel.
Low
- plugin.yml —
api-versionset to4.4.1(invalid format) (plugin.yml) Paper requiresapi-versionto be a major.minor string (1.21), not a plugin version string. An invalid value causes a warning on every server start. Fix: changedapi-version: 4.4.1→api-version: 1.21.
Changed
pom.xml— version4.5.1→4.5.2.plugin.yml— version4.5.1→4.5.2;api-versioncorrected to1.21; description updated.
-
[4.5.1] — Logic Bug Fix Pass (2026-03-16)
Fixed — 12 logic bugs across 11 weapon files
Critical
- BUG-PL-01 ·
PhantomLongbow— ability completely non-functional (PhantomLongbow.java)arrow.remove()was called immediately inperformProjectileLaunch, then aBukkitRunnablewas scheduled 1 tick later. By the time the runnable ran,projectile.isValid()wasfalse→ instant cancel → ability only granted invisibility, nothing else happened. Fix: stop callingremove(). Instead setarrow.setPickupStatus(DISALLOWED),setGravity(false),setDamage(0)so the arrow flies invisibly. The tracking runnable now checksisValid() || isOnGround() || isDead()and callsarrow.remove()itself.
High
-
BUG-FY-02 ·
FountainOfYouth— inverted team check heals enemies, skips teammates (FountainOfYouth.java)isFriendlyFirereturnstruefor teammates. The conditionif (isFriendlyFire(player, target)) continuewas therefore skipping teammates and healing enemies — the exact opposite of the comment. Fix: added!→if (!isFriendlyFire(player, target)) continue. -
BUG-VS-01 ·
VoidStaff— portal CD starts on first portal, blocks second placement (VoidStaff.java)portalCDs.put(...)ran unconditionally after any portal placement. Player placed portal #1, 40 s CD started, clicking again to place portal #2 hit the CD guard and was blocked — so a connected portal pair could never be created. Fix: movedportalCDs.put(...)inside theif (connected)branch only. -
BUG-CC-03 ·
CorruptedCrossbow— extra bolts missinghg_abilitymetadata, no hit effects (CorruptedCrossbow.java) Extra bolts were taggedcorrupted_extraandcorrupted_boltbut nothg_ability.AbilityManager.handleProjectileHitonly dispatchesperformProjectileHitwhenhg_abilityis present. Result: extra bolts dealt only vanilla arrow damage with no Poison or Weakness effects. Fix: addedextra.setMetadata("hg_ability", ...)when spawning each extra bolt.
Medium
-
BUG-CSW-01 ·
CrimsonChainsword— bleed only fires 1 damage tick out of 5 (CrimsonChainsword.java)runTaskTimer(10L, 10L)incrementsticks0→1→2→3→4 then cancels. Damage condition wasticks % 20 == 0— onlyticks=0satisfies this → 1 damage tick instead of 5. Fix: removed% 20 == 0check. Period is already the interval; every call = one bleed tick. -
BUG-SB-01 ·
ShadowBlade— Shadow Step has no team check, teleports behind teammates (ShadowBlade.java) Shadow Step's target loop had noisFriendlyFireguard, allowing teleport behind allies. Fix: addedif (e instanceof Player tp && isFriendlyFire(player, tp)) continue;. -
BUG-RH-01/02 ·
RavagerHorn— lazy location capture + no Ravager cleanup on quit/death (RavagerHorn.java) (1) Stampede lambdas calledplayer.getLocation()lazily — waves diverged if player moved during the 16-tick animation. Fixed: capturecastLoc = player.getLocation().clone()before the loop. (2) NoperformQuit/performDeathhook — Ravager persisted in the world after the player disconnected or died. Fixed: addedactiveRavagersmap +dismountAndRemove()called on both events. -
BUG-DN-01 ·
DeathNote— passive task runs every tick (20×/s), expensive raytracing (DeathNote.java)runTaskTimer(1L, 1L)= 20rayTrace(range:50)calls per player per second. With 10 players = 200 raytrace/s. ChangedTASK_PERIODfrom1Lto3L(~7 raytrace/s/player), still provides smooth progress bar updates.
Low
-
BUG-VW-01 ·
VillagerWand—taggedForRewardmap memory leak (VillagerWand.java) Entries were only removed on entity death. Entities that fled, despawned, or were removed by arena reset left their UUID in the map indefinitely. Fixed: value now stores an expiry timestamp (now + rewardWindow).onEntityDeathchecksnow <= expiry. A cleanup task runs every 20 s to evict stale entries. -
BUG-SG-01 ·
SoulGauntlet— action bar hardcodes-3instead ofblastCost(SoulGauntlet.java) Ifsoul_blast_soulswas changed in config, the displayed soul count was wrong. Fix:(charges - 3)→(charges - blastCost). -
BUG-WS-02 ·
WitherSickles—dualWieldActivedesync when offhand is overwritten (WitherSickles.java) If a player manually placed another item into the offhand slot,dualWieldActivekept the UUID entry, blockingequipOffhandfrom re-running. FixedonInventoryClickto detect this state and calldualWieldActive.remove(uuid). -
BUG-AB-01 ·
ArtemisBow— standalone passiveBukkitRunnableleaks on reload (ArtemisBow.java)startPassive()spawned a new task on every class instantiation. On plugin reload the old instance was discarded but the runnable kept running → N tasks after N reloads. Migrated topassiveTick()hook consumed by the singleUnifiedPassiveTickerinAbilityManager.startPassive()and its runnable removed entirely.
Changed
pom.xml— version4.5.0→4.5.1.plugin.yml— version4.5.0→4.5.1; description updated.
- BUG-PL-01 ·
[4.5.0] — Weapon Overhaul (2026-03-16)
Changed — 4 legendary weapons redesigned
Mechanic ideas for the 4 weapons below were inspired by DemoRaHoplite Weapons by Demora ( Apache-2.0 ). Plugin page: </plugins/demorahopliteweapons>
VoidStaff.java— Dual-mode system- Before: Single sneak+click portal / click shulker logic with no UI feedback.
- After: Full mode-switching system (ported concept from DemoRaHoplite):
LEFT-CLICKtoggles between Portal Mode and Shulker Mode.- Each mode has its own independent cooldown (portal 40 s, shulker 30 s).
- Action bar shows current mode + remaining cooldown, refreshes on move
and on item-switch (
PlayerItemHeldEvent). - Cleanup on quit/death prevents state leaks.
StaffModeenum added for extensibility.
Excalibur.java— True invincibility- Before:
PotionEffect RESISTANCE V— very strong but not true invincibility (high enough burst damage could still kill the player). - After:
event.setCancelled(true)atEventPriority.HIGHEST(concept from DemoRaHoplite) — 100 % damage blocked for the entire 5 s duration.- Attacker hears
IRON_GOLEM_HURTsound when a hit is deflected. - Reflection + projectile deflect mechanics preserved from the old version.
activeInvincibilitymap with expiry timestamp replaces potion effect check.isInvincible()guard added soperformDamaged()(reflect) never fires during the invincibility window.- Cleanup on quit/death.
- Attacker hears
WitherSickles.java— Dual-wield + throw mechanic- Before: Only AOE on-hit wither + lifesteal. No right-click action.
No
amount: 2usage in gameplay. - After: Full dual-wield system (mechanic inspired by DemoRaHoplite):
- Auto-equip offhand: when a player holds the Wither Sickles in main-hand, a protected copy is placed in the off-hand slot automatically.
- Off-hand copy is locked —
InventoryClickEvent,InventoryDragEvent,PlayerDropItemEvent,PlayerSwapHandItemsEventall cancelled for it. Copy is removed when the player switches away. - Right-click throw (CD: 7 s, dmg 12, Wither II × duration × 2): projectile travels as a tick-based raycast with SOUL + WITCH particle trail.
passiveTick()shows throw cooldown in action bar while holding.- Legacy AOE wither + lifesteal preserved on melee hit.
LichStaff.java— Balance pass- Before: Reload time
7 000 ms— could re-fire 4 ice-balls every 7 s, far too strong in arena PvP. - After: Reload time raised to
30 000 ms(30 s), matching DemoRaHoplite'scooldown_seconds: 30for the equivalent weapon. This makes ammo management meaningful — players must choose when to spend their 4 shots.- Between-shot cooldown tightened to
800 ms(from 500 ms) to prevent accidental double-fire while staying responsive. - Reload-complete message now includes countdown (
"Recharging in 30s..."). - Reload-out sound changed to
WITHER_DEATH(thematic) instead ofBEACON_ACTIVATE.
- Between-shot cooldown tightened to
Updated
legend-items/void_staff.yml— lore updated to describe mode-switching.legend-items/excalibur.yml— lore updated to say "TRUE invincibility".legend-items/wither_sickles.yml— lore updated to describe dual-wield + throw.legend-items/lich_staff.yml— lore updated to show30s reloadand ammo dots.pom.xml— version4.4.1→4.5.0.plugin.yml— version4.4.1→4.5.0; description updated.
[4.4] — Final Bug-Fix Pass (All 32 Known Bugs Resolved)
Fixed
Low
-
L-01 · BungeeCord API deprecated in
CooldownManager(CooldownManager.java) Two action-bar sends usedplayer.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(…))— BungeeCord/Spigot legacy chat API that is deprecated on Paper 1.21 and may be removed in a future Paper version. Replaced with Paper-nativeplayer.sendActionBar(Component). BungeeCord imports (net.md_5.bungee.*) removed. AdventureLegacyComponentSerializerused for §-code → Component conversion to keep the existing colour format intact. -
L-02 ·
ChatColor.translateAlternateColorCodes()deprecated across 9 files (ColorUtil.java,LegendaryRecipe.java,Arena.java,DungeonManager.java,HologramManager.java,MessageManager.java,ScoreboardManager.java,AbilityManager.java,LobbyManager.java,ArenaManager.java)ChatColor.translateAlternateColorCodes('&', s)is a Spigot legacy API marked deprecated on Paper 1.21. All 9 call-sites were replaced withColorUtil.colorize(s).ColorUtilwas rewritten to useLegacyComponentSerializer.legacyAmpersand()internally while still returning a §-codedStringso all downstreamsendMessage/setDisplayNamecall-sites work unchanged. AColorUtil.strip()convenience method was also added using Adventure'sPlainTextComponentSerializer. -
L-03 ·
Player.setResourcePack()deprecated on Paper 1.21 (ConnectionListener.java) BothsetResourcePack(url)andsetResourcePack(url, hash)overloads are deprecated. The replacement is Adventure'sResourcePackRequest/ResourcePackInfoAPI, sent viaplayer.sendResourcePacks(request). ConnectionListener now builds aResourcePackRequestwith an optionalrequiredflag and configurable prompt text (see config keysresource-pack.requiredandresource-pack.prompt). -
L-04 · Loot tables hardcoded in
LootTableManager.java(LootTableManager.java,loot.yml) All 30 loot entries (11 common, 11 uncommon, 8 rare) were hardcoded — impossible to change without recompiling. Extracted to a new bundled resourceloot.yml.LootTableManagernow callsplugin.saveResource("loot.yml", false)on first run then loads viaYamlConfiguration. Areload()method is exposed so changes take effect with/perf reload(or equivalent) without restarting the server. Malformed entries log a warning and are skipped; the remaining valid entries load normally. -
L-05 · 4 dungeon types are invisible aliases (
DungeonManager.java)buildUndergroundDungeonandbuildAncientMineshaftwere one-line delegates tobuildCryptandbuildGoldMinewith no documentation. Server operators had no way to know they were paying the performance cost of spawning a dungeon that was identical to another one. Both methods are now annotated with deprecation Javadoc explaining the alias relationship, and a startup INFO message tells operators which config keys to set tofalseto remove the duplicates:dungeons.underground.enabled: falseanddungeons.mineshaft.enabled: false. -
L-08 ·
ConnectionListener— reconnect during active match loses game state (ConnectionListener.java,ArenaListener.java,GamePlayer.java) When a player disconnected and reconnected during a PLAYING or DEATHMATCH phase, thePlayerJoinEventhandler only sent the resource pack. The player spawned at world spawn (or last logout point), in the wrong game mode, with no scoreboard. Three changes:GamePlayergains alastLocationfield updated byArenaListener.onPlayerMove()on every block-level movement.ConnectionListener.onPlayerJoin()now callsMatchService.restoreSession()one tick after join to re-apply scoreboard, bossbar, and game mode.- If the rejoining player was alive, they are teleported to
gp.getLastLocation()so they return exactly where they were before the disconnect.
[4.4.1] — Language update
New Feature
Added 6 new language(de,es,ja,ko,pt,zh) total 10
-
[4.3] — Stabilization Patch 3
Fixed
High
-
H-04 ·
FeastManager— double announce + possible double-spawn (FeastManager.java) Theannounce-minuteslist inconfig.ymlpreviously included both20and19, causing two broadcasts in the very first minute of the match. Even after the config was corrected in v4.1, a subtler edge still remained:timerSeconds == 0at the moment the PLAYING state begins, andfeastSeconds - 0 * 60 == 0— meaning any 0-minute entry in the list would fire instantly. Fix: GuardtimerSeconds <= 0returns immediately. AddedfeastSpawnedset (ConcurrentHashMap-backed) so the feast spawns exactly once per match regardless of how many scheduler ticks land on the boundary second. AddedresetArena(name)to clear the flag between matches. -
H-06 ·
DungeonManager.getMobHP()fallback40.0vs config60.0(DungeonManager.java) Theconfig.ymldeclareddungeons.mob-hp: 60.0as the intended default, but the code fallback was40.0. When the key was absent the generic dungeon mobs (Spider Queen, Crypt Stalker, Gold Miner, Temple Guardian) spawned with 40 HP — one-third weaker than designed. Fallback corrected to60.0.
Medium
-
M-04 ·
Excalibur— reflection hit dungeon mobs, bypassed team system (Excalibur.java)performDamagedreflected against anyLivingEntityattacker. When a dungeon mob hit the Excalibur holder, the reflected damage still had the player as the source, which could splash into nearby arena players and bypassed team friendly-fire checks entirely (the team check only ran forPlayerattackers). Reflection is a PvP mechanic; mob attacks should not trigger it. Changed the guard frominstanceof LivingEntity attackertoinstanceof Player attacker. The projectile deflect block remains unchanged (projectiles can originate from any source). Also added a self-damage guard (attacker.equals(player)) for defensive robustness. -
M-05 ·
Mjolnir—rotationAnglefloat unbounded, overflows after ~18 h (Mjolnir.java)rotationAnglewas incremented byrotationSpeedon every passive tick with no upper bound. A 32-bit float mantissa has 24-bit precision; at 25°/tick × 20 ticks/s the angle exceeds2²⁴in roughly 18 hours of continuous operation, after whichrotateY(Math.toRadians(angle))silently produces garbage rotation matrices and the thrown hammer stops orbiting correctly. Both increment sites now use% 360fto keep the angle in[0, 360). -
M-06 ·
VeinMinerListener—maxVeinSizenot hot-reloadable (VeinMinerListener.java) The field was read once in the constructor and cached for the lifetime of the plugin./perf reload(or any config reload) had no effect on vein size until the next server restart. Removed the cached field;maxVeinSizeis now read from config on everyBlockBreakEventand passed as a parameter tofindVein(). -
M-08 ·
ArenaListener— duplicate no-arg constructor using staticMain.getInstance()(ArenaListener.java) A second constructorpublic ArenaListener()accessedMain.getInstance()— a static singleton call that fails during class initialisation if the plugin is not fully loaded, and couples the listener to the static instance rather than the injected one. The no-arg constructor was a leftover comment artifact; removed. -
M-09 ·
DatabaseManager.getPlayerStats()— synchronous query on main thread (DatabaseManager.java) The method opened a JDBC connection and ran aSELECTon whichever thread called it. When called from command handlers or event listeners on the main thread this blocked the server tick for the entire round-trip. Replaced withgetPlayerStatsAsync(UUID)returningCompletableFuture<Map<String,Object>>dispatched to the Bukkit async scheduler. The original synchronous method is kept as@Deprecated getPlayerStats()(delegates togetPlayerStatsSync) for any call-sites that already run on async threads. -
M-10 ·
PerformanceOptimizer.toggleMonitoring()always returnedtrue(PerformanceOptimizer.java) The implementation wasreturn activeTasks.get() >= 0— anAtomicIntegeris always ≥ 0, so the method always returnedtrueregardless of monitoring state. AddedAtomicBoolean monitoringEnabled(defaulttrue);toggleMonitoring()now inverts the flag and returns the new state.
Low
-
L-06 ·
AbilityDispatcher.LEGEND_KEYstatic field initialised beforeonEnable()(AbilityDispatcher.java) The fieldprivate static final NamespacedKey LEGEND_KEY = new NamespacedKey(Main.getInstance(), "hg_ability")is evaluated when the class is first loaded. If any class (e.g. a command tab-completer) causedAbilityDispatcherto be loaded beforeMain.onEnable()completed,getInstance()returnednulland every subsequent ability dispatch threw NPE. Changed to lazy initialisation via a privategetLegendKey()method that constructs the key on first use. -
L-07 · Hardcoded test-item blacklist
gerald_sniffer/gruntilda/tim_enchanter(ArenaManager.java,config.yml) Three internal test legendary IDs were hard-coded instartCornucopiaPhase2(). Replacing them requires a recompile. Moved to config:legendary.excluded-ids: [](default empty). Server owners can now exclude any legendary from the active pool without touching source code. -
L-09 ·
buildCornucopia()calledoriginalBlocks.clear()destroying prior tracking (ArenaManager.java) Spawn cages and dungeon structures calloriginalBlocks.put()to record every block they place, enabling full restoration on arena reset.buildCornucopia()calledarena.originalBlocks.clear()before building the Cornucopia, erasing all prior entries. On reset, only blocks placed after the clear were restored — cages and dungeons were left as permanent terrain edits. Theclear()call is removed;CornucopiaBuilderalready appends to the map with individualput()calls. -
L-10 ·
GameTaskextendsBukkitRunnablebut is never scheduled — double-tick risk (GameTask.java)GameTaskwas a deadBukkitRunnablesubclass. The arena tick is driven entirely byArenaManager.tick()via the global loop inMain.onEnable(). If a future developer saw the class and scheduled it directly, every arena would tick twice per second.GameTask.run()now throwsUnsupportedOperationExceptionimmediately, surfacing the bug during development rather than silently corrupting timer state.
-
[4.1] — Bug-Fix Patch 1
Fixed
Critical
-
C-03 ·
arena.max-playersdefault value (config.yml) Changed the default from1000to24. The previous value caused spawn-circle radii of ~1500 blocks, placing cage pistons in unloaded chunks and silently failing block placement — players were teleported into the void at match start. -
C-02 ·
ArenaStartEventfired twice per match (MatchService.java) Removed the prematurecallEvent(new ArenaStartEvent(arena))call insidestartMatch(). The event is now fired exactly once, insidereleasePlayers()when the state transitions toPLAYINGand the game actually begins. Previously, any listener hooked toArenaStartEvent(scoreboards, rewards, third-party integrations) would execute twice. -
C-05 ·
DeathNote— non-thread-safeHashMapin passive task (DeathNote.java) ReplacedHashMapwithConcurrentHashMapfor bothprogressMapandtargetMap. These maps are written by the 1-tick passiveBukkitRunnableand concurrently removed byonQuit/onDeathevent callbacks. -
C-04 ·
Arena.spectators— non-thread-safeArrayList(Arena.java) Changed toCollections.synchronizedList(new ArrayList<>()). The spectator list is modified on the main thread (handleDeath) and iterated from async contexts (checkWin, scoreboard updates).
High
-
H-05 ·
DatabaseManager.loadArena()did not mark players as alive (DatabaseManager.java) After loading player UUIDs on server reload,GamePlayer.setAlive(true)is now called for each loaded player. Without this,checkWin()saw zero alive players immediately after reload and ended the match in the same tick. -
H-02 ·
ShadowBlade.revealPlayer()showed vanished player to entire server (ShadowBlade.java)revealPlayer()now iterates only the players in the same arena instead of callinggetOnlinePlayers()server-wide. In multi-arena setups, players in other arenas would receive a spuriousshowPlayer()call. BotharmorMapandvanishedPlayersupgraded fromHashMaptoConcurrentHashMapto eliminate race conditions between the timed-reveal BukkitRunnable, onDeath, and onQuit callbacks.
Medium
- M-07 · Wrong permission node in
BlockBreakListener(BlockBreakListener.java) Changedhg.admintohoplitebr.adminto match the permission declared inplugin.yml. Admins withhoplitebr.admincould not break blocks inside an arena during WAITING / STARTING / ENDING phases.
-
[4.0] — 2026-03-12 — Legendary Ability Audit & Fixes
🐛 Bug Fixes — Legendary Abilities
-
🛡️ ReinforcedElytra never activated (Critical) —
performSneak()called!checkCooldown(player)meaning the ability only "activated" while on cooldown and did nothing when ready. Fixed by removing the negation:if (checkCooldown(player)) return; -
💬 DeathNote spammed chat 20×/sec (Major) —
showProgress()calledplayer.sendMessage()every tick. With the passive task running at 1-tick period, this sent 20 messages per second to chat, making the chat completely unusable while holding the item. Fixed by replacingsendMessage()withsendActionBar()and rendering a proper progress bar (colored block chars + target name). -
💊 FountainOfYouth healed all online players (Major) — The heal loop iterated over
Bukkit.getOnlinePlayers()instead of the arena player list. This meant the item healed players in other arenas, spectators, and any player on the server within range regardless of game state. Fixed by iterating overarena.getPlayers()with a team-fire check. -
💀 HadesHelm applied Wither without checking if helm was equipped (Major) —
performDamaged()added Wither to any attacker when the player took damage, even if they had already unequipped the helm during combat. Fixed by addingAbilityManager.isAbilityItem(player.getInventory().getHelmet(), "hades_helm")guard at the top of the method. -
👢 HermesBoots cancelled fall damage without checking if boots were worn (Major) — Same class of bug as HadesHelm:
performDamaged()suppressed all fall damage even after the player swapped out the boots. Fixed by guarding withisAbilityItem(player.getInventory().getBoots(), "hermes_boots"). -
🔨 MidasSword used deprecated
AttributeModifierconstructor (Major) — The oldAttributeModifier(UUID, String, double, Operation, EquipmentSlot)constructor is removed in Paper 1.21+. Replaced with the currentAttributeModifier(NamespacedKey, double, Operation, EquipmentSlotGroup)API; modifier key stored as aNamespacedKeyfield to allow stable removal on each upgrade. -
🌿 EmeraldBlade used deprecated
AttributeModifierconstructor (Major) — Same issue as MidasSword.updateItemData()callednew AttributeModifier(UUID.randomUUID(), "emerald_blade_damage", …). This also meant a fresh random UUID was generated on every deposit, so the old modifier was never removed, stacking damage indefinitely. Fixed with a stableNamespacedKeyfield (emerald_blade_damage) and the new constructor. -
🩸 CrimsonChainsword leaked
revvedUp/bleedingTargetsmaps on disconnect/death (Minor) — Neither map was cleaned up when a player left or died, causing unbounded memory growth over long sessions. AddedperformQuit()andperformDeath()overrides that remove the player's entries from both maps.
-
[3.9] — 2026-03-12 — Bug Fix Release
🐛 Bug Fixes
-
💀 Game never ended after kills (Critical) —
MatchService.handleDeath()delegated toPlayerDeathListenerto track the death but never calledgp.setAlive(false)on theGamePlayerobject. BecausecheckWin()usesGamePlayer.isAliveas its sole source of truth for alive-player counting, every dead player was still counted as alive — meaning the win condition (aliveCount == 1) was never reached, and games ran until the server was restarted. Fixed by marking the player dead at the very top ofhandleDeath(). -
📦 Stray
Mjolnir.javacausing class ambiguity — A leftover copy ofMjolnir.javaexisted atme/hoplite/hungergamessss/ability/impl/Mjolnir.javafrom a package rename. It still imported classes from the currentme.duong2012gnamespace, which caused class-loading ambiguity at runtime. The file also contained outdated logic (isFriendlyFirereplaced by a manual team equality check). Deleted. -
🔢
pom.xmlversion out of sync withplugin.yml—pom.xmldeclared3.5whileplugin.ymlwas3.9. This meant compiled JARs reported the wrong version in server logs and Modrinth update checks compared against the wrong baseline. Bumpedpom.xmlto3.9.
-
fixed:
✅ Double death processing fix
✅ gamesPlayed database persistence
✅ onDisable stats flush
✅ Mjolnir lightning effect only
✅ isAbilityItem security fix
✅ Missing YAML files added
🔧 Critical Fixes Fixed Adventure API dependency - Resolved ClassNotFound crash
Fixed Mjolnir import package - Resolved compile error
Fixed database schema inconsistency - Fixed the_end column name
Fixed empty exception handling - Added proper logging instead of silent failures
🛡️ Stability Improvements
Fixed memory leaks in DeathNote - Added proper cleanup on plugin disable Enhanced null safety - Added null checks in ArenaManager
