Migrate FightSystem modes and countdowns to GameCore

- Move shared arena-mode abstractions into GameCore
- Adapt fight countdowns and APIs to the shared model
- Add SteamWar issue audit workflow and SQL language settings
This commit is contained in:
2026-07-24 12:18:49 +02:00
parent badff1c70f
commit 867ca6f5dc
133 changed files with 2155 additions and 1076 deletions
+2 -1
View File
@@ -27,6 +27,7 @@ kotlin {
dependencies {
compileOnly(libs.paperapi)
compileOnly(project(":GameCore", "default"))
compileOnly(project(":SpigotCore", "default"))
compileOnly(project(":KotlinCore", "default"))
}
}
@@ -22,7 +22,7 @@ package de.steamwar.tntleague
import de.steamwar.message.Message
import de.steamwar.tntleague.command.*
import de.steamwar.tntleague.events.GlobalListener
import de.steamwar.tntleague.events.LobbyListener
import de.steamwar.tntleague.game.TNTLeagueGame
import de.steamwar.tntleague.game.TNTLeagueTeam
import org.bukkit.Bukkit
import org.bukkit.plugin.java.JavaPlugin
@@ -38,8 +38,8 @@ class TNTLeague : JavaPlugin() {
override fun onEnable() {
message = Message("de.steamwar.tntleague.TNTLeague", classLoader)
server.pluginManager.registerEvents(LobbyListener, this)
server.pluginManager.registerEvents(GlobalListener, this)
TNTLeagueGame.initialize()
logger.info("TNTLeague enabled")
@@ -19,6 +19,8 @@
package de.steamwar.tntleague.config
import de.steamwar.gamecore.ArenaMode
import de.steamwar.gamecore.config.GameRuntimeProperties
import de.steamwar.message.SubMessage
import de.steamwar.sql.Event
import de.steamwar.sql.EventFight
@@ -32,16 +34,19 @@ import org.bukkit.inventory.meta.Damageable
import org.bukkit.inventory.meta.ItemMeta
import java.util.*
private val runtimeProperties = GameRuntimeProperties.load()
data class TNTLeagueConfig(
val startDelay: Int = 10,
val gameTime: Int = 60 * 20,
val prices: Map<Material, Price>,
val blueLeader: UUID? = System.getProperty("blueLeader")?.let { UUID.fromString(it) },
val redLeader: UUID? = System.getProperty("redLeader")?.let { UUID.fromString(it) },
val blueLeader: UUID? = runtimeProperties.blueLeader,
val redLeader: UUID? = runtimeProperties.redLeader,
val eventFightId: Int? = System.getProperty("fightID")?.toInt()
val eventFightId: Int? = runtimeProperties.fightId.takeIf { it != 0 },
val arenaMode: ArenaMode = ArenaMode.from(runtimeProperties),
) {
lateinit var eventFight: EventFight
lateinit var event: Event
@@ -92,9 +97,7 @@ data class TNTLeagueConfig(
}
}
fun isEvent() =
(config.eventFightId
?: 0) > 0
fun isEvent() = config.arenaMode == ArenaMode.EVENT
}
data class Price(
@@ -19,6 +19,12 @@
package de.steamwar.tntleague.game
import de.steamwar.gamecore.countdown.Countdown
import de.steamwar.gamecore.state.GameStateMachine
import de.steamwar.gamecore.state.StateDependentListener
import de.steamwar.gamecore.team.TeamRegistry
import de.steamwar.gamecore.wincondition.GameResult
import de.steamwar.gamecore.wincondition.WinConditions
import de.steamwar.kotlin.util.Area
import de.steamwar.network.NetworkSender
import de.steamwar.network.packets.common.FightInfoPacket
@@ -41,38 +47,49 @@ import org.bukkit.Sound
import org.bukkit.entity.Item
import org.bukkit.entity.Player
import org.bukkit.entity.TNTPrimed
import org.bukkit.event.HandlerList
import org.bukkit.event.Listener
import org.bukkit.inventory.ItemStack
import org.bukkit.scheduler.BukkitTask
import java.sql.Timestamp
import java.time.Instant
object TNTLeagueGame {
var state: GameState = GameState.LOBBY
private val stateMachine = GameStateMachine(GameState.LOBBY)
private val stateDependencies by lazy {
listOf(
StateDependentListener(stateMachine, true, setOf(GameState.LOBBY, GameState.STARTING), plugin, LobbyListener),
StateDependentListener(stateMachine, true, setOf(GameState.RUNNING), plugin, IngameListener),
StateDependentListener(stateMachine, true, setOf(GameState.END), plugin, DummyListener),
)
}
var state: GameState
get() = stateMachine.currentState
set(value) {
if (field.listener != value.listener) {
HandlerList.unregisterAll(field.listener)
plugin.server.pluginManager.registerEvents(value.listener, plugin)
}
field = value
stateMachine.transitionTo(value)
}
fun initialize() {
stateDependencies
}
var gameTimeRemaining: Int = TNTLeagueConfig.config.gameTime
val blueTeam = TNTLeagueTeam(TNTLeagueConfig.config.blueTeam)
val redTeam = TNTLeagueTeam(TNTLeagueConfig.config.redTeam)
private val teams = TeamRegistry<TNTLeagueTeam, Player>(listOf(blueTeam, redTeam))
private lateinit var start: Timestamp
private var task: Int? = null
private var startCountdown: Countdown? = null
private var gameCountdown: Countdown? = null
private var endCountdown: Countdown? = null
private lateinit var spawnerTask: BukkitTask
private lateinit var timerTask: BukkitTask
private fun setup() {
assert(state == GameState.STARTING) { "Game is already running" }
state = GameState.RUNNING
gameTimeRemaining = TNTLeagueConfig.config.gameTime
plugin.server.onlinePlayers.forEach { SWScoreboard.impl.createScoreboard(it, TNTLeagueScoreboard(it)) }
@@ -104,24 +121,33 @@ object TNTLeagueGame {
}
}, 5, 10)
timerTask = plugin.server.scheduler.runTaskTimer(plugin, bukkit {
gameTimeRemaining--
if (gameTimeRemaining == 0) {
if (blueTeam.damagedBlocks > redTeam.damagedBlocks) {
win(blueTeam, WinReason.TIMEOUT)
} else if (redTeam.damagedBlocks > blueTeam.damagedBlocks) {
win(redTeam, WinReason.TIMEOUT)
} else {
draw(WinReason.TIMEOUT)
gameCountdown = object : Countdown(
plugin,
TNTLeagueConfig.config.gameTime,
20,
20,
false,
) {
override fun onTick(timeLeft: Int) {
gameTimeRemaining = timeLeft
if (timeLeft > 0 && timeLeft % 300 == 0) {
message.broadcast("TIME_REMAINING", (timeLeft / 60))
plugin.server.onlinePlayers.forEach {
it.playSound(it.location, Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 1f)
}
}
return@bukkit
}
if (gameTimeRemaining % 300 == 0) {
message.broadcast("TIME_REMAINING", (gameTimeRemaining / 60))
plugin.server.onlinePlayers.forEach { it.playSound(it.location, Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 1f) }
override fun onFinished() {
when (val result = WinConditions.winnerByScore(
teams.teams,
{ team -> team.damagedBlocks.toDouble() },
)) {
is GameResult.Winner -> win(result.winner, WinReason.TIMEOUT)
GameResult.Draw -> draw(WinReason.TIMEOUT)
}
}
}, 20, 20)
}.also { it.enable() }
}
private fun bukkit(f: () -> Unit): () -> Unit =
@@ -140,18 +166,19 @@ object TNTLeagueGame {
message.broadcast("GAME_ENDED")
spawnerTask.cancel()
gameCountdown?.disable()
var shutdown = 10
plugin.server.scheduler.runTaskTimer(plugin, bukkit {
if (shutdown == 0) {
plugin.server.shutdown()
endCountdown = object : Countdown(plugin, 11, 20, 20, false) {
override fun onTick(timeLeft: Int) {
if (timeLeft > 0) {
message.broadcast("SHUTDOWN", timeLeft)
}
}
message.broadcast("SHUTDOWN", shutdown)
shutdown--
}, 20, 20)
override fun onFinished() {
plugin.server.shutdown()
}
}.also { it.enable() }
updateFightinfo()
}
@@ -160,7 +187,10 @@ object TNTLeagueGame {
plugin.server.worlds.first().dropItem(loc, item)
fun getTeam(player: Player) =
if (player in blueTeam.members) blueTeam else if (player in redTeam.members) redTeam else null
teams.findTeam(player)
fun opposite(team: TNTLeagueTeam) =
teams.opposite(team)
fun getFreeTeam(player: Player) =
if (TNTLeagueConfig.isEvent()) getEventFreeTeam(player) else getNormalFreeTeam(player)
@@ -178,27 +208,31 @@ object TNTLeagueGame {
}
fun checkStart() {
if (blueTeam.isReady && redTeam.isReady) {
if (teams.allReady()) {
blueTeam.leader?.inventory?.clear()
redTeam.leader?.inventory?.clear()
state = GameState.STARTING
var countdown = TNTLeagueConfig.config.startDelay
message.broadcast("GAME_STARTING", countdown.toString())
task = plugin.server.scheduler.scheduleSyncRepeatingTask(plugin, {
plugin.server.onlinePlayers.forEach { it.playSound(it.location, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f) }
if (--countdown == 0) {
plugin.server.onlinePlayers.forEach { it.level = 0 }
task = task?.also { plugin.server.scheduler.cancelTask(it) }.let { null }
setup()
} else {
plugin.server.onlinePlayers.forEach { it.level = countdown }
message.broadcast("GAME_STARTING", TNTLeagueConfig.config.startDelay.toString())
startCountdown = object : Countdown(
plugin,
TNTLeagueConfig.config.startDelay,
20,
20,
false,
) {
override fun onTick(timeLeft: Int) {
plugin.server.onlinePlayers.forEach {
it.playSound(it.location, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f)
it.level = timeLeft
}
}
}, 20, 20)
if (task == -1) {
error("Failed to start countdown task")
}
override fun onFinished() {
plugin.server.onlinePlayers.forEach { it.level = 0 }
setup()
}
}.also { it.enable() }
updateFightinfo()
}
@@ -216,7 +250,8 @@ object TNTLeagueGame {
assert(state == GameState.LOBBY || state == GameState.STARTING) { "Game is not in lobby or starting state" }
if (state == GameState.STARTING) {
task = task?.also { plugin.server.scheduler.cancelTask(it) }.let { null }
startCountdown?.disable()
startCountdown = null
state = GameState.LOBBY
}
@@ -277,11 +312,11 @@ object TNTLeagueGame {
))
}
enum class GameState(val listener: Listener, val lobbyName: String) {
LOBBY(LobbyListener, "PRE_LEADER_SETUP"),
STARTING(LobbyListener, "POST_SCHEM_SETUP"),
RUNNING(IngameListener, "RUNNING"),
END(DummyListener, "SPECTATE");
enum class GameState(val lobbyName: String) {
LOBBY("PRE_LEADER_SETUP"),
STARTING("POST_SCHEM_SETUP"),
RUNNING("RUNNING"),
END("SPECTATE");
}
enum class WinReason {
@@ -289,4 +324,4 @@ object TNTLeagueGame {
DESTROYED,
LEAVE
}
}
}
@@ -20,6 +20,7 @@
package de.steamwar.tntleague.game
import de.steamwar.message.SubMessage
import de.steamwar.gamecore.team.GameTeam
import de.steamwar.tntleague.colorByTeam
import de.steamwar.tntleague.config.TNTLeagueWorldConfig
import de.steamwar.tntleague.config.TeamConfig
@@ -36,7 +37,7 @@ import org.bukkit.Sound
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
data class TNTLeagueTeam(val config: TeamConfig) {
data class TNTLeagueTeam(val config: TeamConfig) : GameTeam<Player> {
var leader: Player? = null
set(player) {
@@ -82,11 +83,7 @@ data class TNTLeagueTeam(val config: TeamConfig) {
var coins: Int = 0
val opposite: TNTLeagueTeam
get() = when (this) {
TNTLeagueGame.redTeam -> TNTLeagueGame.blueTeam
TNTLeagueGame.blueTeam -> TNTLeagueGame.redTeam
else -> error("Invalid Team")
}
get() = TNTLeagueGame.opposite(this)
val ergebnisInt: Int
get() = when (this) {
@@ -95,6 +92,21 @@ data class TNTLeagueTeam(val config: TeamConfig) {
else -> error("Invalid Team")
}
override fun teamId() =
name.message
override fun members(): Collection<Player> =
members
override fun leader(): Player? =
leader
override fun ready() =
isReady
override fun contains(member: Player) =
member in members
fun join(player: Player): Boolean {
members.add(player)
@@ -168,4 +180,4 @@ data class TNTLeagueTeam(val config: TeamConfig) {
}
updateFightinfo()
}
}
}
+4 -1
View File
@@ -5,9 +5,12 @@ load: POSTWORLD
api-version: '1.21'
dependencies:
server:
GameCore:
required: true
load: BEFORE
SpigotCore:
required: true
load: BEFORE
KotlinCore:
required: true
load: BEFORE
load: BEFORE