Remove legacy modules, add language & scoreboard
Large refactor removing many legacy subsystems (abilities, kit system, database repos, recraft, world manager, extensive config classes, lunar/luckperms integrations and various listeners/commands). Introduces a lightweight LanguageManager, AntiRunningManager, ScoreboardManager, ConnectListener and a SoupListener; simplifies the main SpeedHG plugin to initialize these components and register the connection listener. Build changes: update Gradle wrapper to 8.10, remove paperweight and several external dependencies, add fr.mrmicky:fastboard and simplify shadowJar/build task configuration. Adds default language resource (languages/en_US.yml) and updates plugin/config resources. Purpose: simplify and decouple the plugin, reduce dependency surface and prepare for a leaner, modular rewrite.
This commit is contained in:
@@ -1,138 +1,370 @@
|
||||
package club.mcscrims.speedhg.game
|
||||
|
||||
import club.mcscrims.speedhg.SpeedHG
|
||||
import club.mcscrims.speedhg.game.impl.BattleState
|
||||
import club.mcscrims.speedhg.game.impl.DeathmatchState
|
||||
import club.mcscrims.speedhg.game.impl.EndState
|
||||
import club.mcscrims.speedhg.game.impl.FeastState
|
||||
import club.mcscrims.speedhg.game.impl.ImmunityState
|
||||
import club.mcscrims.speedhg.game.impl.PreStartState
|
||||
import club.mcscrims.speedhg.game.impl.WaitingState
|
||||
import org.bukkit.Location
|
||||
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.util.BoundingBox
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
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 {
|
||||
|
||||
private var currentState: GameState? = null
|
||||
internal val gameStateTypes = ConcurrentHashMap<GameStateTypes, GameState>()
|
||||
var currentState: GameState = GameState.LOBBY
|
||||
private set
|
||||
|
||||
private val winners = ArrayList<Player>()
|
||||
var timer = 0
|
||||
|
||||
internal lateinit var feastLocation: Location
|
||||
internal lateinit var feastBox: BoundingBox
|
||||
internal var feastHeight: Int = 1
|
||||
val alivePlayers = mutableSetOf<UUID>()
|
||||
|
||||
fun initialize()
|
||||
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()
|
||||
{
|
||||
currentState = WaitingState(
|
||||
this,
|
||||
plugin,
|
||||
plugin.schedulerManager,
|
||||
plugin.pluginConfig.data.getDuration( "waiting" ).seconds
|
||||
)
|
||||
when( currentState )
|
||||
{
|
||||
GameState.LOBBY ->
|
||||
{
|
||||
if ( Bukkit.getOnlinePlayers().size >= minPlayers )
|
||||
{
|
||||
setGameState( GameState.STARTING )
|
||||
timer = lobbyTime
|
||||
}
|
||||
}
|
||||
|
||||
gameStateTypes[ GameStateTypes.WAITING ] = currentState!!
|
||||
gameStateTypes[ GameStateTypes.PRE_START ] = PreStartState( this, plugin, plugin.schedulerManager, plugin.pluginConfig.data.getDuration( "pre_start" ).seconds )
|
||||
gameStateTypes[ GameStateTypes.IMMUNITY ] = ImmunityState( this, plugin, plugin.schedulerManager, plugin.pluginConfig.data.getDuration( "immunity" ).seconds )
|
||||
gameStateTypes[ GameStateTypes.BATTLE ] = BattleState( this, plugin, plugin.schedulerManager, plugin.pluginConfig.data.getDuration( "battle" ).seconds )
|
||||
gameStateTypes[ GameStateTypes.FEAST ] = FeastState( this, plugin, plugin.schedulerManager, plugin.pluginConfig.data.getDuration( "feast" ).seconds )
|
||||
gameStateTypes[ GameStateTypes.DEATHMATCH ] = DeathmatchState( this, plugin, plugin.schedulerManager, plugin.pluginConfig.data.getDuration( "deathmatch" ).seconds )
|
||||
gameStateTypes[ GameStateTypes.END ] = EndState( this, plugin, plugin.schedulerManager, plugin.pluginConfig.data.getDuration( "end" ).seconds )
|
||||
GameState.STARTING ->
|
||||
{
|
||||
if ( Bukkit.getOnlinePlayers().size < minPlayers )
|
||||
{
|
||||
setGameState( GameState.LOBBY )
|
||||
Bukkit.getOnlinePlayers().forEach { player ->
|
||||
player.sendMsg( "game.start-aborted" )
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
currentState?.onEnter( null )
|
||||
} catch ( e: Exception ) {
|
||||
plugin.logger.severe("Error during onEnter for state ${currentState?.name}: ${e.message}")
|
||||
e.printStackTrace()
|
||||
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 transitionTo(
|
||||
stateType: GameStateTypes
|
||||
fun setGameState(
|
||||
newState: GameState
|
||||
) {
|
||||
val previousState = currentState
|
||||
val nextState = gameStateTypes[ stateType ]!!
|
||||
this.currentState = newState
|
||||
}
|
||||
|
||||
try {
|
||||
currentState?.onExit( nextState )
|
||||
} catch ( e: Exception ) {
|
||||
plugin.logger.severe("Error during onExit for state ${currentState?.name}: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
private fun startGame()
|
||||
{
|
||||
setGameState( GameState.INVINCIBILITY )
|
||||
timer = invincibilityTime
|
||||
|
||||
if ( nextState is FeastState )
|
||||
{
|
||||
feastLocation = nextState.feastLocation
|
||||
feastBox = nextState.feastBox
|
||||
feastHeight = nextState.feastHeight
|
||||
}
|
||||
val world = Bukkit.getWorld( "world" ) ?: return
|
||||
world.time = 0
|
||||
world.setStorm( false )
|
||||
|
||||
currentState = nextState
|
||||
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
|
||||
|
||||
try {
|
||||
nextState.onEnter( previousState )
|
||||
} catch ( e: Exception ) {
|
||||
plugin.logger.severe("Error during onEnter for state ${nextState.name}: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
val speedEffect = PotionEffect(
|
||||
PotionEffectType.SPEED,
|
||||
timer,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
)
|
||||
|
||||
fun addWinners(
|
||||
vararg winners: Player
|
||||
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?
|
||||
) {
|
||||
winners.forEach { this.winners.add( it ) }
|
||||
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()
|
||||
}
|
||||
|
||||
fun getWinners(): List<Player> = winners.toList()
|
||||
|
||||
fun getCurrentState(): GameState? = currentState
|
||||
|
||||
fun getCurrentStateType(): GameStateTypes? = gameStateTypes.filter { it.value.name == currentState?.name }.keys.firstOrNull()
|
||||
|
||||
fun isRunning(): Boolean
|
||||
private fun checkWin()
|
||||
{
|
||||
return getCurrentStateType() == GameStateTypes.IMMUNITY ||
|
||||
getCurrentStateType() == GameStateTypes.BATTLE ||
|
||||
getCurrentStateType() == GameStateTypes.FEAST ||
|
||||
getCurrentStateType() == GameStateTypes.DEATHMATCH
|
||||
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 )
|
||||
}
|
||||
}
|
||||
|
||||
fun isBeforeFeast(): Boolean
|
||||
{
|
||||
return getCurrentStateType() == GameStateTypes.WAITING ||
|
||||
getCurrentStateType() == GameStateTypes.PRE_START ||
|
||||
getCurrentStateType() == GameStateTypes.IMMUNITY ||
|
||||
(getCurrentStateType() == GameStateTypes.BATTLE &&
|
||||
!( currentState as BattleState ).afterFeast )
|
||||
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 )
|
||||
}
|
||||
}
|
||||
|
||||
fun isBefore(
|
||||
stateType: GameStateTypes
|
||||
): Boolean
|
||||
{
|
||||
return getCurrentStateType()?.points!! < stateType.points
|
||||
// --- 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 ))
|
||||
}
|
||||
|
||||
fun shutdown()
|
||||
private fun updateCompass()
|
||||
{
|
||||
currentState?.onExit( null )
|
||||
currentState = null
|
||||
}
|
||||
val players = Bukkit.getOnlinePlayers().filter { alivePlayers.contains( it.uniqueId ) }
|
||||
|
||||
}
|
||||
for ( p in players )
|
||||
{
|
||||
var nearest: Player? = null
|
||||
var minDistance = Double.MAX_VALUE
|
||||
|
||||
enum class GameStateTypes(
|
||||
val points: Int
|
||||
) {
|
||||
WAITING( 0 ),
|
||||
PRE_START( 1 ),
|
||||
IMMUNITY( 2 ),
|
||||
BATTLE( 3 ),
|
||||
FEAST( 4 ),
|
||||
DEATHMATCH( 5 ),
|
||||
END( 6 )
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user