package club.mcscrims.speedhg.game import club.mcscrims.speedhg.SpeedHG import club.mcscrims.speedhg.util.sendMsg import club.mcscrims.speedhg.util.trans import net.kyori.adventure.title.Title import org.bukkit.* import org.bukkit.attribute.Attribute import org.bukkit.entity.Player import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.entity.EntityDamageByEntityEvent import org.bukkit.event.entity.EntityDamageEvent import org.bukkit.event.entity.PlayerDeathEvent import org.bukkit.event.player.PlayerQuitEvent import org.bukkit.inventory.ItemStack import org.bukkit.potion.PotionEffect import org.bukkit.potion.PotionEffectType import org.bukkit.scheduler.BukkitTask import java.util.* import kotlin.random.Random class GameManager( private val plugin: SpeedHG ): Listener { var currentState: GameState = GameState.LOBBY private set var timer = 0 val alivePlayers = mutableSetOf() private var gameTask: BukkitTask? = null // Einstellungen aus Config (gecached für Performance) private val minPlayers = plugin.config.getInt("game.min-players", 2) private val lobbyTime = plugin.config.getInt("game.lobby-time", 60) private val invincibilityTime = plugin.config.getInt("game.invincibility-time", 60) private val startBorder = plugin.config.getDouble("game.border-start", 300.0) private val endBorder = plugin.config.getDouble("game.border-end", 20.0) // Zeit in Sekunden, bis Border komplett klein ist (z.B. 10 Min) private val borderShrinkTime = plugin.config.getLong("game.border-shrink-time", 600) init { plugin.server.pluginManager.registerEvents( this, plugin ) gameTask = Bukkit.getScheduler().runTaskTimer( plugin, { -> gameLoop() }, 20L, 20L ) } private fun gameLoop() { when( currentState ) { GameState.LOBBY -> { if ( Bukkit.getOnlinePlayers().size >= minPlayers ) { setGameState( GameState.STARTING ) timer = lobbyTime } } GameState.STARTING -> { if ( Bukkit.getOnlinePlayers().size < minPlayers ) { setGameState( GameState.LOBBY ) Bukkit.getOnlinePlayers().forEach { player -> player.sendMsg( "game.start-aborted" ) } return } timer-- if (timer in listOf( 60, 30, 10, 5, 4, 3, 2, 1 )) { Bukkit.getOnlinePlayers().forEach { p -> p.sendMsg( "timer.lobby", "time" to timer.toString() ) p.playSound( p.location, Sound.BLOCK_NOTE_BLOCK_HAT, 1f, 1f ) } } if ( timer <= 0 ) startGame() } GameState.INVINCIBILITY -> { timer-- if ( timer <= 0 ) startFighting() else { Bukkit.getOnlinePlayers().forEach { p -> p.sendActionBar(p.trans( "timer.actionbar-invincibility", "time" to timer.toString() )) } } } GameState.INGAME -> { timer++ updateCompass() checkWin() } GameState.ENDING -> { timer-- if ( timer <= 0 ) Bukkit.shutdown() } } } fun setGameState( newState: GameState ) { this.currentState = newState } private fun startGame() { setGameState( GameState.INVINCIBILITY ) timer = invincibilityTime val world = Bukkit.getWorld( "world" ) ?: return world.time = 0 world.setStorm( false ) val border = world.worldBorder border.center = Location( world, 0.0, 0.0, 0.0 ) border.size = startBorder border.damageBuffer = 0.0 border.damageAmount = 1.0 val speedEffect = PotionEffect( PotionEffectType.SPEED, timer, 0, false, false, true ) val hasteEffect = PotionEffect( PotionEffectType.HASTE, timer, 0, false, false, true ) alivePlayers.clear() Bukkit.getOnlinePlayers().forEach { player -> alivePlayers.add( player.uniqueId ) player.gameMode = GameMode.SURVIVAL player.health = player.getAttribute( Attribute.GENERIC_MAX_HEALTH )?.value ?: 20.0 player.foodLevel = 20 player.inventory.clear() player.activePotionEffects.forEach { player.removePotionEffect( it.type ) } player.addPotionEffects(listOf( speedEffect, hasteEffect )) teleportRandomly( player, world, startBorder / 2 ) // TODO: Kit Items geben // plugin.kitManager.giveKit( player ) player.inventory.addItem(ItemStack( Material.COMPASS )) player.sendMsg( "game.started" ) } Bukkit.getOnlinePlayers().forEach { player -> player.sendMsg( "game.invincibility-start" ) } } private fun startFighting() { setGameState( GameState.INGAME ) timer = 0 // Reset Timer für "Ingame" val world = Bukkit.getWorld( "world" ) ?: return world.worldBorder.setSize( endBorder, borderShrinkTime ) plugin.antiRunningManager.resetTimers() Bukkit.getOnlinePlayers().forEach { p -> p.sendMsg( "game.fighting-started" ) p.playSound( p.location, Sound.ENTITY_ENDER_DRAGON_GROWL, 1f, 1f ) p.showTitle(Title.title( p.trans( "title.fight-main" ), p.trans( "title.fight-sub" ) )) } } fun onPlayerEliminated( player: Player, killer: Player? ) { if (!alivePlayers.contains( player.uniqueId )) return alivePlayers.remove( player.uniqueId ) player.gameMode = GameMode.SPECTATOR player.inventory.contents.filterNotNull().forEach { player.world.dropItemNaturally( player.location, it ) } val msgKey = if ( killer != null ) "game.death-killed" else "game.death-pve" val killerName = killer?.name ?: "Environment" Bukkit.getOnlinePlayers().forEach { p -> p.sendMsg( msgKey, "player" to player.name, "killer" to killerName, "left" to alivePlayers.size.toString() ) } checkWin() } private fun checkWin() { if ( currentState != GameState.INGAME && currentState != GameState.INVINCIBILITY ) return if ( alivePlayers.size <= 1 ) { val winnerUUID = alivePlayers.firstOrNull() val winnerName = if ( winnerUUID != null ) Bukkit.getPlayer( winnerUUID )?.name ?: "N/A" else "N/A" endGame( winnerName ) } } private fun endGame( winnerName: String ) { setGameState( GameState.ENDING ) timer = 15 Bukkit.getOnlinePlayers().forEach { p -> p.showTitle(Title.title( p.trans( "title.win-main", "winner" to winnerName ), p.trans( "title.win-sub" ) )) p.sendMsg( "game.win-chat", "winner" to winnerName ) } } // --- Helfer Methoden --- private fun teleportRandomly( player: Player, world: World, maxRadius: Double ) { var loc: Location var safe = false var attempts = 0 do { 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 ) val block = loc.block.type val below = loc.subtract( 0.0, 1.0, 0.0 ).block.type if ( below.isSolid && below != Material.LAVA && below != Material.CACTUS && block == Material.AIR ) safe = true attempts++ } while ( !safe && attempts < 20 ) player.teleport(loc.add( 0.0, 1.0, 0.0 )) } private fun updateCompass() { val players = Bukkit.getOnlinePlayers().filter { alivePlayers.contains( it.uniqueId ) } for ( p in players ) { var nearest: Player? = null var minDistance = Double.MAX_VALUE for ( target in players ) { if ( p == target ) continue val dist = p.location.distanceSquared( target.location ) if ( dist < minDistance ) { minDistance = dist nearest = target } } if ( nearest != null ) p.compassTarget = nearest.location else p.compassTarget = p.world.spawnLocation } } // --- Event Listener Integration --- @EventHandler fun onDeath( event: PlayerDeathEvent ) { if ( currentState == GameState.INGAME || currentState == GameState.INVINCIBILITY ) { event.deathMessage( null ) event.drops.clear() Bukkit.getScheduler().runTask( plugin ) { -> event.entity.spigot().respawn() } onPlayerEliminated( event.entity, event.entity.killer ) } } @EventHandler fun onQuit( event: PlayerQuitEvent ) { if (alivePlayers.contains( event.player.uniqueId )) { if ( currentState == GameState.INGAME || currentState == GameState.INVINCIBILITY ) { val lastDamageCause = event.player.lastDamageCause if ( lastDamageCause != null && lastDamageCause is EntityDamageByEntityEvent ) { val attacker = lastDamageCause.damager if ( attacker is Player ) { onPlayerEliminated( event.player, attacker ) return } } onPlayerEliminated( event.player, null ) // PVE Tod } else alivePlayers.remove( event.player.uniqueId ) } } @EventHandler fun onDamage( event: EntityDamageEvent ) { if ( currentState == GameState.INVINCIBILITY && event.entity is Player ) event.isCancelled = true if ( currentState == GameState.LOBBY || currentState == GameState.STARTING || currentState == GameState.ENDING ) event.isCancelled = true // Nie Schaden in Lobby/Ende } }