diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/ResourcePack.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/ResourcePack.kt index f22c651..5295775 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/ResourcePack.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/ResourcePack.kt @@ -8,13 +8,15 @@ object ResourcePack { val deathReason = Material.GRAY_STAINED_GLASS_PANE val questionMark = Material.GRASS_BLOCK val time = Material.CLOCK + val dot = Material.GRAY_STAINED_GLASS + val arrowDown = Material.WHITE_STAINED_GLASS // Roles val innocent = Material.GREEN_STAINED_GLASS_PANE val detective = Material.YELLOW_STAINED_GLASS_PANE val traitor = Material.RED_STAINED_GLASS_PANE val jackal = Material.LIGHT_BLUE_STAINED_GLASS_PANE - val sidekick = Material.LIGHT_BLUE_STAINED_GLASS_PANE + val sidekick = Material.BLUE_STAINED_GLASS_PANE // Special Items val cloakingDevice = Material.COBWEB diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/TTTItemListener.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/TTTItemListener.kt index 059598e..f82ef4b 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/TTTItemListener.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/TTTItemListener.kt @@ -11,6 +11,7 @@ import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.entity.EntityDamageByEntityEvent import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.InventoryCloseEvent import org.bukkit.event.player.PlayerEvent import org.bukkit.event.player.PlayerInteractEvent import org.bukkit.event.player.PlayerItemConsumeEvent @@ -73,6 +74,13 @@ open class TTTItemListener(private val tttItem: TTTItem, private val cancelDamag } } + protected fun handle(event: InventoryCloseEvent, handler: (tttPlayer: TTTPlayer) -> Unit) { + val player = event.player + if (player is Player) { + handler(PlayerManager.getTTTPlayer(player) ?: return) + } + } + protected fun handle(event: T, handler: (tttPlayer: TTTPlayer) -> Unit) { handler(PlayerManager.getTTTPlayer(event.player) ?: return) } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/corpses/TTTCorpse.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/corpses/TTTCorpse.kt index a03d3d2..2de86ac 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/corpses/TTTCorpse.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/corpses/TTTCorpse.kt @@ -124,6 +124,7 @@ class TTTCorpse private constructor( } fun destroy() { + ensureNotDestroyed() status = Status.DESTROYED CorpseAPI.removeCorpse(corpse) updateTimeListener.cancel() @@ -146,15 +147,13 @@ class TTTCorpse private constructor( private const val REASON_SLOT = 1 private const val TIME_SLOT = 2 - fun spawn(tttPlayer: TTTPlayer, reason: DeathReason) { - CorpseManager.add(TTTCorpse( - tttPlayer, - tttPlayer.player.location, - tttPlayer.role, - reason, - tttPlayer.credits - )) - } + fun spawn(tttPlayer: TTTPlayer, reason: DeathReason): TTTCorpse = TTTCorpse( + tttPlayer, + tttPlayer.player.location, + tttPlayer.role, + reason, + tttPlayer.credits + ).also { CorpseManager.add(it) } fun spawnFake(role: Role, tttPlayer: TTTPlayer, location: Location) { CorpseManager.add(TTTCorpse( diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/StateContainer.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/StateContainer.kt index 65dc531..5278897 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/StateContainer.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/StateContainer.kt @@ -39,7 +39,7 @@ class InversedStateContainer(private val stateClass: KClass) { val tttPlayers get() = PlayerManager.tttPlayers.filter { it.stateContainer.has(stateClass) } - fun forEachState(fn: (T, TTTPlayer) -> Unit) { + fun forEveryState(fn: (T, TTTPlayer) -> Unit) { tttPlayers.forEach { it.stateContainer.get(stateClass)?.run { fn(this, it) } } } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/TTTPlayer.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/TTTPlayer.kt index 760112a..701edc1 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/TTTPlayer.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/TTTPlayer.kt @@ -84,15 +84,21 @@ class TTTPlayer(player: Player, role: Role) { fun onDeath(reason: DeathReason = DeathReason.SUICIDE) { GameManager.ensurePhase(GamePhase.COMBAT) + player.sendMessage(TTTPlugin.prefix + "${ChatColor.RED}${ChatColor.BOLD}Du bist gestorben") + player.gameMode = GameMode.SPECTATOR alive = false - TTTCorpse.spawn(this, reason) + val tttCorpse = TTTCorpse.spawn(this, reason) player.inventory.clear() credits = 0 -// PlayerManager.letRemainingRoleGroupWin() - plugin.server.pluginManager.callEvent(TTTPlayerDeathEvent(this, player.location)) + val event = TTTPlayerDeathEvent(this, player.location, tttCorpse) + plugin.server.pluginManager.callEvent(event) + + if (event.letRoundEnd) { +// PlayerManager.letRemainingRoleGroupWin() + } } fun revive(location: Location, credits: Int = 0) { @@ -106,6 +112,7 @@ class TTTPlayer(player: Player, role: Role) { Shop.setItems(this) + plugin.server.pluginManager.callEvent(TTTPlayerReviveEvent(this)) player.sendMessage(TTTPlugin.prefix + "${ChatColor.GREEN}${ChatColor.BOLD}Du wurdest wiederbelebt") plugin.server.scheduler.runTask(plugin, fun() { diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/TTTPlayerDeathEvent.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/TTTPlayerDeathEvent.kt index 954c0dd..520b983 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/TTTPlayerDeathEvent.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/TTTPlayerDeathEvent.kt @@ -1,18 +1,22 @@ package de.moritzruth.spigot_ttt.game.players +import de.moritzruth.spigot_ttt.game.corpses.TTTCorpse import org.bukkit.Location import org.bukkit.event.Event import org.bukkit.event.HandlerList class TTTPlayerDeathEvent( val tttPlayer: TTTPlayer, - val location: Location + val location: Location, + val tttCorpse: TTTCorpse ): Event() { override fun getHandlers(): HandlerList { @Suppress("RedundantCompanionReference") // false positive return Companion.handlers } + var letRoundEnd = true + companion object { private val handlers = HandlerList() diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/TTTPlayerReviveEvent.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/TTTPlayerReviveEvent.kt new file mode 100644 index 0000000..e3fba90 --- /dev/null +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/TTTPlayerReviveEvent.kt @@ -0,0 +1,18 @@ +package de.moritzruth.spigot_ttt.game.players + +import org.bukkit.event.Event +import org.bukkit.event.HandlerList + +class TTTPlayerReviveEvent(val tttPlayer: TTTPlayer): Event() { + override fun getHandlers(): HandlerList { + @Suppress("RedundantCompanionReference") // false positive + return Companion.handlers + } + + companion object { + private val handlers = HandlerList() + + @JvmStatic + fun getHandlerList() = handlers + } +} diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/items/ItemManager.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/items/ItemManager.kt index dff15e9..144971f 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/items/ItemManager.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/items/ItemManager.kt @@ -29,7 +29,7 @@ object ItemManager { BaseballBat, CloakingDevice, Rifle, EnderPearl, Radar, HealingPotion, Fireball, - Teleporter, MartyrdomGrenade, FakeCorpse, Defibrillator + Teleporter, MartyrdomGrenade, FakeCorpse, Defibrillator, SecondChance ) val droppedItemStates = mutableMapOf() diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/Defibrillator.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/Defibrillator.kt index 85fef96..9b2f8c9 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/Defibrillator.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/Defibrillator.kt @@ -82,7 +82,7 @@ object Defibrillator: TTTItem, Buyable { } @EventHandler - fun onGameEnd(event: GameEndEvent) = isc.forEachState { state, tttPlayer -> state.reset(tttPlayer) } + fun onGameEnd(event: GameEndEvent) = isc.forEveryState { state, tttPlayer -> state.reset(tttPlayer) } } sealed class Action(val tttPlayer: TTTPlayer) { diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/MartyrdomGrenade.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/MartyrdomGrenade.kt index a42c0d0..22d964f 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/MartyrdomGrenade.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/MartyrdomGrenade.kt @@ -59,7 +59,7 @@ object MartyrdomGrenade: TTTItem, Buyable { } @EventHandler - fun onGameEnd(event: GameEndEvent) = isc.forEachState { state, _ -> + fun onGameEnd(event: GameEndEvent) = isc.forEveryState { state, _ -> state.explodeTask?.cancel() state.explodeTask = null } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/Radar.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/Radar.kt index 3f30e12..108168d 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/Radar.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/Radar.kt @@ -107,7 +107,7 @@ object Radar: TTTItem, Buyable { } @EventHandler - fun onGameEnd(event: GameEndEvent) = isc.forEachState { state, tttPlayer -> state.reset(tttPlayer) } + fun onGameEnd(event: GameEndEvent) = isc.forEveryState { state, tttPlayer -> state.reset(tttPlayer) } } override val packetListener = object : PacketAdapter(plugin, PacketType.Play.Server.ENTITY_METADATA) { diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/SecondChance.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/SecondChance.kt new file mode 100644 index 0000000..ad34939 --- /dev/null +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/items/impl/SecondChance.kt @@ -0,0 +1,155 @@ +package de.moritzruth.spigot_ttt.items.impl + +import de.moritzruth.spigot_ttt.ResourcePack +import de.moritzruth.spigot_ttt.TTTItemListener +import de.moritzruth.spigot_ttt.game.GameEndEvent +import de.moritzruth.spigot_ttt.game.GameManager +import de.moritzruth.spigot_ttt.game.players.* +import de.moritzruth.spigot_ttt.items.Buyable +import de.moritzruth.spigot_ttt.items.PASSIVE +import de.moritzruth.spigot_ttt.items.TTTItem +import de.moritzruth.spigot_ttt.plugin +import de.moritzruth.spigot_ttt.utils.applyMeta +import de.moritzruth.spigot_ttt.utils.hideInfo +import de.moritzruth.spigot_ttt.utils.setAllToItem +import org.bukkit.ChatColor +import org.bukkit.Location +import org.bukkit.boss.BarColor +import org.bukkit.boss.BarStyle +import org.bukkit.event.EventHandler +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.InventoryCloseEvent +import org.bukkit.event.inventory.InventoryType +import org.bukkit.inventory.ItemStack +import org.bukkit.scheduler.BukkitTask +import java.time.Duration +import java.time.Instant +import kotlin.random.Random + +object SecondChance: TTTItem, Buyable { + private val DISPLAY_NAME = "${ChatColor.GREEN}${ChatColor.BOLD}Second Chance" + val ON_CORPSE = ResourcePack.Items.arrowDown + val ON_SPAWN = ResourcePack.Items.dot + private const val TIMEOUT = 20.0 + + override val type = TTTItem.Type.SPECIAL + override val itemStack = ItemStack(ResourcePack.Items.secondChance).applyMeta { + setDisplayName("$DISPLAY_NAME $PASSIVE") + hideInfo() + lore = listOf( + "", + "${ChatColor.GOLD}Du wirst mit einer 50%-Chance", + "${ChatColor.GOLD}wiederbelebt, wenn du stirbst" + ) + } + override val buyableBy = roles(Role.TRAITOR, Role.JACKAL) + override val buyLimit = 1 + override val price = 2 + val isc = InversedStateContainer(State::class) + + private val chooseSpawnInventory = plugin.server.createInventory( + null, + InventoryType.CHEST, + "${ChatColor.DARK_RED}${ChatColor.BOLD}Second Chance" + ).apply { + setAllToItem(setOf(0, 1, 2, 9, 10, 11, 18, 19, 20), ItemStack(ON_CORPSE).applyMeta { + hideInfo() + setDisplayName("${ChatColor.GREEN}${ChatColor.BOLD}Bei der Leiche") + }) + + setAllToItem(setOf(3, 4, 5, 12, 13, 14, 21, 22, 23), ItemStack(ResourcePack.Items.textureless).applyMeta { + hideInfo() + setDisplayName("${ChatColor.RESET}${ChatColor.BOLD}Wo möchtest du spawnen?") + }) + + setAllToItem(setOf(6, 7, 8, 15, 16, 17, 24, 25, 26), ItemStack(ON_SPAWN).applyMeta { + hideInfo() + setDisplayName("${ChatColor.AQUA}${ChatColor.BOLD}Am Spawn") + }) + } + + override fun onBuy(tttPlayer: TTTPlayer) { + isc.getOrCreate(tttPlayer) + } + + override val listener = object : TTTItemListener(this, true) { + @EventHandler + fun onTTTPlayerDeath(event: TTTPlayerDeathEvent) { + val state = isc.get(event.tttPlayer) + if (state != null) { + if (Random.nextBoolean()) { + event.letRoundEnd = false + event.tttPlayer.player.openInventory(chooseSpawnInventory) + state.timeoutAction = TimeoutAction(event.tttPlayer, event.tttCorpse.corpse.trueLocation) + } + } + } + + @EventHandler + fun onInventoryClose(event: InventoryCloseEvent) = handle(event) { tttPlayer -> + if (event.inventory == chooseSpawnInventory) { + if (isc.get(tttPlayer)?.timeoutAction != null) { + plugin.server.scheduler.runTask(plugin, fun() { + if (isc.get(tttPlayer) != null) tttPlayer.player.openInventory(chooseSpawnInventory) + }) + } + } + } + + @EventHandler + fun onInventoryClick(event: InventoryClickEvent) = handle(event) { tttPlayer -> + if (event.clickedInventory != chooseSpawnInventory) return@handle + val state = isc.get(tttPlayer) ?: return@handle + val timeoutAction = state.timeoutAction ?: return@handle + + val location = when (event.currentItem?.type) { + ON_SPAWN -> GameManager.world.spawnLocation + ON_CORPSE -> timeoutAction.deathLocation + else -> return@handle + } + + timeoutAction.stop() + tttPlayer.revive(location) + } + + @EventHandler + fun onTTTPlayerRevive(event: TTTPlayerReviveEvent) { + isc.get(event.tttPlayer)?.timeoutAction?.stop() + } + + @EventHandler + fun onGameEnd(event: GameEndEvent) { + isc.forEveryState { state, _ -> state.timeoutAction?.stop() } + } + } + + class TimeoutAction( + private val tttPlayer: TTTPlayer, + val deathLocation: Location + ) { + private val startedAt = Instant.now()!! + private var bossBar = plugin.server.createBossBar( + "${ChatColor.GREEN}${ChatColor.BOLD}Second Chance", + BarColor.GREEN, + BarStyle.SOLID + ).also { it.addPlayer(tttPlayer.player) } + + private var task: BukkitTask = plugin.server.scheduler.runTaskTimer(plugin, fun() { + val duration = Duration.between(startedAt, Instant.now()).toMillis().toDouble() / 1000 + val progress = duration / TIMEOUT + + if (progress > 1) stop() else bossBar.progress = 1.0 - progress + }, 0, 1) + + fun stop() { + isc.remove(tttPlayer) + task.cancel() + tttPlayer.player.closeInventory() + bossBar.removePlayer(tttPlayer.player) + } + } + + class State: IState { + var timeoutAction: TimeoutAction? = null + } +} diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/items/weapons/guns/Gun.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/items/weapons/guns/Gun.kt index 34f3d27..2a43428 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/items/weapons/guns/Gun.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/items/weapons/guns/Gun.kt @@ -201,7 +201,7 @@ abstract class Gun( fun onTTTPlayerDeath(event: TTTPlayerDeathEvent) = isc.get(event.tttPlayer)?.reset() @EventHandler - fun onGameEnd(event: GameEndEvent) = isc.forEachState { state, _ -> state.reset() } + fun onGameEnd(event: GameEndEvent) = isc.forEveryState { state, _ -> state.reset() } } class ActionInProgressError: RuntimeException("The gun has an ongoing action which may not be canceled") diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/utils/InventoryExtensions.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/utils/InventoryExtensions.kt index aec24d1..40f4a08 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/utils/InventoryExtensions.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/utils/InventoryExtensions.kt @@ -10,7 +10,7 @@ fun Inventory.setAllToItem(indexes: Iterable, itemStack: ItemStack) { indexes.forEach { setItem(it, itemStack) } } -fun Inventory.removeTTTItem(tttItem: TTTItem) = clear(indexOfFirst { it.type == tttItem.itemStack.type }) +fun Inventory.removeTTTItem(tttItem: TTTItem) = clear(indexOfFirst { it?.type == tttItem.itemStack.type }) fun Inventory.removeTTTItemNextTick(tttItem: TTTItem) = plugin.server.scheduler.runTask(plugin, fun() { removeTTTItem(tttItem) })