Files
GameModes-SpeedHG/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt
TDSTOS 8c2ab684bb Add perk system with GUI and integrations
Introduce a complete passive perk system: Perk base class, PerkManager (registration, selection, lifecycle, persistence), and PlayerPerksRepository (DB schema + upsert/find). Add four example perks (Oracle, Vampire, Featherweight, Bloodlust) and a single PerkEventDispatcher to route combat/environment/kill events to active perks. Provide PerkSelectorMenu GUI and /perks command, integrate perk initialization, registration, application and cleanup into SpeedHG and GameManager, and hook load/evict into StatsListener. Also add language entries and register the command in plugin.yml. This change enables players to select up to two passive perks, persists selections, and dispatches relevant events to perk implementations.
2026-04-04 03:16:43 +02:00

442 lines
11 KiB
Kotlin

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<UUID>()
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)
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()
}
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()
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,
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 )
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() )
}
plugin.discordWebhookManager.broadcastEmbed(
title = "🎮 Spiel gestartet!",
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().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
pitManager.reset()
val winnerUUID = alivePlayers.firstOrNull()
Bukkit.getOnlinePlayers().forEach { p ->
if ( p.uniqueId == winnerUUID )
{
plugin.statsManager.addWin( p.uniqueId )
plugin.rankingManager.onPlayerResult( p, isWinner = true )
}
}
plugin.kitManager.clearAll()
plugin.perkManager.removeAllActivePerks()
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 )
}
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 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
}
}