Refactor kits, add safety checks and fixes
Multiple refactors and defensive fixes across kits and event handling: - RecraftManager: use Bukkit.getOnlinePlayers() collection API instead of stream. - ActiveAbility: mark backing _hitsRequired as @Volatile for thread-safety. - BlackPanther, Rattlesnake, TheWorld, Gladiator, Goblin, Venom, Voodoo: change how kit overrides are accessed (lazy or helper) to avoid stale initialization and improve readability. - Gladiator: add an ended flag to avoid double-ending fights. - Goblin & TheWorld: add game state checks to avoid restoring kits or operating on players after game end. - Rattlesnake: guard scheduled miss task with player.isOnline check and simplify action bar call. - Venom: clean up active shield tasks on kit removal and make damage handling null-safe with apply. - Voodoo: wrap passive tick in runCatching and log failures to prevent uncaught exceptions from killing tasks. - KitEventDispatcher: skip handling if victim is not alive, change interact handler to ignoreCancelled = false, and add isAlive helper. - ItemBuilder: switch lore serialization to MiniMessage, disable default italic decoration, and reuse a MiniMessage instance. These changes improve robustness, avoid race conditions, and add defensive guards against invalid state during scheduled tasks and event handling.
This commit is contained in:
@@ -32,7 +32,7 @@ class RecraftManager(
|
||||
return
|
||||
}
|
||||
|
||||
Bukkit.getOnlinePlayers().stream()
|
||||
Bukkit.getOnlinePlayers()
|
||||
.filter { plugin.gameManager.alivePlayers.contains( it.uniqueId ) }
|
||||
.forEach {
|
||||
val recraft = Recraft()
|
||||
|
||||
@@ -48,6 +48,7 @@ abstract class ActiveAbility(
|
||||
* und dann O(1) gelesen. Initialisiert mit dem Hardcoded-Default
|
||||
* als Safety-Net falls cacheHitsRequired() nie aufgerufen wird.
|
||||
*/
|
||||
@Volatile
|
||||
private var _hitsRequired: Int = -1
|
||||
|
||||
val hitsRequired: Int
|
||||
|
||||
@@ -62,18 +62,17 @@ class BlackPantherKit : Kit()
|
||||
|
||||
companion object
|
||||
{
|
||||
private val kitOverride get() =
|
||||
SpeedHG.instance.customGameManager.settings.kits.kits["blackpanther"]
|
||||
private fun override() = SpeedHG.instance.customGameManager.settings.kits.kits["blackpanther"]
|
||||
?: CustomGameSettings.KitOverride()
|
||||
|
||||
/** PDC key string shared with [KitEventDispatcher] for push-projectiles. */
|
||||
const val PUSH_PROJECTILE_KEY = "blackpanther_push_projectile"
|
||||
|
||||
private val FIST_MODE_MS = kitOverride.fistModeDurationMs // 12 seconds
|
||||
private val PUSH_RADIUS = kitOverride.pushRadius
|
||||
private val POUNCE_MIN_FALL = kitOverride.pounceMinFall
|
||||
private val POUNCE_RADIUS = kitOverride.pounceRadius
|
||||
private val POUNCE_DAMAGE = kitOverride.pounceDamage // 3 hearts = 6 HP
|
||||
private val FIST_MODE_MS = override().fistModeDurationMs // 12 seconds
|
||||
private val PUSH_RADIUS = override().pushRadius
|
||||
private val POUNCE_MIN_FALL = override().pounceMinFall
|
||||
private val POUNCE_RADIUS = override().pounceRadius
|
||||
private val POUNCE_DAMAGE = override().pounceDamage // 3 hearts = 6 HP
|
||||
}
|
||||
|
||||
// ── Cached ability instances ──────────────────────────────────────────────
|
||||
|
||||
@@ -42,9 +42,10 @@ class GladiatorKit : Kit() {
|
||||
override val icon: Material
|
||||
get() = Material.IRON_BARS
|
||||
|
||||
private val kitOverride get() =
|
||||
private val kitOverride: CustomGameSettings.KitOverride by lazy {
|
||||
plugin.customGameManager.settings.kits.kits["gladiator"]
|
||||
?: CustomGameSettings.KitOverride()
|
||||
}
|
||||
|
||||
// ── Cached ability instances (avoid allocating per event call) ────────────
|
||||
private val aggressiveActive = AllActive( Playstyle.AGGRESSIVE )
|
||||
@@ -243,6 +244,8 @@ class GladiatorKit : Kit() {
|
||||
enemy.teleport(Location( world, center.x - radius / 2, center.y + 1, center.z, -90f, 0f ))
|
||||
}
|
||||
|
||||
private var ended = false
|
||||
|
||||
override fun run()
|
||||
{
|
||||
if ( !gladiator.isOnline || !enemy.isOnline )
|
||||
@@ -272,6 +275,9 @@ class GladiatorKit : Kit() {
|
||||
|
||||
private fun endFight()
|
||||
{
|
||||
if ( ended ) return
|
||||
ended = true
|
||||
|
||||
gladiator.apply {
|
||||
removeMetadata( KitMetaData.IN_GLADIATOR.getKey(), plugin )
|
||||
removePotionEffect( PotionEffectType.WITHER )
|
||||
|
||||
@@ -2,6 +2,7 @@ package club.mcscrims.speedhg.kit.impl
|
||||
|
||||
import club.mcscrims.speedhg.SpeedHG
|
||||
import club.mcscrims.speedhg.config.CustomGameSettings
|
||||
import club.mcscrims.speedhg.game.GameState
|
||||
import club.mcscrims.speedhg.kit.Kit
|
||||
import club.mcscrims.speedhg.kit.Playstyle
|
||||
import club.mcscrims.speedhg.kit.ability.AbilityResult
|
||||
@@ -36,9 +37,10 @@ class GoblinKit : Kit() {
|
||||
override val icon: Material
|
||||
get() = Material.MOSSY_COBBLESTONE
|
||||
|
||||
private val kitOverride get() =
|
||||
private val kitOverride: CustomGameSettings.KitOverride by lazy {
|
||||
plugin.customGameManager.settings.kits.kits["goblin"]
|
||||
?: CustomGameSettings.KitOverride()
|
||||
}
|
||||
|
||||
// ── Cached ability instances (avoid allocating per event call) ────────────
|
||||
private val aggressiveActive = AggressiveActive()
|
||||
@@ -152,7 +154,8 @@ class GoblinKit : Kit() {
|
||||
val task = Bukkit.getScheduler().runTaskLater( plugin, { ->
|
||||
activeStealTasks.remove( player.uniqueId )
|
||||
// Nur wiederherstellen, wenn Spieler noch alive und Spiel läuft
|
||||
if (plugin.gameManager.alivePlayers.contains( player.uniqueId ))
|
||||
if (plugin.gameManager.alivePlayers.contains( player.uniqueId ) &&
|
||||
plugin.gameManager.currentState == GameState.INGAME )
|
||||
{
|
||||
plugin.kitManager.removeKit( player )
|
||||
plugin.kitManager.selectKit( player, currentKit )
|
||||
|
||||
@@ -57,15 +57,14 @@ class RattlesnakeKit : Kit() {
|
||||
internal val lastPounceUse: MutableMap<UUID, Long> = ConcurrentHashMap()
|
||||
|
||||
companion object {
|
||||
private val kitOverride get() =
|
||||
SpeedHG.instance.customGameManager.settings.kits.kits["rattlesnake"]
|
||||
private fun override() = SpeedHG.instance.customGameManager.settings.kits.kits["rattlesnake"]
|
||||
?: CustomGameSettings.KitOverride()
|
||||
|
||||
private val POUNCE_COOLDOWN_MS = kitOverride.pounceCooldownMs
|
||||
private val MAX_SNEAK_MS = kitOverride.pounceMaxSneakMs
|
||||
private val MIN_RANGE = kitOverride.pounceMinRange
|
||||
private val MAX_RANGE = kitOverride.pounceMaxRange
|
||||
private val POUNCE_TIMEOUT_TICKS = kitOverride.pounceTimeoutTicks
|
||||
private val POUNCE_COOLDOWN_MS = override().pounceCooldownMs
|
||||
private val MAX_SNEAK_MS = override().pounceMaxSneakMs
|
||||
private val MIN_RANGE = override().pounceMinRange
|
||||
private val MAX_RANGE = override().pounceMaxRange
|
||||
private val POUNCE_TIMEOUT_TICKS = override().pounceTimeoutTicks
|
||||
}
|
||||
|
||||
// ── Cached ability instances ──────────────────────────────────────────────
|
||||
@@ -171,6 +170,7 @@ class RattlesnakeKit : Kit() {
|
||||
// ── Miss timeout ──────────────────────────────────────────────────
|
||||
Bukkit.getScheduler().runTaskLater(plugin, { ->
|
||||
if (!pouncingPlayers.remove(player.uniqueId)) return@runTaskLater // already hit
|
||||
if ( !player.isOnline ) return@runTaskLater
|
||||
|
||||
player.world.getNearbyEntities(player.location, 5.0, 5.0, 5.0)
|
||||
.filterIsInstance<Player>()
|
||||
@@ -179,7 +179,6 @@ class RattlesnakeKit : Kit() {
|
||||
enemy.addPotionEffect(PotionEffect(PotionEffectType.NAUSEA, 3 * 20, 0))
|
||||
enemy.addPotionEffect(PotionEffect(PotionEffectType.SLOWNESS, 3 * 20, 0))
|
||||
}
|
||||
if (player.isOnline)
|
||||
player.sendActionBar(player.trans("kits.rattlesnake.messages.pounce_miss"))
|
||||
}, POUNCE_TIMEOUT_TICKS)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package club.mcscrims.speedhg.kit.impl
|
||||
|
||||
import club.mcscrims.speedhg.SpeedHG
|
||||
import club.mcscrims.speedhg.config.CustomGameSettings
|
||||
import club.mcscrims.speedhg.game.GameState
|
||||
import club.mcscrims.speedhg.kit.Kit
|
||||
import club.mcscrims.speedhg.kit.Playstyle
|
||||
import club.mcscrims.speedhg.kit.ability.AbilityResult
|
||||
@@ -83,16 +84,15 @@ class TheWorldKit : Kit() {
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val kitOverride get() =
|
||||
SpeedHG.instance.customGameManager.settings.kits.kits["theworld"]
|
||||
private fun override() = SpeedHG.instance.customGameManager.settings.kits.kits["theworld"]
|
||||
?: CustomGameSettings.KitOverride()
|
||||
|
||||
private val ABILITY_COOLDOWN_MS = kitOverride.abilityCooldownMs
|
||||
private val SHOCKWAVE_RADIUS = kitOverride.shockwaveRadius
|
||||
private val TELEPORT_RANGE = kitOverride.teleportRange
|
||||
private val MAX_TELEPORT_CHARGES = kitOverride.maxTeleportCharges
|
||||
private val FREEZE_DURATION_TICKS = kitOverride.freezeDurationTicks
|
||||
private val MAX_HITS_ON_FROZEN = kitOverride.maxHitsOnFrozen
|
||||
private val ABILITY_COOLDOWN_MS = override().abilityCooldownMs
|
||||
private val SHOCKWAVE_RADIUS = override().shockwaveRadius
|
||||
private val TELEPORT_RANGE = override().teleportRange
|
||||
private val MAX_TELEPORT_CHARGES = override().maxTeleportCharges
|
||||
private val FREEZE_DURATION_TICKS = override().freezeDurationTicks
|
||||
private val MAX_HITS_ON_FROZEN = override().maxHitsOnFrozen
|
||||
}
|
||||
|
||||
// ── Cached ability instances ──────────────────────────────────────────────
|
||||
@@ -225,10 +225,10 @@ class TheWorldKit : Kit() {
|
||||
override fun run() {
|
||||
ticks++
|
||||
|
||||
if (ticks >= FREEZE_DURATION_TICKS ||
|
||||
!target.isOnline ||
|
||||
if (ticks >= FREEZE_DURATION_TICKS || !target.isOnline ||
|
||||
!plugin.gameManager.alivePlayers.contains(target.uniqueId) ||
|
||||
!frozenEnemies.containsKey(target.uniqueId))
|
||||
!frozenEnemies.containsKey(target.uniqueId) ||
|
||||
plugin.gameManager.currentState == GameState.ENDING) // ← neu
|
||||
{
|
||||
doUnfreeze(target)
|
||||
cancel()
|
||||
|
||||
@@ -49,9 +49,10 @@ class VenomKit : Kit() {
|
||||
override val icon: Material
|
||||
get() = Material.SPIDER_EYE
|
||||
|
||||
private val kitOverride get() =
|
||||
private val kitOverride: CustomGameSettings.KitOverride by lazy {
|
||||
plugin.customGameManager.settings.kits.kits["venom"]
|
||||
?: CustomGameSettings.KitOverride()
|
||||
}
|
||||
|
||||
// ── Cached ability instances (avoid allocating per event call) ────────────
|
||||
private val aggressiveActive = AggressiveActive()
|
||||
@@ -116,6 +117,11 @@ class VenomKit : Kit() {
|
||||
override fun onRemove(
|
||||
player: Player
|
||||
) {
|
||||
activeShields[ player.uniqueId ]?.let { shield ->
|
||||
shield.expireTask.cancel()
|
||||
shield.particleTask.cancel()
|
||||
activeShields.remove( player.uniqueId )
|
||||
}
|
||||
val items = cachedItems.remove( player.uniqueId ) ?: return
|
||||
items.forEach { player.inventory.remove( it ) }
|
||||
}
|
||||
@@ -275,11 +281,11 @@ class VenomKit : Kit() {
|
||||
attacker: Player,
|
||||
event: EntityDamageByEntityEvent
|
||||
) {
|
||||
val shield = activeShields[ victim.uniqueId ] ?: return
|
||||
shield.remainingCapacity -= event.damage
|
||||
val isCrit = event.isCritical
|
||||
event.damage = if ( isCrit ) 3.0 else 2.0
|
||||
if ( shield.remainingCapacity <= 0 ) breakShield( victim )
|
||||
activeShields[victim.uniqueId]?.apply {
|
||||
remainingCapacity -= event.damage
|
||||
event.damage = if (event.isCritical) 3.0 else 2.0
|
||||
if (remainingCapacity <= 0) breakShield(victim)
|
||||
} ?: return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -58,9 +58,10 @@ class VoodooKit : Kit() {
|
||||
/** Tracks active curses: victim UUID → System.currentTimeMillis() expiry. */
|
||||
internal val cursedExpiry: MutableMap<UUID, Long> = ConcurrentHashMap()
|
||||
|
||||
private val kitOverride get() =
|
||||
private val kitOverride: CustomGameSettings.KitOverride by lazy {
|
||||
plugin.customGameManager.settings.kits.kits["voodoo"]
|
||||
?: CustomGameSettings.KitOverride()
|
||||
}
|
||||
|
||||
// ── Cached ability instances ──────────────────────────────────────────────
|
||||
private val aggressiveActive = AggressiveActive()
|
||||
@@ -258,7 +259,8 @@ class VoodooKit : Kit() {
|
||||
if (!player.isOnline || !plugin.gameManager.alivePlayers.contains(player.uniqueId)) {
|
||||
cancel(); return
|
||||
}
|
||||
tickPassive(player)
|
||||
runCatching { tickPassive( player ) }
|
||||
.onFailure { plugin.logger.severe( "[VoodooKit] tickPassive error: ${it.message}" ) }
|
||||
}
|
||||
}.runTaskTimer(plugin, 0L, 20L)
|
||||
tasks[player.uniqueId] = task
|
||||
|
||||
@@ -83,6 +83,7 @@ class KitEventDispatcher(
|
||||
val victim = event.entity as? Player ?: return
|
||||
|
||||
if ( !isIngame() ) return
|
||||
if (!isAlive( victim )) return
|
||||
|
||||
val attackerKit = kitManager.getSelectedKit( attacker ) ?: return
|
||||
val attackerPlaystyle = kitManager.getSelectedPlaystyle( attacker )
|
||||
@@ -114,7 +115,7 @@ class KitEventDispatcher(
|
||||
*/
|
||||
@EventHandler(
|
||||
priority = EventPriority.HIGH,
|
||||
ignoreCancelled = true
|
||||
ignoreCancelled = false
|
||||
)
|
||||
fun onInteract(
|
||||
event: PlayerInteractEvent
|
||||
@@ -397,4 +398,11 @@ class KitEventDispatcher(
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun isAlive(
|
||||
player: Player
|
||||
): Boolean
|
||||
{
|
||||
return plugin.gameManager.alivePlayers.contains( player.uniqueId )
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package club.mcscrims.speedhg.util
|
||||
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.format.TextDecoration
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.enchantments.Enchantment
|
||||
@@ -11,6 +13,8 @@ class ItemBuilder(
|
||||
private val itemStack: ItemStack
|
||||
) {
|
||||
|
||||
private val mm = MiniMessage.miniMessage()
|
||||
|
||||
constructor(
|
||||
type: Material
|
||||
) : this(
|
||||
@@ -48,13 +52,10 @@ class ItemBuilder(
|
||||
lore: List<String>
|
||||
): ItemBuilder
|
||||
{
|
||||
itemStack.editMeta {
|
||||
val cLore = lore.stream()
|
||||
.map( this::color )
|
||||
.map( Component::text )
|
||||
.toList()
|
||||
|
||||
it.lore( cLore as List<Component> )
|
||||
itemStack.editMeta { meta ->
|
||||
meta.lore(lore.map { line ->
|
||||
mm.deserialize( line ).decoration( TextDecoration.ITALIC, false )
|
||||
})
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user