package club.mcscrims.speedhg.game import club.mcscrims.speedhg.SpeedHG import club.mcscrims.speedhg.game.modules.FeastManager import club.mcscrims.speedhg.game.modules.PitManager import club.mcscrims.speedhg.game.modules.RecraftManager 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) private val borderShrinkTime = plugin.config.getLong("game.border-shrink-time", 600) private val maxRadiusTeleport = plugin.config.getDouble("game.max-radius-teleport", 50.0) val feastManager = FeastManager( plugin ) val pitManager = PitManager( plugin ) val recraftManager = RecraftManager( plugin ) init { plugin.server.pluginManager.registerEvents( this, plugin ) gameTask = Bukkit.getScheduler().runTaskTimer( plugin, { -> gameLoop() }, 20L, 20L ) recraftManager.startRunnable() Bukkit.getScheduler().runTaskLater( plugin, { -> val world = Bukkit.getWorld( "world" ) world?.worldBorder?.apply { center = Location( world, 0.0, 0.0, 0.0 ) size = startBorder damageBuffer = 0.0 damageAmount = 1.0 } }, 10L ) } private var lobbyIdleCount: Int = 0 private fun gameLoop() { when( currentState ) { GameState.LOBBY -> { if ( Bukkit.getOnlinePlayers().size >= minPlayers ) { setGameState( GameState.STARTING ) timer = lobbyTime } else { if ( lobbyIdleCount >= 15 ) { lobbyIdleCount = 0 Bukkit.getOnlinePlayers().forEach { it.sendMsg( "game.lobby-idle", "current" to Bukkit.getOnlinePlayers().size.toString(), "min" to minPlayers.toString() ) } } lobbyIdleCount++ } } 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() feastManager.onTick( timer ) pitManager.onTick( timer ) } GameState.ENDING -> { timer-- if ( timer <= 0 ) Bukkit.shutdown() } } } fun setGameState( newState: GameState ) { this.currentState = newState } private fun startGame() { feastManager.reset() pitManager.reset() plugin.lobbyItemManager.clearAll() setGameState( GameState.INVINCIBILITY ) timer = invincibilityTime val world = Bukkit.getWorld( "world" ) ?: return world.time = 0 world.setStorm( false ) world.worldBorder.apply { center = Location( world, 0.0, 0.0, 0.0 ) size = startBorder damageBuffer = 0.0 damageAmount = 1.0 } val speedEffect = PotionEffect( PotionEffectType.SPEED, invincibilityTime * 20, 0, false, false, true ) val hasteEffect = PotionEffect( PotionEffectType.HASTE, invincibilityTime * 20, 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, maxRadiusTeleport ) plugin.kitManager.applyKit( player ) // verteilt Items + ruft onAssign + passive.onActivate plugin.perkManager.applyPerks( player ) player.inventory.addItem(ItemStack( Material.COMPASS )) player.sendMsg( "game.started" ) } plugin.rankingManager.startRound( Bukkit.getOnlinePlayers() ) Bukkit.getOnlinePlayers().forEach { player -> player.sendMsg( "game.invincibility-start", "time" to invincibilityTime.toString() ) } val variantName = plugin.config.getString( "lunarclient.variantName" ) plugin.discordWebhookManager.broadcastEmbed( title = "🎮 Spiel gestartet! (${variantName})", description = "Eine neue Runde SpeedHG mit **${Bukkit.getOnlinePlayers().size}** Spielern hat begonnen!", colorHex = 0x55FF55 // Grün ) } 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 plugin.statsManager.addDeath( player.uniqueId ) plugin.statsManager.addLoss( player.uniqueId ) plugin.rankingManager.onPlayerResult( player, isWinner = false ) if ( killer != null ) { killer.exp += 0.5f plugin.statsManager.addKill( killer.uniqueId ) plugin.rankingManager.registerRoundKill( killer.uniqueId ) } player.inventory.contents.filterNotNull() .filter { !plugin.kitManager.isKitItem( player, it ) } .forEach { item -> player.world.dropItemNaturally( player.location, item ) } 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 val teamManager = plugin.presetTeamManager val roundOver = when { // Nur noch 0 oder 1 Spieler übrig → immer Ende alivePlayers.size <= 1 -> true // Teams aktiv: Alle Überlebenden im selben Team → Team gewinnt teamManager.isEnabled && teamManager.allAliveInSameTeam(alivePlayers) -> true else -> false } if (roundOver) { val winnerUUID = alivePlayers.firstOrNull() // Den sichtbaren Gewinner-Namen ermitteln: // Bei Team-Sieg alle Mitglieder des Gewinner-Teams anzeigen. val winnerName = buildWinnerName(winnerUUID) endGame(winnerName) } } private fun endGame( winnerName: String ) { setGameState( GameState.ENDING ) timer = 15 pitManager.reset() val winnerUUID = alivePlayers.firstOrNull() val winnerTeam = if ( plugin.presetTeamManager.isEnabled && winnerUUID != null ) plugin.presetTeamManager.getTeam( winnerUUID ) else null Bukkit.getOnlinePlayers().forEach { p -> val isWinner = winnerTeam?.contains( p.uniqueId ) ?: ( p.uniqueId == winnerUUID ) if ( isWinner ) { plugin.statsManager.addWin( p.uniqueId ) plugin.rankingManager.onPlayerResult( p, isWinner = true ) } 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 ) } plugin.kitManager.clearAll() plugin.perkManager.removeAllActivePerks() plugin.discordWebhookManager.broadcastEmbed( title = "🏆 Spiel beendet!", description = "**$winnerName** hat das Spiel gewonnen! GG!", colorHex = 0xFFAA00 // Gold ) Bukkit.getScheduler().runTaskLater( plugin, { -> plugin.podiumManager.launch( winnerUUID ) }, 60L ) } // --- Helfer Methoden --- private fun buildWinnerName(anyAliveUUID: UUID?): String { anyAliveUUID ?: return "N/A" val teamManager = plugin.presetTeamManager if (!teamManager.isEnabled) { return Bukkit.getPlayer(anyAliveUUID)?.name ?: "N/A" } val winnerTeam = teamManager.getTeam(anyAliveUUID) return if (winnerTeam != null && winnerTeam.size > 1) { // "PlayerA & PlayerB" als Team-Gewinner-String winnerTeam.members .mapNotNull { Bukkit.getPlayer(it)?.name } .joinToString(" & ") } else { Bukkit.getPlayer(anyAliveUUID)?.name ?: "N/A" } } 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 ) && !plugin.perkManager.isGhost( it ) } for ( p in players ) { var nearest: Player? = null var minDistance = Double.MAX_VALUE for ( target in players ) { if ( p == target ) continue if ( plugin.presetTeamManager.isEnabled && plugin.presetTeamManager.areInSameTeam( 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 } }