/* * 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 . */ 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 } }