Optimize code; memory-leaks, race-condition

Fix bugs like StackOverflow, Thread-Leak, or Race-Condition. Optimize code generally
This commit is contained in:
TDSTOS
2026-04-13 02:42:24 +02:00
parent f9dba5b10d
commit b526b85262
10 changed files with 99 additions and 88 deletions

View File

@@ -212,6 +212,7 @@ class SpeedHG : JavaPlugin() {
override fun onDisable() override fun onDisable()
{ {
podiumManager.cleanup() podiumManager.cleanup()
gameManager.shutdown()
if ( ::lobbyAnnouncer.isInitialized ) lobbyAnnouncer.stop() if ( ::lobbyAnnouncer.isInitialized ) lobbyAnnouncer.stop()
if ( ::perkManager.isInitialized ) perkManager.shutdown() if ( ::perkManager.isInitialized ) perkManager.shutdown()
if ( ::tablistManager.isInitialized ) tablistManager.shutdown() if ( ::tablistManager.isInitialized ) tablistManager.shutdown()

View File

@@ -142,17 +142,19 @@ class StatsManager(private val plugin: SpeedHG) {
* Aufrufpunkt: `PlayerQuitEvent` — nach diesem Event brauchen wir die * Aufrufpunkt: `PlayerQuitEvent` — nach diesem Event brauchen wir die
* Daten nicht mehr im RAM. * Daten nicht mehr im RAM.
*/ */
fun saveAndEvict(uuid: UUID) { fun saveAndEvict( uuid: UUID ) {
val stats = cache[uuid] ?: return val stats = cache[ uuid ] ?: return
scope.launch { scope.launch {
try { try {
repository.upsert(stats) repository.upsert( stats )
} catch (e: Exception) { } catch ( e: Exception ) {
plugin.logger.severe("[StatsManager] Fehler beim Speichern für $uuid: ${e.message}") plugin.logger.severe( "[StatsManager] Fehler beim Speichern für $uuid: ${e.message}" )
} finally { } finally {
cache.remove(uuid) // Nur entfernen wenn der Cache-Eintrag noch unser ursprüngliches Objekt ist
dirtyFlags.remove(uuid) // (verhindert, dass ein Reconnect-Eintrag gelöscht wird)
cache.remove( uuid, stats )
dirtyFlags.remove( uuid )
} }
} }
} }

View File

@@ -60,40 +60,26 @@ class TornadoDisaster : NaturalDisaster() {
return eligibleBiomes.any { biome.contains( it ) } return eligibleBiomes.any { biome.contains( it ) }
} }
override fun trigger( override fun trigger( plugin: SpeedHG, player: Player )
plugin: SpeedHG, {
player: Player
) {
val center = player.location.clone() val center = player.location.clone()
val world = center.world ?: return val world = center.world ?: return
val coroutineScope = CoroutineScope( Dispatchers.Default + SupervisorJob() )
var tick = 0 var tick = 0
var angle = 0.0 var angle = 0.0
var mainTask: BukkitTask? = null
mainTask = Bukkit.getScheduler().runTaskTimer( plugin, { -> Bukkit.getScheduler().runTaskTimer( plugin, { ->
tick++ tick++
if ( tick > DURATION_TICKS ) if ( tick > DURATION_TICKS ) return@runTaskTimer // Bukkit cancelt via cancel()
{
mainTask?.cancel()
coroutineScope.cancel()
return@runTaskTimer
}
val currentAngle = angle
angle += ROTATION_SPEED
coroutineScope.launch {
val positions = ArrayList<Triple<Double, Double, Double>>( MAX_HEIGHT * SPOKES ) val positions = ArrayList<Triple<Double, Double, Double>>( MAX_HEIGHT * SPOKES )
for ( layer in 0..MAX_HEIGHT ) for ( layer in 0..MAX_HEIGHT )
{ {
val layerRadius = (( MAX_HEIGHT - layer ).toDouble() / MAX_HEIGHT ) val layerRadius = ( MAX_HEIGHT - layer ).toDouble() / MAX_HEIGHT
for ( spoke in 0 until SPOKES ) for ( spoke in 0 until SPOKES )
{ {
val a = currentAngle + ( spoke * ( Math.PI * 2.0 / SPOKES )) val a = angle + ( spoke * ( Math.PI * 2.0 / SPOKES ))
positions += Triple( positions += Triple(
center.x + cos( a ) * layerRadius, center.x + cos( a ) * layerRadius,
center.y + layer.toDouble(), center.y + layer.toDouble(),
@@ -101,14 +87,10 @@ class TornadoDisaster : NaturalDisaster() {
) )
} }
} }
angle += ROTATION_SPEED
Bukkit.getScheduler().runTask( plugin ) { -> positions.forEach { ( x, y, z ) ->
positions.forEach { (x, y, z) -> world.spawnParticle( Particle.CAMPFIRE_COSY_SMOKE, x, y, z, 1, 0.0, 0.0, 0.0, 0.0 )
world.spawnParticle(
Particle.CAMPFIRE_COSY_SMOKE,
x, y, z,
1, 0.0, 0.0, 0.0, 0.0
)
} }
if ( tick % 20 == 0 ) if ( tick % 20 == 0 )
@@ -119,21 +101,17 @@ class TornadoDisaster : NaturalDisaster() {
plugin.gameManager.alivePlayers plugin.gameManager.alivePlayers
.mapNotNull { Bukkit.getPlayer( it ) } .mapNotNull { Bukkit.getPlayer( it ) }
.filter { it.location.distance( center ) in 0.5..PULL_RADIUS }
.forEach { nearby -> .forEach { nearby ->
val dist = nearby.location.distance( center ) 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 strength = ( 1.0 - dist / PULL_RADIUS ) * 0.35
val toCenter: Vector = center.toVector() val toCenter = center.toVector()
.subtract( nearby.location.toVector() ) .subtract( nearby.location.toVector() )
.normalize() .normalize()
.multiply( strength ) .multiply( strength )
toCenter.y = strength * 0.45 toCenter.y = strength * 0.45
nearby.velocity = nearby.velocity.add( toCenter ) nearby.velocity = nearby.velocity.add( toCenter )
} }
}
}
}, 0L, 1L ) }, 0L, 1L )
} }

View File

@@ -21,6 +21,7 @@ import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType import org.bukkit.potion.PotionEffectType
import org.bukkit.scheduler.BukkitTask import org.bukkit.scheduler.BukkitTask
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.random.Random import kotlin.random.Random
class GameManager( class GameManager(
@@ -32,7 +33,7 @@ class GameManager(
var timer = 0 var timer = 0
val alivePlayers = mutableSetOf<UUID>() val alivePlayers: MutableSet<UUID> = ConcurrentHashMap.newKeySet()
private var gameTask: BukkitTask? = null private var gameTask: BukkitTask? = null
@@ -72,6 +73,13 @@ class GameManager(
}, 10L ) }, 10L )
} }
fun shutdown()
{
recraftManager.shutdown()
gameTask?.cancel()
gameTask = null
}
private var lobbyIdleCount: Int = 0 private var lobbyIdleCount: Int = 0
private fun gameLoop() private fun gameLoop()
@@ -442,7 +450,7 @@ class GameManager(
val x = Random.nextDouble( -maxRadius, maxRadius ) val x = Random.nextDouble( -maxRadius, maxRadius )
val z = Random.nextDouble( -maxRadius, maxRadius ) val z = Random.nextDouble( -maxRadius, maxRadius )
val y = world.getHighestBlockYAt( x.toInt(), z.toInt() ) + 1.0 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 block = loc.block.type
val below = loc.subtract( 0.0, 1.0, 0.0 ).block.type val below = loc.subtract( 0.0, 1.0, 0.0 ).block.type
@@ -452,7 +460,7 @@ class GameManager(
attempts++ attempts++
} while ( !safe && attempts < 20 ) } while ( !safe && attempts < 20 )
player.teleport(loc.add( 0.0, 1.0, 0.0 )) player.teleport( loc )
} }
private fun updateCompass() private fun updateCompass()

View File

@@ -11,8 +11,10 @@ import org.bukkit.GameMode
import org.bukkit.Sound import org.bukkit.Sound
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority
import org.bukkit.event.Listener import org.bukkit.event.Listener
import org.bukkit.event.entity.EntityDamageByEntityEvent import org.bukkit.event.entity.EntityDamageByEntityEvent
import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.potion.PotionEffect import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType import org.bukkit.potion.PotionEffectType
import java.util.* 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() private fun checkPlayers()
{ {
if (plugin.gameManager.currentState != GameState.INGAME) if (plugin.gameManager.currentState != GameState.INGAME)

View File

@@ -7,11 +7,14 @@ import org.bukkit.Material
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.scheduler.BukkitRunnable import org.bukkit.scheduler.BukkitRunnable
import org.bukkit.scheduler.BukkitTask
class RecraftManager( class RecraftManager(
private val plugin: SpeedHG private val plugin: SpeedHG
) { ) {
private var task: BukkitTask? = null
private val beforeFeast = plugin.config.getBoolean( "game.recraftNerf.beforeFeast", true ) private val beforeFeast = plugin.config.getBoolean( "game.recraftNerf.beforeFeast", true )
private val recraftNerfEnabled = plugin.config.getBoolean( "game.recraftNerf.enabled", false ) private val recraftNerfEnabled = plugin.config.getBoolean( "game.recraftNerf.enabled", false )
private val maxRecraftAmount = plugin.config.getInt( "game.recraftNerf.maxAmount", 64 ) private val maxRecraftAmount = plugin.config.getInt( "game.recraftNerf.maxAmount", 64 )
@@ -21,7 +24,7 @@ class RecraftManager(
if ( !recraftNerfEnabled ) if ( !recraftNerfEnabled )
return return
object : BukkitRunnable() { task = object : BukkitRunnable() {
override fun run() override fun run()
{ {
@@ -29,6 +32,7 @@ class RecraftManager(
plugin.gameManager.feastManager.hasSpawned ) plugin.gameManager.feastManager.hasSpawned )
{ {
this.cancel() this.cancel()
task = null
return return
} }
@@ -51,6 +55,12 @@ class RecraftManager(
}.runTaskTimer( plugin, 10L, 10L ) }.runTaskTimer( plugin, 10L, 10L )
} }
fun shutdown()
{
task?.cancel()
task = null
}
private class Recraft { private class Recraft {
private val recraftMaterials = listOf( private val recraftMaterials = listOf(

View File

@@ -276,7 +276,7 @@ class ArmorerKit : Kit()
// Shared no-active placeholder // Shared no-active placeholder
// ========================================================================= // =========================================================================
inner class NoActive( private class NoActive(
playstyle: Playstyle playstyle: Playstyle
) : ActiveAbility( playstyle ) ) : ActiveAbility( playstyle )
{ {

View File

@@ -181,7 +181,7 @@ class GladiatorKit : Kit()
player.location.clone().add( 0.0, 64.0, 0.0 ), player.location.clone().add( 0.0, 64.0, 0.0 ),
capturedRadius, capturedRadius,
capturedHeight + 2 capturedHeight + 2
) ) ?: return AbilityResult.ConditionNotMet( "No space found for gladiator arena" )
val center = BukkitAdapter.adapt( player.world, gladiatorRegion.center ) val center = BukkitAdapter.adapt( player.world, gladiatorRegion.center )
WorldEditUtils.createCylinder( player.world, center, capturedRadius - 1, true, 1, Material.WHITE_STAINED_GLASS ) WorldEditUtils.createCylinder( player.world, center, capturedRadius - 1, true, 1, Material.WHITE_STAINED_GLASS )
@@ -228,11 +228,13 @@ class GladiatorKit : Kit()
private fun getGladiatorLocation( private fun getGladiatorLocation(
location: Location, location: Location,
radius: Int, radius: Int,
height: Int height: Int,
): Region attempt: Int = 0
): Region?
{ {
val random = Random() if ( attempt >= 20 ) return null // Kein Platz gefunden → Ability abbrechen
val random = Random()
val region = CylinderRegion( val region = CylinderRegion(
BukkitAdapter.adapt( location.world ), BukkitAdapter.adapt( location.world ),
BukkitAdapter.asBlockVector( location ), BukkitAdapter.asBlockVector( location ),
@@ -248,7 +250,8 @@ class GladiatorKit : Kit()
if ( random.nextBoolean() ) -10.0 else 10.0 if ( random.nextBoolean() ) -10.0 else 10.0
), ),
radius, radius,
height height,
attempt + 1 // ← Versuch mitzählen
) )
else region else region
} }

View File

@@ -67,7 +67,7 @@ import kotlin.math.sin
* [lastHitEnemy]-Fenster und berechnet eine Position 1,8 Blöcke hinter dem Feind. * [lastHitEnemy]-Fenster und berechnet eine Position 1,8 Blöcke hinter dem Feind.
* *
* ### Smoke-Mechanismus * ### 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. * im Ring erhält Blindness I + Slowness I, die regelmäßig erneuert werden.
*/ */
class NinjaKit : Kit() { class NinjaKit : Kit() {

View File

@@ -40,11 +40,9 @@ class TeamSelectionListener(
} }
@EventHandler( priority = EventPriority.MONITOR ) @EventHandler( priority = EventPriority.MONITOR )
fun onQuit( fun onQuit( event: PlayerQuitEvent )
event: PlayerQuitEvent {
) { // Immer aus dem Team-Tracking entfernen, unabhängig vom GameState
val state = plugin.gameManager.currentState
if ( state == GameState.LOBBY || state == GameState.STARTING )
plugin.presetTeamManager.leave( event.player ) plugin.presetTeamManager.leave( event.player )
} }