/* * 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.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.colorByTeam 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.message import de.steamwar.tntleague.plugin import de.steamwar.tntleague.util.* import org.bukkit.GameMode import org.bukkit.Location import org.bukkit.Material 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 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.impl.createScoreboard(it, TNTLeagueScoreboard(it)) } blueTeam.start() redTeam.start() message.broadcast("GAME_STARTED") 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) { message.broadcast("TIME_REMAINING", (gameTimeRemaining / 60)) plugin.server.onlinePlayers.forEach { it.playSound(it.location, Sound.BLOCK_NOTE_BLOCK_PLING, 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.impl.removeScoreboard(it) it.playSound(it.location, Sound.ENTITY_ENDER_DRAGON_DEATH, 1f, 1f) } message.broadcast("GAME_ENDED") spawnerTask.cancel() var shutdown = 10 plugin.server.scheduler.runTaskTimer(plugin, bukkit { if (shutdown == 0) { plugin.server.shutdown() } message.broadcast("SHUTDOWN", shutdown) 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 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 } } }, 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 } state = GameState.LOBBY } } fun win(tntLeagueTeam: TNTLeagueTeam, reason: WinReason) { if (state != GameState.RUNNING) return end() plugin.server.onlinePlayers.forEach { message.send("TEAM_WIN", it, message.parse(tntLeagueTeam.name, it).colorByTeam(tntLeagueTeam)) } statistic(tntLeagueTeam, reason) explode(tntLeagueTeam.opposite) } fun draw(reason: WinReason) { if (state != GameState.RUNNING) return end() message.broadcast("DRAW") 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 } }