diff --git a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt index 41e2cb1..b0ae69a 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt @@ -212,6 +212,7 @@ class SpeedHG : JavaPlugin() { override fun onDisable() { podiumManager.cleanup() + gameManager.shutdown() if ( ::lobbyAnnouncer.isInitialized ) lobbyAnnouncer.stop() if ( ::perkManager.isInitialized ) perkManager.shutdown() if ( ::tablistManager.isInitialized ) tablistManager.shutdown() diff --git a/src/main/kotlin/club/mcscrims/speedhg/database/StatsManager.kt b/src/main/kotlin/club/mcscrims/speedhg/database/StatsManager.kt index 620c707..26fc59e 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/database/StatsManager.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/database/StatsManager.kt @@ -142,17 +142,19 @@ class StatsManager(private val plugin: SpeedHG) { * Aufrufpunkt: `PlayerQuitEvent` — nach diesem Event brauchen wir die * Daten nicht mehr im RAM. */ - fun saveAndEvict(uuid: UUID) { - val stats = cache[uuid] ?: return + fun saveAndEvict( uuid: UUID ) { + val stats = cache[ uuid ] ?: return scope.launch { try { - repository.upsert(stats) - } catch (e: Exception) { - plugin.logger.severe("[StatsManager] Fehler beim Speichern für $uuid: ${e.message}") + repository.upsert( stats ) + } catch ( e: Exception ) { + plugin.logger.severe( "[StatsManager] Fehler beim Speichern für $uuid: ${e.message}" ) } finally { - cache.remove(uuid) - dirtyFlags.remove(uuid) + // Nur entfernen wenn der Cache-Eintrag noch unser ursprüngliches Objekt ist + // (verhindert, dass ein Reconnect-Eintrag gelöscht wird) + cache.remove( uuid, stats ) + dirtyFlags.remove( uuid ) } } } diff --git a/src/main/kotlin/club/mcscrims/speedhg/disaster/impl/TornadoDisaster.kt b/src/main/kotlin/club/mcscrims/speedhg/disaster/impl/TornadoDisaster.kt index 3d6083d..e4d7a0b 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/disaster/impl/TornadoDisaster.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/disaster/impl/TornadoDisaster.kt @@ -60,80 +60,58 @@ class TornadoDisaster : NaturalDisaster() { return eligibleBiomes.any { biome.contains( it ) } } - override fun trigger( - plugin: SpeedHG, - player: Player - ) { + override fun trigger( plugin: SpeedHG, player: Player ) + { val center = player.location.clone() - val world = center.world ?: return - val coroutineScope = CoroutineScope( Dispatchers.Default + SupervisorJob() ) + val world = center.world ?: return - var tick = 0 + var tick = 0 var angle = 0.0 - var mainTask: BukkitTask? = null - mainTask = Bukkit.getScheduler().runTaskTimer( plugin, { -> + Bukkit.getScheduler().runTaskTimer( plugin, { -> tick++ - if ( tick > DURATION_TICKS ) - { - mainTask?.cancel() - coroutineScope.cancel() - return@runTaskTimer - } + if ( tick > DURATION_TICKS ) return@runTaskTimer // Bukkit cancelt via cancel() - val currentAngle = angle + val positions = ArrayList>( MAX_HEIGHT * SPOKES ) + for ( layer in 0..MAX_HEIGHT ) + { + val layerRadius = ( MAX_HEIGHT - layer ).toDouble() / MAX_HEIGHT + for ( spoke in 0 until SPOKES ) + { + val a = angle + ( spoke * ( Math.PI * 2.0 / SPOKES )) + positions += Triple( + center.x + cos( a ) * layerRadius, + center.y + layer.toDouble(), + center.z + sin( a ) * layerRadius + ) + } + } angle += ROTATION_SPEED - coroutineScope.launch { - val positions = ArrayList>( MAX_HEIGHT * SPOKES ) - - for ( layer in 0..MAX_HEIGHT ) - { - val layerRadius = (( MAX_HEIGHT - layer ).toDouble() / MAX_HEIGHT ) - for ( spoke in 0 until SPOKES ) - { - val a = currentAngle + ( spoke * ( Math.PI * 2.0 / SPOKES )) - positions += Triple( - center.x + cos( a ) * layerRadius, - center.y + layer.toDouble(), - center.z + sin( a ) * layerRadius - ) - } - } - - Bukkit.getScheduler().runTask( plugin ) { -> - positions.forEach { (x, y, z) -> - world.spawnParticle( - Particle.CAMPFIRE_COSY_SMOKE, - x, y, z, - 1, 0.0, 0.0, 0.0, 0.0 - ) - } - - if ( tick % 20 == 0 ) - { - world.playSound( center, Sound.ENTITY_PHANTOM_FLAP, 1.5f, 0.4f ) - world.playSound( center, Sound.WEATHER_RAIN, 0.5f, 0.5f ) - } - - plugin.gameManager.alivePlayers - .mapNotNull { Bukkit.getPlayer( it ) } - .forEach { nearby -> - val dist = nearby.location.distance( center ) - if ( dist !in 0.5..PULL_RADIUS ) return@forEach - - val strength = ( 1.0 - dist / PULL_RADIUS ) * 0.35 - val toCenter: Vector = center.toVector() - .subtract( nearby.location.toVector() ) - .normalize() - .multiply( strength ) - - toCenter.y = strength * 0.45 - nearby.velocity = nearby.velocity.add( toCenter ) - } - } + positions.forEach { ( x, y, z ) -> + world.spawnParticle( Particle.CAMPFIRE_COSY_SMOKE, x, y, z, 1, 0.0, 0.0, 0.0, 0.0 ) } + + if ( tick % 20 == 0 ) + { + world.playSound( center, Sound.ENTITY_PHANTOM_FLAP, 1.5f, 0.4f ) + world.playSound( center, Sound.WEATHER_RAIN, 0.5f, 0.5f ) + } + + plugin.gameManager.alivePlayers + .mapNotNull { Bukkit.getPlayer( it ) } + .filter { it.location.distance( center ) in 0.5..PULL_RADIUS } + .forEach { nearby -> + val dist = nearby.location.distance( center ) + val strength = ( 1.0 - dist / PULL_RADIUS ) * 0.35 + val toCenter = center.toVector() + .subtract( nearby.location.toVector() ) + .normalize() + .multiply( strength ) + toCenter.y = strength * 0.45 + nearby.velocity = nearby.velocity.add( toCenter ) + } }, 0L, 1L ) } diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt b/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt index 3023d31..029620a 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt @@ -21,6 +21,7 @@ import org.bukkit.potion.PotionEffect import org.bukkit.potion.PotionEffectType import org.bukkit.scheduler.BukkitTask import java.util.* +import java.util.concurrent.ConcurrentHashMap import kotlin.random.Random class GameManager( @@ -32,7 +33,7 @@ class GameManager( var timer = 0 - val alivePlayers = mutableSetOf() + val alivePlayers: MutableSet = ConcurrentHashMap.newKeySet() private var gameTask: BukkitTask? = null @@ -72,6 +73,13 @@ class GameManager( }, 10L ) } + fun shutdown() + { + recraftManager.shutdown() + gameTask?.cancel() + gameTask = null + } + private var lobbyIdleCount: Int = 0 private fun gameLoop() @@ -442,7 +450,7 @@ class GameManager( val x = Random.nextDouble( -maxRadius, maxRadius ) val z = Random.nextDouble( -maxRadius, maxRadius ) val y = world.getHighestBlockYAt( x.toInt(), z.toInt() ) + 1.0 - loc = Location( world, x, y, z ) + loc = Location( world, x, y + 1.0, z ) val block = loc.block.type val below = loc.subtract( 0.0, 1.0, 0.0 ).block.type @@ -452,7 +460,7 @@ class GameManager( attempts++ } while ( !safe && attempts < 20 ) - player.teleport(loc.add( 0.0, 1.0, 0.0 )) + player.teleport( loc ) } private fun updateCompass() diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/modules/AntiRunningManager.kt b/src/main/kotlin/club/mcscrims/speedhg/game/modules/AntiRunningManager.kt index aef8fbe..a7de399 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/game/modules/AntiRunningManager.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/game/modules/AntiRunningManager.kt @@ -11,8 +11,10 @@ import org.bukkit.GameMode import org.bukkit.Sound import org.bukkit.entity.Player import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority import org.bukkit.event.Listener import org.bukkit.event.entity.EntityDamageByEntityEvent +import org.bukkit.event.player.PlayerQuitEvent import org.bukkit.potion.PotionEffect import org.bukkit.potion.PotionEffectType import java.util.* @@ -62,6 +64,15 @@ class AntiRunningManager( } } + @EventHandler( priority = EventPriority.MONITOR ) + fun onQuit( + event: PlayerQuitEvent + ) { + lastCombatAction.remove( event.player.uniqueId ) + // BossBar entfernen falls aktiv (verhindert Ghost-BossBar nach Reconnect) + event.player.hideBossBar( warningBar ) + } + private fun checkPlayers() { if (plugin.gameManager.currentState != GameState.INGAME) diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/modules/RecraftManager.kt b/src/main/kotlin/club/mcscrims/speedhg/game/modules/RecraftManager.kt index fbafbee..ba500dd 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/game/modules/RecraftManager.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/game/modules/RecraftManager.kt @@ -7,11 +7,14 @@ import org.bukkit.Material import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack import org.bukkit.scheduler.BukkitRunnable +import org.bukkit.scheduler.BukkitTask class RecraftManager( private val plugin: SpeedHG ) { + private var task: BukkitTask? = null + private val beforeFeast = plugin.config.getBoolean( "game.recraftNerf.beforeFeast", true ) private val recraftNerfEnabled = plugin.config.getBoolean( "game.recraftNerf.enabled", false ) private val maxRecraftAmount = plugin.config.getInt( "game.recraftNerf.maxAmount", 64 ) @@ -21,7 +24,7 @@ class RecraftManager( if ( !recraftNerfEnabled ) return - object : BukkitRunnable() { + task = object : BukkitRunnable() { override fun run() { @@ -29,6 +32,7 @@ class RecraftManager( plugin.gameManager.feastManager.hasSpawned ) { this.cancel() + task = null return } @@ -51,6 +55,12 @@ class RecraftManager( }.runTaskTimer( plugin, 10L, 10L ) } + fun shutdown() + { + task?.cancel() + task = null + } + private class Recraft { private val recraftMaterials = listOf( diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/ArmorerKit.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/ArmorerKit.kt index 8d7ff15..64a7fff 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/ArmorerKit.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/ArmorerKit.kt @@ -276,7 +276,7 @@ class ArmorerKit : Kit() // Shared no-active placeholder // ========================================================================= - inner class NoActive( + private class NoActive( playstyle: Playstyle ) : ActiveAbility( playstyle ) { diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/GladiatorKit.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/GladiatorKit.kt index e4b82ab..409cc3c 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/GladiatorKit.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/GladiatorKit.kt @@ -181,7 +181,7 @@ class GladiatorKit : Kit() player.location.clone().add( 0.0, 64.0, 0.0 ), capturedRadius, capturedHeight + 2 - ) + ) ?: return AbilityResult.ConditionNotMet( "No space found for gladiator arena" ) val center = BukkitAdapter.adapt( player.world, gladiatorRegion.center ) WorldEditUtils.createCylinder( player.world, center, capturedRadius - 1, true, 1, Material.WHITE_STAINED_GLASS ) @@ -228,11 +228,13 @@ class GladiatorKit : Kit() private fun getGladiatorLocation( location: Location, radius: Int, - height: Int - ): Region + height: Int, + attempt: Int = 0 + ): Region? { - val random = Random() + if ( attempt >= 20 ) return null // Kein Platz gefunden → Ability abbrechen + val random = Random() val region = CylinderRegion( BukkitAdapter.adapt( location.world ), BukkitAdapter.asBlockVector( location ), @@ -248,7 +250,8 @@ class GladiatorKit : Kit() if ( random.nextBoolean() ) -10.0 else 10.0 ), radius, - height + height, + attempt + 1 // ← Versuch mitzählen ) else region } diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/NinjaKit.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/NinjaKit.kt index eb4151b..deedfcf 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/NinjaKit.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/NinjaKit.kt @@ -67,7 +67,7 @@ import kotlin.math.sin * [lastHitEnemy]-Fenster und berechnet eine Position 1,8 Blöcke hinter dem Feind. * * ### Smoke-Mechanismus - * Ein BukkitTask spawnt alle [smokRefreshTicks] einen Partikelring. Jeder Feind + * Ein BukkitTask spawnt alle [smokeRefreshTicks] einen Partikelring. Jeder Feind * im Ring erhält Blindness I + Slowness I, die regelmäßig erneuert werden. */ class NinjaKit : Kit() { diff --git a/src/main/kotlin/club/mcscrims/speedhg/team/gui/TeamSelectionListener.kt b/src/main/kotlin/club/mcscrims/speedhg/team/gui/TeamSelectionListener.kt index d73c167..601dc32 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/team/gui/TeamSelectionListener.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/team/gui/TeamSelectionListener.kt @@ -40,12 +40,10 @@ class TeamSelectionListener( } @EventHandler( priority = EventPriority.MONITOR ) - fun onQuit( - event: PlayerQuitEvent - ) { - val state = plugin.gameManager.currentState - if ( state == GameState.LOBBY || state == GameState.STARTING ) - plugin.presetTeamManager.leave( event.player ) + fun onQuit( event: PlayerQuitEvent ) + { + // Immer aus dem Team-Tracking entfernen, unabhängig vom GameState + plugin.presetTeamManager.leave( event.player ) } @EventHandler