Files
SteamWar/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueGame.kt
T

277 lines
10 KiB
Kotlin

/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.tntleague.game
import de.steamwar.kotlin.message.*
import de.steamwar.kotlin.util.Area
import de.steamwar.scoreboard.SWScoreboard
import de.steamwar.sql.Fight
import de.steamwar.sql.FightPlayer
import de.steamwar.sql.SteamwarUser
import de.steamwar.tntleague.config.TNTLeagueConfig
import de.steamwar.tntleague.config.TNTLeagueWorldConfig
import de.steamwar.tntleague.config.world
import de.steamwar.tntleague.events.DummyListener
import de.steamwar.tntleague.events.IngameListener
import de.steamwar.tntleague.events.LobbyListener
import de.steamwar.tntleague.inventory.DealerInventory
import de.steamwar.tntleague.plugin
import de.steamwar.tntleague.util.*
import net.kyori.adventure.bossbar.BossBar
import net.kyori.adventure.sound.Sound
import org.bukkit.GameMode
import org.bukkit.Location
import org.bukkit.Material
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
set(value) {
if (field.listener != value.listener) {
HandlerList.unregisterAll(field.listener)
plugin.server.pluginManager.registerEvents(value.listener, plugin)
}
field = value
}
var gameTimeRemaining: Int = TNTLeagueConfig.config.gameTime
val blueTeam = TNTLeagueTeam(TNTLeagueWorldConfig.blueTeam, TNTLeagueTeam.Team.BLUE)
val redTeam = TNTLeagueTeam(TNTLeagueWorldConfig.redTeam, TNTLeagueTeam.Team.RED)
private lateinit var start: Timestamp
private var task: Int? = 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
plugin.server.onlinePlayers.forEach { SWScoreboard.createScoreboard(it, TNTLeagueScoreboard(it)) }
blueTeam.start()
redTeam.start()
plugin.server.broadcast(translate("gameStarted").success())
val tnt = ItemStack(Material.TNT)
start = Timestamp.from(Instant.now())
spawnerTask = plugin.server.scheduler.runTaskTimer(plugin, bukkit {
if (world.getNearbyEntitiesByType(Item::class.java, TNTLeagueWorldConfig.blueTeam.itemSpawn, 3.0).sumOf { it.itemStack.amount } <= 256) {
spawnItems(TNTLeagueWorldConfig.blueTeam.itemSpawn, tnt)
spawnItems(TNTLeagueWorldConfig.blueTeam.itemSpawn, DealerInventory.coins)
}
if (world.getNearbyEntitiesByType(Item::class.java, TNTLeagueWorldConfig.redTeam.itemSpawn, 3.0).sumOf { it.itemStack.amount } <= 256) {
spawnItems(TNTLeagueWorldConfig.redTeam.itemSpawn, tnt)
spawnItems(TNTLeagueWorldConfig.redTeam.itemSpawn, DealerInventory.coins)
}
}, 5, 10)
timerTask = plugin.server.scheduler.runTaskTimer(plugin, bukkit {
gameTimeRemaining--
if (gameTimeRemaining == 0) {
draw(WinReason.TIMEOUT)
return@bukkit
}
if (gameTimeRemaining % 300 == 0) {
plugin.server.broadcast(translate("timeRemaining", (gameTimeRemaining / 60).toString().yellow()).basic())
plugin.server.onlinePlayers.forEach { it.playSound(Sound.sound(org.bukkit.Sound.BLOCK_NOTE_BLOCK_PLING.key, Sound.Source.MASTER, 1f, 1f)) }
}
}, 20, 20)
}
private fun bukkit(f: () -> Unit): () -> Unit = f
private fun end() {
if(state != GameState.RUNNING) return
state = GameState.END
plugin.server.onlinePlayers.forEach {
it.gameMode = GameMode.SPECTATOR
SWScoreboard.removeScoreboard(it)
it.playSound(Sound.sound(org.bukkit.Sound.ENTITY_ENDER_DRAGON_DEATH.key, Sound.Source.MASTER, 1f, 1f))
}
plugin.server.broadcast(translate("gameEnded").success())
spawnerTask.cancel()
var shutdown = 10
plugin.server.scheduler.runTaskTimer(plugin, bukkit {
if (shutdown == 0) {
plugin.server.shutdown()
}
plugin.server.broadcast(translate("shutdown", shutdown.toString().yellow()).basic())
shutdown--
}, 20, 20)
}
private fun spawnItems(loc: Location, item: ItemStack) = 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
fun getFreeTeam() = if (blueTeam.leader == null) blueTeam else if (redTeam.leader == null) redTeam else null
fun checkStart() {
if (blueTeam.isReady && redTeam.isReady) {
blueTeam.leader?.inventory?.clear()
redTeam.leader?.inventory?.clear()
state = GameState.STARTING
var countdown = TNTLeagueConfig.config.startDelay
plugin.server.broadcast(translate("gameStarting", countdown.toString().yellow()).basic())
val bar = BossBar.bossBar(translate("gameStart", countdown.toString().yellow()).gray(), (TNTLeagueConfig.config.startDelay - countdown) / TNTLeagueConfig.config.startDelay.toFloat(), BossBar.Color.GREEN, BossBar.Overlay.NOTCHED_10)
plugin.server.onlinePlayers.forEach { bar.addViewer(it) }
task = plugin.server.scheduler.scheduleSyncRepeatingTask(plugin, {
plugin.server.onlinePlayers.forEach { it.playSound(Sound.sound(org.bukkit.Sound.ENTITY_EXPERIENCE_ORB_PICKUP.key, Sound.Source.MASTER, 1f, 1f)) }
if (countdown-- == 0) {
plugin.server.onlinePlayers.forEach { it.hideBossBar(bar) }
task = task?.also { plugin.server.scheduler.cancelTask(it) }.let { null }
setup()
} else {
bar.name(translate("gameStart", countdown.toString().yellow()).gray())
bar.progress((TNTLeagueConfig.config.startDelay - countdown) / TNTLeagueConfig.config.startDelay.toFloat())
plugin.server.onlinePlayers.filter { !it.activeBossBars().contains(bar) }.forEach { bar.addViewer(it) }
}
}, 20, 20)
if (task == -1) {
error("Failed to start countdown task")
}
}
}
fun playerLeave(player: Player) {
blueTeam.invites.remove(player)
redTeam.invites.remove(player)
getTeam(player)?.apply {
members.remove(player)
if (leader == player && members.isNotEmpty() && state == GameState.RUNNING) {
win(this.opposite, WinReason.LEAVE)
}
}
}
fun reset() {
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 }
plugin.server.onlinePlayers.forEach { p -> p.activeBossBars().forEach { it.removeViewer(p) } }
state = GameState.LOBBY
}
}
fun win(tntLeagueTeam: TNTLeagueTeam, reason: WinReason) {
if (state != GameState.RUNNING) return
end()
plugin.server.broadcast(translate("teamWin", translate(tntLeagueTeam.name).color(tntLeagueTeam.color)).success())
statistic(tntLeagueTeam, reason)
explode(tntLeagueTeam.opposite)
}
fun draw(reason: WinReason) {
if (state != GameState.RUNNING) return
end()
plugin.server.broadcast(translate("draw").success())
statistic(null, reason)
}
fun explode(team: TNTLeagueTeam) {
Area(team.config.spawnLocation.clone().add(20.0, 30.0, 20.0), team.config.spawnLocation.clone().subtract(20.0, 0.0, 20.0).add(0.0, 30.0, 0.0))
.locations
.filterIndexed { index, _ -> index % 7 == 0 }
.forEachIndexed { index, location ->
world.spawn(location, TNTPrimed::class.java).apply {
fuseTicks = index + 40
}
}
}
private fun statistic(winTeam: TNTLeagueTeam?, reason: WinReason) {
val fightId = Fight.create(
"TNTLeague",
world.name,
start,
TNTLeagueConfig.config.gameTime - gameTimeRemaining,
SteamwarUser.get(blueTeam.leader!!.uniqueId).id,
SteamwarUser.get(redTeam.leader!!.uniqueId).id,
null,
null,
when (winTeam) {
blueTeam -> 1
redTeam -> 2
else -> 0
},
when (reason) {
WinReason.TIMEOUT -> "TIMEOUT"
WinReason.DESTROYED -> "DESTROYED"
WinReason.LEAVE -> "LEAVE"
}
)
addTeamMember(blueTeam, fightId)
addTeamMember(redTeam, fightId)
}
private fun addTeamMember(team: TNTLeagueTeam, fightId: Int) {
team.members.filter { team.leader != it }
.forEach {
FightPlayer.create(
fightId,
SteamwarUser.get(it.uniqueId).id,
team == blueTeam,
"TNTLeague",
0,
false
)
}
}
enum class GameState(val listener: Listener) {
LOBBY(LobbyListener),
STARTING(LobbyListener),
RUNNING(IngameListener),
END(DummyListener);
}
enum class WinReason {
TIMEOUT,
DESTROYED,
LEAVE
}
}