diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/GameListener.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/GameListener.kt index 56facfa..d0477d2 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/GameListener.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/GameListener.kt @@ -1,190 +1,28 @@ package de.moritzruth.spigot_ttt.game -import com.comphenix.packetwrapper.WrapperPlayServerPlayerInfo -import com.comphenix.protocol.PacketType -import com.comphenix.protocol.events.PacketAdapter -import com.comphenix.protocol.events.PacketEvent -import com.comphenix.protocol.wrappers.EnumWrappers -import com.comphenix.protocol.wrappers.PlayerInfoData -import de.moritzruth.spigot_ttt.TTTPlugin -import de.moritzruth.spigot_ttt.game.players.* -import de.moritzruth.spigot_ttt.plugin -import de.moritzruth.spigot_ttt.utils.call -import de.moritzruth.spigot_ttt.utils.nextTick -import org.bukkit.ChatColor -import org.bukkit.GameMode -import org.bukkit.Material +import de.moritzruth.spigot_ttt.game.players.TTTPlayer import org.bukkit.entity.Player -import org.bukkit.event.EventHandler -import org.bukkit.event.EventPriority import org.bukkit.event.Listener -import org.bukkit.event.block.Action -import org.bukkit.event.entity.EntityDamageByEntityEvent -import org.bukkit.event.entity.EntityDamageEvent -import org.bukkit.event.entity.PlayerDeathEvent -import org.bukkit.event.player.* -import java.util.* +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.InventoryCloseEvent +import org.bukkit.event.player.PlayerEvent -object GameListener : Listener { - private val BLOCKED_COMMANDS = setOf("me", "tell", "msg") - - @EventHandler - fun onPlayerJoin(event: PlayerJoinEvent) = PlayerManager.onPlayerJoin(event.player) - - @EventHandler - fun onPlayerQuit(event: PlayerQuitEvent) = PlayerManager.onPlayerQuit(event.player) - - @EventHandler - fun onPlayerCommandPreprocess(event: PlayerCommandPreprocessEvent) { - if (event.message.startsWith("/rl") && GameManager.phase != null) { // /reload is not blocked - event.player.sendMessage(TTTPlugin.prefix + "${ChatColor.RED}The server may not be reloaded while the game is running") - event.player.sendMessage(TTTPlugin.prefix + "${ChatColor.RED}You can force reload by using ${ChatColor.WHITE}/reload") - event.isCancelled = true - return - } - - if (BLOCKED_COMMANDS.find { event.message.startsWith("/$it") } != null) { - if (GameManager.phase != null) { - event.player.sendMessage(TTTPlugin.prefix + "${ChatColor.RED}Dieser Befehl ist blockiert.") - event.isCancelled = true - } +abstract class GameListener: Listener { + protected fun handle(event: InventoryClickEvent, handler: (tttPlayer: TTTPlayer) -> Unit) { + val whoClicked = event.whoClicked + if (whoClicked is Player) { + handler(TTTPlayer.of(whoClicked) ?: return) } } - @EventHandler - fun onEntityDamageByEntity(event: EntityDamageByEntityEvent) { - val player = event.damager + protected open fun handle(event: InventoryCloseEvent, handler: (tttPlayer: TTTPlayer) -> Unit) { + val player = event.player if (player is Player) { - if (player.inventory.itemInMainHand.type == Material.AIR) { - event.damage = 0.2 - } + handler(TTTPlayer.of(player) ?: return) } } - private val ZERO_NO_DAMAGE_TICKS_CAUSES = EnumSet.of( - EntityDamageEvent.DamageCause.ENTITY_ATTACK, - EntityDamageEvent.DamageCause.CUSTOM, - EntityDamageEvent.DamageCause.ENTITY_EXPLOSION, - EntityDamageEvent.DamageCause.BLOCK_EXPLOSION, - EntityDamageEvent.DamageCause.FALL, - EntityDamageEvent.DamageCause.ENTITY_SWEEP_ATTACK, - EntityDamageEvent.DamageCause.FALLING_BLOCK, - EntityDamageEvent.DamageCause.SUICIDE - )!! - - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - fun onEntityDamageLow(event: EntityDamageEvent) { - if (ZERO_NO_DAMAGE_TICKS_CAUSES.contains(event.cause)) { - val player = event.entity - if (player is Player) { - nextTick { player.noDamageTicks = 0 } - } - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - fun onEntityDamageHighest(event: EntityDamageEvent) { - if (event.entity !is Player) return - val tttPlayer = TTTPlayer.of(event.entity as Player) ?: return - if (event.cause == EntityDamageEvent.DamageCause.CUSTOM) return - - val reason = when (event.cause) { - EntityDamageEvent.DamageCause.FALL -> DeathReason.FALL - EntityDamageEvent.DamageCause.BLOCK_EXPLOSION, - EntityDamageEvent.DamageCause.ENTITY_EXPLOSION -> DeathReason.EXPLOSION - EntityDamageEvent.DamageCause.DROWNING -> DeathReason.DROWNED - EntityDamageEvent.DamageCause.FIRE, - EntityDamageEvent.DamageCause.FIRE_TICK, - EntityDamageEvent.DamageCause.LAVA, - EntityDamageEvent.DamageCause.HOT_FLOOR -> DeathReason.FIRE - EntityDamageEvent.DamageCause.POISON, - EntityDamageEvent.DamageCause.WITHER -> DeathReason.POISON - else -> DeathReason.SUICIDE - } - - val e = TTTPlayerDamageEvent(tttPlayer, event.finalDamage, reason).call() - - if (tttPlayer.player.health - e.damage <= 0) { - tttPlayer.onDeath(reason, null) - event.damage = 0.0 - } else { - event.damage = e.damage - } - } - - @EventHandler - fun onPlayerDeath(event: PlayerDeathEvent) { - event.deathMessage = null - } - - @EventHandler - fun onPlayerInteract(event: PlayerInteractEvent) { - if (event.player.inventory.itemInMainHand.type == Material.AIR && event.action == Action.LEFT_CLICK_BLOCK) { - GameManager.destroyBlock(event.clickedBlock!!) - } - } - - @EventHandler(priority = EventPriority.LOWEST) - fun onPlayerSwapHandItemsLowest(event: PlayerSwapHandItemsEvent) { - event.isCancelled = true - } - - @EventHandler - fun onAsyncPlayerChat(event: AsyncPlayerChatEvent) { - val senderTTTPlayer = TTTPlayer.of(event.player) ?: return - - if (!senderTTTPlayer.alive) { - PlayerManager.tttPlayers.filter { !it.alive }.forEach { - it.player.sendMessage("${ChatColor.GRAY}[${ChatColor.RED}TOT${ChatColor.GRAY}] <${event.player.displayName}> ${event.message}") - } - - event.isCancelled = true - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - fun onTTTPlayerTrueDeath(event: TTTPlayerTrueDeathEvent) { - if (event.winnerRoleGroup != RoleGroup.JACKAL && event.tttPlayer.role == Role.JACKAL) { - val sidekicks = PlayerManager.tttPlayers.filter { it.role == Role.SIDEKICK } - - if (sidekicks.isNotEmpty()) { - val newJackal = sidekicks.random() - newJackal.changeRole(Role.JACKAL) - - event.tttPlayer.changeRole(Role.SIDEKICK) // The old Jackal - - sidekicks.forEach { sidekick -> - if (sidekick != newJackal) { - sidekick.player.sendMessage(TTTPlugin.prefix + "${newJackal.player.displayName} ${ChatColor.GREEN}ist der neue Jackal") - } - } - } - } - } - - val packetListener = object : PacketAdapter(plugin, PacketType.Play.Server.PLAYER_INFO) { - override fun onPacketSending(event: PacketEvent) { - val packet = WrapperPlayServerPlayerInfo(event.packet) - - if ( - packet.action == EnumWrappers.PlayerInfoAction.UPDATE_GAME_MODE || - packet.action == EnumWrappers.PlayerInfoAction.ADD_PLAYER - ) { - packet.data = packet.data.map { info -> - val tttPlayer = PlayerManager.tttPlayers.find { it.player.uniqueId == info.profile.uuid } - - if (tttPlayer == null) info - else PlayerInfoData( - info.profile, - info.latency, - if (event.player.uniqueId == info.profile.uuid) { - if (event.player.gameMode == GameMode.SPECTATOR) EnumWrappers.NativeGameMode.SPECTATOR - else EnumWrappers.NativeGameMode.SURVIVAL - } else EnumWrappers.NativeGameMode.SURVIVAL, - info.displayName - ) - }.toMutableList() - } - } + protected fun handle(event: T, handler: (tttPlayer: TTTPlayer) -> Unit) { + handler(TTTPlayer.of(event.player) ?: return) } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/GameManager.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/GameManager.kt index 377bd1b..dc4ec67 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/GameManager.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/GameManager.kt @@ -31,13 +31,13 @@ object GameManager { val destroyedBlocks = mutableMapOf() private val listeners = ItemManager.listeners - .plus(GameListener) + .plus(GeneralGameListener) .plus(ShopListener) .plus(CorpseListener) .plus(TTTClassManager.listeners) private val packetListeners = ItemManager.packetListeners - .plus(GameListener.packetListener) + .plus(GeneralGameListener.packetListener) fun initialize() { adjustWorld() diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/GeneralGameListener.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/GeneralGameListener.kt new file mode 100644 index 0000000..4e59899 --- /dev/null +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/GeneralGameListener.kt @@ -0,0 +1,190 @@ +package de.moritzruth.spigot_ttt.game + +import com.comphenix.packetwrapper.WrapperPlayServerPlayerInfo +import com.comphenix.protocol.PacketType +import com.comphenix.protocol.events.PacketAdapter +import com.comphenix.protocol.events.PacketEvent +import com.comphenix.protocol.wrappers.EnumWrappers +import com.comphenix.protocol.wrappers.PlayerInfoData +import de.moritzruth.spigot_ttt.TTTPlugin +import de.moritzruth.spigot_ttt.game.players.* +import de.moritzruth.spigot_ttt.plugin +import de.moritzruth.spigot_ttt.utils.call +import de.moritzruth.spigot_ttt.utils.nextTick +import org.bukkit.ChatColor +import org.bukkit.GameMode +import org.bukkit.Material +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.block.Action +import org.bukkit.event.entity.EntityDamageByEntityEvent +import org.bukkit.event.entity.EntityDamageEvent +import org.bukkit.event.entity.PlayerDeathEvent +import org.bukkit.event.player.* +import java.util.* + +object GeneralGameListener : Listener { + private val BLOCKED_COMMANDS = setOf("me", "tell", "msg") + + @EventHandler + fun onPlayerJoin(event: PlayerJoinEvent) = PlayerManager.onPlayerJoin(event.player) + + @EventHandler + fun onPlayerQuit(event: PlayerQuitEvent) = PlayerManager.onPlayerQuit(event.player) + + @EventHandler + fun onPlayerCommandPreprocess(event: PlayerCommandPreprocessEvent) { + if (event.message.startsWith("/rl") && GameManager.phase != null) { // /reload is not blocked + event.player.sendMessage(TTTPlugin.prefix + "${ChatColor.RED}The server may not be reloaded while the game is running") + event.player.sendMessage(TTTPlugin.prefix + "${ChatColor.RED}You can force reload by using ${ChatColor.WHITE}/reload") + event.isCancelled = true + return + } + + if (BLOCKED_COMMANDS.find { event.message.startsWith("/$it") } != null) { + if (GameManager.phase != null) { + event.player.sendMessage(TTTPlugin.prefix + "${ChatColor.RED}Dieser Befehl ist blockiert.") + event.isCancelled = true + } + } + } + + @EventHandler + fun onEntityDamageByEntity(event: EntityDamageByEntityEvent) { + val player = event.damager + if (player is Player) { + if (player.inventory.itemInMainHand.type == Material.AIR) { + event.damage = 0.2 + } + } + } + + private val ZERO_NO_DAMAGE_TICKS_CAUSES = EnumSet.of( + EntityDamageEvent.DamageCause.ENTITY_ATTACK, + EntityDamageEvent.DamageCause.CUSTOM, + EntityDamageEvent.DamageCause.ENTITY_EXPLOSION, + EntityDamageEvent.DamageCause.BLOCK_EXPLOSION, + EntityDamageEvent.DamageCause.FALL, + EntityDamageEvent.DamageCause.ENTITY_SWEEP_ATTACK, + EntityDamageEvent.DamageCause.FALLING_BLOCK, + EntityDamageEvent.DamageCause.SUICIDE + )!! + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + fun onEntityDamageLow(event: EntityDamageEvent) { + if (ZERO_NO_DAMAGE_TICKS_CAUSES.contains(event.cause)) { + val player = event.entity + if (player is Player) { + nextTick { player.noDamageTicks = 0 } + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + fun onEntityDamageHighest(event: EntityDamageEvent) { + if (event.entity !is Player) return + val tttPlayer = TTTPlayer.of(event.entity as Player) ?: return + if (event.cause == EntityDamageEvent.DamageCause.CUSTOM) return + + val reason = when (event.cause) { + EntityDamageEvent.DamageCause.FALL -> DeathReason.FALL + EntityDamageEvent.DamageCause.BLOCK_EXPLOSION, + EntityDamageEvent.DamageCause.ENTITY_EXPLOSION -> DeathReason.EXPLOSION + EntityDamageEvent.DamageCause.DROWNING -> DeathReason.DROWNED + EntityDamageEvent.DamageCause.FIRE, + EntityDamageEvent.DamageCause.FIRE_TICK, + EntityDamageEvent.DamageCause.LAVA, + EntityDamageEvent.DamageCause.HOT_FLOOR -> DeathReason.FIRE + EntityDamageEvent.DamageCause.POISON, + EntityDamageEvent.DamageCause.WITHER -> DeathReason.POISON + else -> DeathReason.SUICIDE + } + + val e = TTTPlayerDamageEvent(tttPlayer, event.finalDamage, reason).call() + + if (tttPlayer.player.health - e.damage <= 0) { + tttPlayer.onDeath(reason, null) + event.damage = 0.0 + } else { + event.damage = e.damage + } + } + + @EventHandler + fun onPlayerDeath(event: PlayerDeathEvent) { + event.deathMessage = null + } + + @EventHandler + fun onPlayerInteract(event: PlayerInteractEvent) { + if (event.player.inventory.itemInMainHand.type == Material.AIR && event.action == Action.LEFT_CLICK_BLOCK) { + GameManager.destroyBlock(event.clickedBlock!!) + } + } + + @EventHandler(priority = EventPriority.LOWEST) + fun onPlayerSwapHandItemsLowest(event: PlayerSwapHandItemsEvent) { + event.isCancelled = true + } + + @EventHandler + fun onAsyncPlayerChat(event: AsyncPlayerChatEvent) { + val senderTTTPlayer = TTTPlayer.of(event.player) ?: return + + if (!senderTTTPlayer.alive) { + PlayerManager.tttPlayers.filter { !it.alive }.forEach { + it.player.sendMessage("${ChatColor.GRAY}[${ChatColor.RED}TOT${ChatColor.GRAY}] <${event.player.displayName}> ${event.message}") + } + + event.isCancelled = true + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + fun onTTTPlayerTrueDeath(event: TTTPlayerTrueDeathEvent) { + if (event.winnerRoleGroup != RoleGroup.JACKAL && event.tttPlayer.role == Role.JACKAL) { + val sidekicks = PlayerManager.tttPlayers.filter { it.role == Role.SIDEKICK } + + if (sidekicks.isNotEmpty()) { + val newJackal = sidekicks.random() + newJackal.changeRole(Role.JACKAL) + + event.tttPlayer.changeRole(Role.SIDEKICK) // The old Jackal + + sidekicks.forEach { sidekick -> + if (sidekick != newJackal) { + sidekick.player.sendMessage(TTTPlugin.prefix + "${newJackal.player.displayName} ${ChatColor.GREEN}ist der neue Jackal") + } + } + } + } + } + + val packetListener = object : PacketAdapter(plugin, PacketType.Play.Server.PLAYER_INFO) { + override fun onPacketSending(event: PacketEvent) { + val packet = WrapperPlayServerPlayerInfo(event.packet) + + if ( + packet.action == EnumWrappers.PlayerInfoAction.UPDATE_GAME_MODE || + packet.action == EnumWrappers.PlayerInfoAction.ADD_PLAYER + ) { + packet.data = packet.data.map { info -> + val tttPlayer = PlayerManager.tttPlayers.find { it.player.uniqueId == info.profile.uuid } + + if (tttPlayer == null) info + else PlayerInfoData( + info.profile, + info.latency, + if (event.player.uniqueId == info.profile.uuid) { + if (event.player.gameMode == GameMode.SPECTATOR) EnumWrappers.NativeGameMode.SPECTATOR + else EnumWrappers.NativeGameMode.SURVIVAL + } else EnumWrappers.NativeGameMode.SURVIVAL, + info.displayName + ) + }.toMutableList() + } + } + } +} diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/TTTClass.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/TTTClass.kt index 6cbf5a6..6ffbe0b 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/TTTClass.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/TTTClass.kt @@ -1,23 +1,19 @@ package de.moritzruth.spigot_ttt.game.classes -import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.game.players.TTTPlayer import org.bukkit.ChatColor -import org.bukkit.event.Listener -abstract class TTTClass( - val displayName: String, - val chatColor: ChatColor, - val defaultItems: Set = emptySet() -) { - val coloredDisplayName = "$chatColor$displayName" +abstract class TTTClass { + lateinit var tttPlayer: TTTPlayer - open val listener: Listener? = null + open fun init() {} + open fun reset() {} - open fun onInit(tttPlayer: TTTPlayer) {} - - object None: TTTClass( + object None: TTTClassCompanion( "Keine", - ChatColor.GRAY - ) + ChatColor.GRAY, + Instance::class + ) { + class Instance: TTTClass() + } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/TTTClassCompanion.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/TTTClassCompanion.kt new file mode 100644 index 0000000..9a83b1a --- /dev/null +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/TTTClassCompanion.kt @@ -0,0 +1,24 @@ +package de.moritzruth.spigot_ttt.game.classes + +import de.moritzruth.spigot_ttt.game.items.TTTItem +import de.moritzruth.spigot_ttt.game.players.TTTPlayer +import org.bukkit.ChatColor +import org.bukkit.event.Listener +import kotlin.reflect.KClass +import kotlin.reflect.full.primaryConstructor + +abstract class TTTClassCompanion( + val displayName: String, + val chatColor: ChatColor, + private val instanceType: KClass, + val defaultItems: Set> = emptySet() +) { + val coloredDisplayName = "$chatColor$displayName" + fun createInstance(tttPlayer: TTTPlayer): TTTClass { + val instance = instanceType.primaryConstructor!!.call() + instance.tttPlayer = tttPlayer + return instance + } + + open val listener: Listener? = null +} diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/TTTClassManager.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/TTTClassManager.kt index d23f6fa..002428f 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/TTTClassManager.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/TTTClassManager.kt @@ -1,21 +1,17 @@ package de.moritzruth.spigot_ttt.game.classes -import de.moritzruth.spigot_ttt.game.classes.impl.Gambler -import de.moritzruth.spigot_ttt.game.classes.impl.Ninja -import de.moritzruth.spigot_ttt.game.classes.impl.Stuntman -import de.moritzruth.spigot_ttt.game.classes.impl.Warrior +import de.moritzruth.spigot_ttt.game.classes.impl.* import java.util.* object TTTClassManager { private val TTT_CLASSES = setOf( - Warrior, Gambler, Stuntman, Ninja - // Oracle is disabled because of the bug with the radar + Warrior, Gambler, Stuntman, Ninja, Oracle ) val listeners = TTT_CLASSES.mapNotNull { it.listener } - fun createClassesIterator(count: Int): Iterator { - val set: MutableSet = TTT_CLASSES.toMutableSet() + fun createClassesIterator(count: Int): Iterator { + val set: MutableSet = TTT_CLASSES.toMutableSet() val playersWithoutClass = count - TTT_CLASSES.size if (playersWithoutClass > 0) set.addAll(Collections.nCopies(playersWithoutClass, TTTClass.None)) diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Gambler.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Gambler.kt index fa5e843..cde1b29 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Gambler.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Gambler.kt @@ -1,11 +1,15 @@ package de.moritzruth.spigot_ttt.game.classes.impl import de.moritzruth.spigot_ttt.game.classes.TTTClass +import de.moritzruth.spigot_ttt.game.classes.TTTClassCompanion import de.moritzruth.spigot_ttt.game.items.impl.SecondChance import org.bukkit.ChatColor -object Gambler: TTTClass( - "Gambler", - ChatColor.YELLOW, - setOf(SecondChance) -) +class Gambler: TTTClass() { + companion object: TTTClassCompanion( + "Gambler", + ChatColor.YELLOW, + Gambler::class, + setOf(SecondChance) + ) +} diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Ninja.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Ninja.kt index e561fb5..6200185 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Ninja.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Ninja.kt @@ -1,8 +1,11 @@ package de.moritzruth.spigot_ttt.game.classes.impl -import de.moritzruth.spigot_ttt.game.GameEndEvent import de.moritzruth.spigot_ttt.game.classes.TTTClass -import de.moritzruth.spigot_ttt.game.players.* +import de.moritzruth.spigot_ttt.game.classes.TTTClassCompanion +import de.moritzruth.spigot_ttt.game.players.DeathReason +import de.moritzruth.spigot_ttt.game.players.TTTPlayer +import de.moritzruth.spigot_ttt.game.players.TTTPlayerDamageEvent +import de.moritzruth.spigot_ttt.game.players.TTTPlayerReviveEvent import de.moritzruth.spigot_ttt.plugin import de.moritzruth.spigot_ttt.utils.nextTick import org.bukkit.ChatColor @@ -14,13 +17,11 @@ import org.bukkit.potion.PotionEffectType import org.bukkit.scheduler.BukkitTask import org.bukkit.util.Vector -object Ninja: TTTClass( - "Ninja", - ChatColor.LIGHT_PURPLE -) { - private val isc = InversedStateContainer(State::class) +class Ninja: TTTClass() { + var jumpsRemaining = 1 + var checkOnGroundTask: BukkitTask? = null - override fun onInit(tttPlayer: TTTPlayer) { + override fun init() { tttPlayer.player.allowFlight = true tttPlayer.player.addPotionEffect(PotionEffect( PotionEffectType.JUMP, @@ -31,70 +32,58 @@ object Ninja: TTTClass( )) } - override val listener = object : Listener { - @EventHandler(ignoreCancelled = true) - fun onPlayerToggleFlight(event: PlayerToggleFlightEvent) { - val tttPlayer = TTTPlayer.of(event.player) ?: return - - if (event.isFlying && tttPlayer.tttClass == Ninja) { - val state = isc.getOrCreate(tttPlayer) - - val current = tttPlayer.player.velocity - tttPlayer.player.velocity = Vector(current.x * 3, 0.8, current.z * 3) - state.jumpsRemaining -= 1 - - if (state.jumpsRemaining == 0) { - tttPlayer.player.allowFlight = false - - state.checkOnGroundTask = plugin.server.scheduler.runTaskTimer(plugin, fun() { - if (tttPlayer.player.isOnGround) { - state.jumpsRemaining = 1 - tttPlayer.player.allowFlight = true - state.reset() - } - }, 1, 1) - } - - event.isCancelled = true - } - } - - @EventHandler(ignoreCancelled = true) - fun onTTTPlayerDamage(event: TTTPlayerDamageEvent) { - if (event.tttPlayer.tttClass == Ninja) { - if (event.deathReason == DeathReason.FALL) event.damage = 0.0 - } - } - - @EventHandler - fun onTTTPlayerDeath(event: TTTPlayerDeathEvent) { - isc.get(event.tttPlayer)?.reset() - isc.remove(event.tttPlayer) - } - - @EventHandler - fun onTTTPlayerReviveEvent(event: TTTPlayerReviveEvent) { - if (event.tttPlayer.tttClass == Ninja) { - // This must be delayed for 2 ticks, idk why - nextTick { nextTick { event.tttPlayer.player.allowFlight = true } } - } - } - - @EventHandler - fun onGameEnd(event: GameEndEvent) { - isc.forEveryState { state, _ -> - state.reset() - } - } + override fun reset() { + checkOnGroundTask?.cancel() + checkOnGroundTask = null } - class State: IState { - var jumpsRemaining = 1 - var checkOnGroundTask: BukkitTask? = null + companion object: TTTClassCompanion( + "Ninja", + ChatColor.LIGHT_PURPLE, + Ninja::class + ) { + override val listener = object : Listener { + @EventHandler(ignoreCancelled = true) + fun onPlayerToggleFlight(event: PlayerToggleFlightEvent) { + val tttPlayer = TTTPlayer.of(event.player) ?: return + val instance = tttPlayer.tttClassInstance + if (instance !is Ninja) return - fun reset() { - checkOnGroundTask?.cancel() - checkOnGroundTask = null + if (event.isFlying) { + val vel = tttPlayer.player.velocity + tttPlayer.player.velocity = Vector(vel.x * 3, 0.8, vel.z * 3) + instance.jumpsRemaining -= 1 + + if (instance.jumpsRemaining == 0) { + tttPlayer.player.allowFlight = false + + instance.checkOnGroundTask = plugin.server.scheduler.runTaskTimer(plugin, fun() { + if (tttPlayer.player.isOnGround) { + instance.jumpsRemaining = 1 + tttPlayer.player.allowFlight = true + instance.reset() + } + }, 1, 1) + } + + event.isCancelled = true + } + } + + @EventHandler(ignoreCancelled = true) + fun onTTTPlayerDamage(event: TTTPlayerDamageEvent) { + if (event.tttPlayer.tttClass == Ninja) { + if (event.deathReason == DeathReason.FALL) event.damage = 0.0 + } + } + + @EventHandler + fun onTTTPlayerReviveEvent(event: TTTPlayerReviveEvent) { + if (event.tttPlayer.tttClass == Ninja) { + // This must be delayed for 2 ticks, idk why + nextTick { nextTick { event.tttPlayer.player.allowFlight = true } } + } + } } } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Oracle.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Oracle.kt index 739a5c1..49c3d45 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Oracle.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Oracle.kt @@ -1,11 +1,15 @@ package de.moritzruth.spigot_ttt.game.classes.impl import de.moritzruth.spigot_ttt.game.classes.TTTClass +import de.moritzruth.spigot_ttt.game.classes.TTTClassCompanion import de.moritzruth.spigot_ttt.game.items.impl.Radar import org.bukkit.ChatColor -object Oracle: TTTClass( - "Oracle", - ChatColor.DARK_AQUA, - setOf(Radar) -) +class Oracle: TTTClass() { + companion object: TTTClassCompanion( + "Oracle", + ChatColor.DARK_AQUA, + Oracle::class, + setOf(Radar) + ) +} diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Stuntman.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Stuntman.kt index d23dd84..8038ed2 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Stuntman.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Stuntman.kt @@ -1,23 +1,27 @@ package de.moritzruth.spigot_ttt.game.classes.impl import de.moritzruth.spigot_ttt.game.classes.TTTClass +import de.moritzruth.spigot_ttt.game.classes.TTTClassCompanion import de.moritzruth.spigot_ttt.game.players.DeathReason import de.moritzruth.spigot_ttt.game.players.TTTPlayerDamageEvent import org.bukkit.ChatColor import org.bukkit.event.EventHandler import org.bukkit.event.Listener -object Stuntman: TTTClass( - "Stuntman", - ChatColor.DARK_RED -) { - val IMMUNE_DEATH_REASONS = setOf(DeathReason.FALL, DeathReason.EXPLOSION) +class Stuntman: TTTClass() { + companion object: TTTClassCompanion( + "Stuntman", + ChatColor.DARK_RED, + Stuntman::class + ) { + val IMMUNE_DEATH_REASONS = setOf(DeathReason.FALL, DeathReason.EXPLOSION) - override val listener = object : Listener { - @EventHandler(ignoreCancelled = true) - fun onEntityDamage(event: TTTPlayerDamageEvent) { - if (event.tttPlayer.tttClass == Stuntman) { - if (IMMUNE_DEATH_REASONS.contains(event.deathReason)) event.damage = 0.0 + override val listener = object : Listener { + @EventHandler(ignoreCancelled = true) + fun onEntityDamage(event: TTTPlayerDamageEvent) { + if (event.tttPlayer.tttClass == Stuntman) { + if (IMMUNE_DEATH_REASONS.contains(event.deathReason)) event.damage = 0.0 + } } } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Warrior.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Warrior.kt index 497ad32..9440536 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Warrior.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/classes/impl/Warrior.kt @@ -1,25 +1,34 @@ package de.moritzruth.spigot_ttt.game.classes.impl import de.moritzruth.spigot_ttt.game.classes.TTTClass -import de.moritzruth.spigot_ttt.game.players.TTTPlayer +import de.moritzruth.spigot_ttt.game.classes.TTTClassCompanion import de.moritzruth.spigot_ttt.game.players.TTTPlayerDamageEvent import org.bukkit.ChatColor import org.bukkit.event.EventHandler import org.bukkit.event.Listener -object Warrior: TTTClass( - "Warrior", - ChatColor.BLUE -) { - override fun onInit(tttPlayer: TTTPlayer) { - tttPlayer.walkSpeed -= 0.05F +class Warrior: TTTClass() { + override fun init() { + tttPlayer.walkSpeed -= WALK_SPEED_DECREASE } - override val listener = object : Listener { - @EventHandler(ignoreCancelled = true) - fun onEntityDamage(event: TTTPlayerDamageEvent) { - if (event.tttPlayer.tttClass == Warrior) { - event.damage *= 0.9 + override fun reset() { + tttPlayer.walkSpeed += WALK_SPEED_DECREASE + } + + companion object: TTTClassCompanion( + "Warrior", + ChatColor.BLUE, + Warrior::class + ) { + const val WALK_SPEED_DECREASE = 0.05F + + override val listener = object : Listener { + @EventHandler(ignoreCancelled = true) + fun onEntityDamage(event: TTTPlayerDamageEvent) { + if (event.tttPlayer.tttClass == Warrior) { + event.damage *= 0.8 + } } } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/corpses/CorpseListener.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/corpses/CorpseListener.kt index 1ff2b74..3444a13 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/corpses/CorpseListener.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/corpses/CorpseListener.kt @@ -26,7 +26,7 @@ object CorpseListener: Listener { } } - @EventHandler + @EventHandler(priority = EventPriority.LOW) fun onPlayerInteractEntity(event: PlayerInteractEntityEvent) { val tttPlayer = TTTPlayer.of(event.player) ?: return val tttCorpse = CorpseManager.getTTTCorpse(event.rightClicked) ?: 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 546c1bb..cdc50bf 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 @@ -18,7 +18,6 @@ import org.bukkit.entity.Zombie import org.bukkit.event.inventory.InventoryType import org.bukkit.inventory.ItemStack import org.bukkit.scheduler.BukkitTask -import org.bukkit.util.Vector import java.time.Instant class TTTCorpse private constructor( @@ -26,10 +25,9 @@ class TTTCorpse private constructor( location: Location, private val role: Role, private val reason: DeathReason, - private var credits: Int, - velocity: Vector = Vector() + private var credits: Int ) { - var status = Status.UNIDENTIFIED; private set + private var status = Status.UNIDENTIFIED; private set val entity: Zombie val inventory = tttPlayer.player.server.createInventory(null, InventoryType.HOPPER, "${role.chatColor}${tttPlayer.player.displayName}") @@ -83,7 +81,7 @@ class TTTCorpse private constructor( private fun setReasonItem() { if (status == Status.INSPECTED) { - val reasonItemStack = if (reason is DeathReason.Item) reason.item.itemStack.clone() else ItemStack(Resourcepack.Items.deathReason) + val reasonItemStack = if (reason is DeathReason.Item) reason.item.templateItemStack.clone() else ItemStack(Resourcepack.Items.deathReason) inventory.setItem(REASON_SLOT, reasonItemStack.applyMeta { setDisplayName("${ChatColor.RESET}" + reason.displayText) lore = listOf("${ChatColor.GRAY}Grund des Todes") @@ -163,8 +161,7 @@ class TTTCorpse private constructor( tttPlayer.player.location, tttPlayer.role, reason, - tttPlayer.credits, - tttPlayer.player.velocity + tttPlayer.credits ).also { CorpseManager.add(it) } fun spawnFake(role: Role, tttPlayer: TTTPlayer, location: Location) { diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/ItemEvents.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/ItemEvents.kt new file mode 100644 index 0000000..c5e8e75 --- /dev/null +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/ItemEvents.kt @@ -0,0 +1,5 @@ +package de.moritzruth.spigot_ttt.game.items + +class ClickEvent { + var isCancelled = true +} diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/ItemManager.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/ItemManager.kt index e5019f3..bbbf639 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/ItemManager.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/ItemManager.kt @@ -1,117 +1,146 @@ package de.moritzruth.spigot_ttt.game.items +import de.moritzruth.spigot_ttt.game.GameListener import de.moritzruth.spigot_ttt.game.GameManager import de.moritzruth.spigot_ttt.game.items.impl.* import de.moritzruth.spigot_ttt.game.items.impl.weapons.BaseballBat import de.moritzruth.spigot_ttt.game.items.impl.weapons.Knife import de.moritzruth.spigot_ttt.game.items.impl.weapons.guns.* -import de.moritzruth.spigot_ttt.game.players.IState import de.moritzruth.spigot_ttt.game.players.TTTPlayer -import de.moritzruth.spigot_ttt.plugin -import de.moritzruth.spigot_ttt.utils.nextTick +import de.moritzruth.spigot_ttt.game.players.TTTPlayerDeathEvent +import de.moritzruth.spigot_ttt.utils.isLeftClick +import de.moritzruth.spigot_ttt.utils.isRightClick import de.moritzruth.spigot_ttt.utils.sendActionBarMessage -import org.bukkit.ChatColor +import org.bukkit.Location import org.bukkit.Material import org.bukkit.entity.Item import org.bukkit.entity.Player import org.bukkit.event.EventHandler -import org.bukkit.event.Listener +import org.bukkit.event.EventPriority +import org.bukkit.event.entity.EntityDamageByEntityEvent import org.bukkit.event.entity.EntityPickupItemEvent import org.bukkit.event.entity.ItemDespawnEvent import org.bukkit.event.player.PlayerDropItemEvent +import org.bukkit.event.player.PlayerInteractEvent import org.bukkit.event.player.PlayerItemHeldEvent +import org.bukkit.event.player.PlayerSwapHandItemsEvent import org.bukkit.inventory.ItemStack object ItemManager { - val ITEMS: Set = setOf( - Pistol, - Knife, Glock, Deagle, Shotgun, SidekickDeagle, - BaseballBat, - CloakingDevice, Rifle, - EnderPearl, Radar, HealingPotion, Fireball, - Teleporter, MartyrdomGrenade, FakeCorpse, Defibrillator, SecondChance + val ITEMS: Set> = setOf( + Deagle, Glock, Pistol, Rifle, SidekickDeagle, BaseballBat, Knife, CloakingDevice, Defibrillator, + EnderPearl, FakeCorpse, Fireball, HealingPotion, MartyrdomGrenade, Radar, SecondChance, Teleporter, + Shotgun, Radar, SecondChance ) - val droppedItemStates = mutableMapOf() - val listeners get () = ITEMS.mapNotNull { it.listener }.plus(listener) val packetListeners get () = ITEMS.mapNotNull { it.packetListener } - private fun getItemByMaterial(material: Material) = ITEMS.find { tttItem -> material === tttItem.itemStack.type } - fun getItemByItemStack(itemStack: ItemStack) = getItemByMaterial(itemStack.type) + private fun getTTTItemByMaterial(material: Material) = ITEMS.find { tttItem -> material == tttItem.material } + fun getTTTItemByItemStack(itemStack: ItemStack) = getTTTItemByMaterial(itemStack.type) + fun getInstanceByItemStack(itemStack: ItemStack) = getTTTItemByItemStack(itemStack)?.getInstance(itemStack) - fun reset() { - droppedItemStates.clear() - GameManager.world.getEntitiesByClass(Item::class.java).forEach { - it.remove() - } + fun dropItem(location: Location, tttItem: TTTItem<*>) { + val instance = tttItem.createInstance() + GameManager.world.dropItem(location, instance.createItemStack()) } - val listener = object : Listener { - @EventHandler - fun onPlayerItemHeld(event: PlayerItemHeldEvent) { - val tttPlayer = TTTPlayer.of(event.player) ?: return - val itemStack = event.player.inventory.getItem(event.newSlot) + fun reset() { + GameManager.world.getEntitiesByClass(Item::class.java).forEach(Item::remove) + ITEMS.forEach(TTTItem<*>::reset) + } - tttPlayer.itemInHand = - if (itemStack == null || itemStack.type === Material.AIR) null - else getItemByItemStack(itemStack) + val listener = object : GameListener() { + @EventHandler + fun onPlayerInteract(event: PlayerInteractEvent) = handle(event) { + val instance = event.item?.let { getInstanceByItemStack(it) } ?: return@handle + + val clickEvent = ClickEvent() + if (event.action.isLeftClick) instance.onLeftClick(clickEvent) + else if (event.action.isRightClick) instance.onRightClick(clickEvent) + event.isCancelled = clickEvent.isCancelled } - @EventHandler - fun onPlayerDropItem(event: PlayerDropItemEvent) { - val tttPlayer = TTTPlayer.of(event.player) ?: return - val tttItem = getItemByItemStack(event.itemDrop.itemStack) ?: return - - if (tttItem.type != TTTItem.Type.SPECIAL) { - if (tttItem is DropHandler) { - if (!tttItem.onDrop(tttPlayer, event.itemDrop)) { - event.isCancelled = true - return - } + @EventHandler(ignoreCancelled = true) + fun onEntityDamageByEntity(event: EntityDamageByEntityEvent) { + val damager = event.damager + if (damager is Player) { + TTTPlayer.of(damager) ?: return + val item = damager.inventory.itemInMainHand + if (item.type != Material.AIR) { + val tttItem = getTTTItemByItemStack(item) ?: return + event.isCancelled = tttItem.disableDamage } - - plugin.server.scheduler.runTask(plugin, fun() { - tttPlayer.updateItemInHand() - }) - } else { - event.player.sendActionBarMessage("${ChatColor.RED}Du kannst dieses Item nicht droppen") - event.isCancelled = true } } @EventHandler - fun onItemDespawn(event: ItemDespawnEvent) { - if (getItemByItemStack(event.entity.itemStack) != null) { - event.entity.ticksLived = 1 + fun onPlayerSwapHandItems(event: PlayerSwapHandItemsEvent) = handle(event) { _ -> + val instance = event.offHandItem?.let { getInstanceByItemStack(it) } ?: return@handle + instance.onHandSwap() + event.isCancelled = true + } + + @EventHandler + fun onPlayerItemHeld(event: PlayerItemHeldEvent) = handle(event) { tttPlayer -> + tttPlayer.player.inventory.getItem(event.previousSlot) + ?.also { itemStack -> getInstanceByItemStack(itemStack)?.isSelected = false } + + tttPlayer.player.inventory.getItem(event.newSlot) + ?.also { itemStack -> getInstanceByItemStack(itemStack)?.isSelected = true } + } + + @EventHandler + fun onPlayerDropItem(event: PlayerDropItemEvent) = handle(event) { tttPlayer -> + val instance = getInstanceByItemStack(event.itemDrop.itemStack) ?: return@handle + + val notDroppableReason = instance.notDroppableReason + if (notDroppableReason == null) { + instance.carrier = null + } else { + tttPlayer.player.sendActionBarMessage(notDroppableReason) event.isCancelled = true } } @EventHandler fun onEntityPickupItem(event: EntityPickupItemEvent) { - if (event.entity !is Player) { - return - } + val player = event.entity + if (player !is Player) return - val player = event.entity as Player val tttPlayer = TTTPlayer.of(player) ?: return + val instance = getInstanceByItemStack(event.item.itemStack) - val tttItem = getItemByItemStack(event.item.itemStack) - - if (tttItem != null) { - if (runCatching { tttPlayer.checkAddItemPreconditions(tttItem) }.isSuccess) { - nextTick { tttPlayer.updateItemInHand() } - - if (tttItem is DropHandler) { - tttItem.onPickup(tttPlayer, event.item) - } - + if (instance != null) { + if (runCatching { tttPlayer.checkAddItemPreconditions(instance.tttItem) }.isSuccess) { + instance.carrier = tttPlayer return } } event.isCancelled = true } + + @EventHandler + fun onItemDespawn(event: ItemDespawnEvent) { + if (getTTTItemByItemStack(event.entity.itemStack) != null) { + event.entity.ticksLived = 1 + event.isCancelled = true + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + fun onTTTPlayerDeath(event: TTTPlayerDeathEvent) { + val itemStackInHand = event.tttPlayer.player.inventory.itemInMainHand + if (itemStackInHand.type != Material.AIR) { + val instance = getInstanceByItemStack(itemStackInHand) + if (instance != null && instance.notDroppableReason == null) + GameManager.world.dropItem(event.location, instance.createItemStack()) + + event.tttPlayer.getOwningTTTItemInstances().forEach { + event.tttPlayer.removeItem(it.tttItem, false) + } + } + } } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/ItemSpawner.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/ItemSpawner.kt index acd81c5..0e763f8 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/ItemSpawner.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/ItemSpawner.kt @@ -2,14 +2,15 @@ package de.moritzruth.spigot_ttt.game.items import de.moritzruth.spigot_ttt.game.GameManager import de.moritzruth.spigot_ttt.utils.ConfigurationFile +import de.moritzruth.spigot_ttt.utils.Probability import de.moritzruth.spigot_ttt.utils.roundToCenter import org.bukkit.Location +import java.util.* object ItemSpawner { private const val CONFIG_PATH = "spawn-locations" private val spawnLocationsConfig = ConfigurationFile("spawnLocations") - private val spawningItems = ItemManager.ITEMS.filter { it is Spawning } private fun getSpawnLocations(): Set { return spawnLocationsConfig.getStringList(CONFIG_PATH).map { @@ -25,14 +26,19 @@ object ItemSpawner { } fun spawnWeapons() { - var itemIterator = spawningItems.shuffled().iterator() + val spawningItems = mutableListOf>() + loop@ for (tttItem in ItemManager.ITEMS) { + val count = Probability.values().indexOf(tttItem.spawnProbability) + 1 + spawningItems.addAll(Collections.nCopies(count, tttItem)) + } + var itemsIterator = spawningItems.shuffled().iterator() for (location in getSpawnLocations()) { - if (!itemIterator.hasNext()) { - itemIterator = spawningItems.shuffled().iterator() + if (!itemsIterator.hasNext()) { + itemsIterator = spawningItems.shuffled().iterator() } - GameManager.world.dropItem(location, itemIterator.next().itemStack.clone()) + ItemManager.dropItem(location, itemsIterator.next()) } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/TTTItem.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/TTTItem.kt index 470860b..9250685 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/TTTItem.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/TTTItem.kt @@ -3,46 +3,142 @@ package de.moritzruth.spigot_ttt.game.items import com.comphenix.protocol.events.PacketListener import de.moritzruth.spigot_ttt.game.players.Role import de.moritzruth.spigot_ttt.game.players.TTTPlayer +import de.moritzruth.spigot_ttt.plugin +import de.moritzruth.spigot_ttt.utils.Probability +import de.moritzruth.spigot_ttt.utils.applyMeta +import de.moritzruth.spigot_ttt.utils.nextTick import org.bukkit.ChatColor -import org.bukkit.entity.Item +import org.bukkit.NamespacedKey import org.bukkit.event.Listener import org.bukkit.inventory.ItemStack +import org.bukkit.persistence.PersistentDataType import java.util.* +import kotlin.reflect.KClass +import kotlin.reflect.full.primaryConstructor -interface Selectable { - fun onSelect(tttPlayer: TTTPlayer) - fun onDeselect(tttPlayer: TTTPlayer) -} +open class TTTItem( + val type: Type, + val templateItemStack: ItemStack, + val instanceType: KClass, + val shopInfo: ShopInfo? = null, + val spawnProbability: Probability? = null, + val disableDamage: Boolean = true +) { + open val listener: Listener? = null + open val packetListener: PacketListener? = null -interface DropHandler { - fun onDrop(tttPlayer: TTTPlayer, itemEntity: Item): Boolean - fun onPickup(tttPlayer: TTTPlayer, itemEntity: Item) -} + val material = templateItemStack.type -interface Buyable { - val buyableBy: EnumSet - val price: Int - val buyLimit: Int? -} + val instancesByUUID = mutableMapOf() + fun getInstance(itemStack: ItemStack) = + itemStack.itemMeta?.persistentDataContainer?.get(ID_KEY, PersistentDataType.STRING) + ?.let { instancesByUUID[UUID.fromString(it)] } -val PASSIVE = "${ChatColor.RESET}${ChatColor.RED}(Passiv)" + fun getInstance(tttPlayer: TTTPlayer) = instancesByUUID.values.find { it.carrier === tttPlayer } -// Marker -interface Spawning + fun reset() { + instancesByUUID.values.forEach { + it.carrier?.removeItem(it.tttItem, removeInstance = false) + it.reset() + } + instancesByUUID.clear() + } -interface TTTItem { - val listener: Listener? get() = null - val packetListener: PacketListener? get() = null - val itemStack: ItemStack - val type: Type - - fun onOwn(tttPlayer: TTTPlayer) {} - fun onRemove(tttPlayer: TTTPlayer) {} + fun createInstance(): InstanceT = instanceType.primaryConstructor!!.call() + .also { instancesByUUID[it.uuid] = it } enum class Type(val maxItemsOfTypeInInventory: Int?) { MELEE(1), - PISTOL_LIKE(1), + PISTOL_LIKE(2), HEAVY_WEAPON(1), SPECIAL(null); } + + data class ShopInfo( + val buyableBy: EnumSet, + val price: Int, + val buyLimit: Int = 0 + ) { + init { + if (buyLimit < 0) throw IllegalArgumentException("buyLimit must be positive") + } + } + + companion object { + val PASSIVE_SUFFIX = " ${ChatColor.RESET}${ChatColor.RED}(Passiv)" + val ID_KEY = NamespacedKey(plugin, "instance") + } + + abstract class Instance(val tttItem: TTTItem<*>, droppable: Boolean = true) { + val uuid = UUID.randomUUID()!! + + fun createItemStack() = tttItem.templateItemStack.clone().applyMeta { + persistentDataContainer.set(ID_KEY, PersistentDataType.STRING, uuid.toString()) + } + + private var isFirstCarrier = true + open var carrier: TTTPlayer? = null + set(newCarrier) { + if (newCarrier == field) return // Do nothing it does not get changed + + if (newCarrier == null) { + val oldCarrier = field!! + isSelected = false + field = newCarrier + onCarrierRemoved(oldCarrier) + } else { + field = newCarrier + onCarrierSet(newCarrier, isFirstCarrier) + isFirstCarrier = false + nextTick { + if (newCarrier.player.inventory.itemInMainHand.type == tttItem.material) isSelected = true + } + } + } + + /** + * This is called after onDeselect + */ + protected open fun onCarrierRemoved(oldCarrier: TTTPlayer) {} + + /** + * This is called before isSelected is set to true in the next tick (only if the item is in the main hand) + */ + protected open fun onCarrierSet(carrier: TTTPlayer, isFirst: Boolean) {} + + protected fun requireCarrier() = + carrier ?: run { + throw IllegalStateException("The item must be carried to perform this action") + } + + /** + * The reason why the item can not be dropped or null if it can be dropped. + * Should be overridden with a dynamic getter. + */ + open val notDroppableReason: String? = + if (droppable) null + else "${ChatColor.RED}Du kannst dieses Item nicht droppen" + + open fun onRightClick(event: ClickEvent) { event.isCancelled = false } + open fun onLeftClick(event: ClickEvent) { event.isCancelled = false } + open fun onHandSwap() {} + + open fun reset() {} + + var isSelected = false + set(value) { + if (value == isSelected) return + field = value + + if (value) onSelect() + else onDeselect() + } + + protected open fun onSelect() {} + + /** + * If this is called because the carrier is set to null, it is called before the field is changed + */ + protected open fun onDeselect() {} + } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/TTTItemListener.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/TTTItemListener.kt index 74117bc..351c5b6 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/TTTItemListener.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/TTTItemListener.kt @@ -1,66 +1,16 @@ package de.moritzruth.spigot_ttt.game.items +import de.moritzruth.spigot_ttt.game.GameListener import de.moritzruth.spigot_ttt.game.players.TTTPlayer -import de.moritzruth.spigot_ttt.game.players.TTTPlayerDeathEvent -import de.moritzruth.spigot_ttt.utils.isLeftClick -import de.moritzruth.spigot_ttt.utils.isRightClick +import de.moritzruth.spigot_ttt.game.players.TTTPlayerReviveEvent import org.bukkit.entity.Player -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 -import org.bukkit.event.player.PlayerSwapHandItemsEvent -import org.bukkit.inventory.ItemStack - -open class TTTItemListener( - private val tttItem: TTTItem, - private val cancelDamage: Boolean, - private val removeOnDeath: Boolean = true -): Listener { - @EventHandler - open fun onEntityDamageByEntity(event: EntityDamageByEntityEvent) = handle(event) { _, _ -> - if (cancelDamage) event.isCancelled = true - } - - @EventHandler - open fun onTTTPlayerDeath(event: TTTPlayerDeathEvent) { - if (removeOnDeath) event.tttPlayer.removeItem(tttItem) - } - - @EventHandler - fun onPlayerInteract(event: PlayerInteractEvent) = handle(event) { tttPlayer -> - event.isCancelled = true - val data = ClickEventData(tttPlayer, event.item!!, event) - if (event.action.isRightClick) onRightClick(data) - else if (event.action.isLeftClick) onLeftClick(data) - } - - open fun onRightClick(data: ClickEventData) { - data.event.isCancelled = false - } - - open fun onLeftClick(data: ClickEventData) { - data.event.isCancelled = false - } - - protected fun handle(event: PlayerInteractEvent, handler: (tttPlayer: TTTPlayer) -> Unit) { - if (event.item?.type == tttItem.itemStack.type) { - val tttPlayer = TTTPlayer.of(event.player) ?: return - handler(tttPlayer) - } - } - - protected fun handle(event: PlayerSwapHandItemsEvent, handler: (tttPlayer: TTTPlayer) -> Unit) { - if (event.offHandItem?.type == tttItem.itemStack.type) { - val tttPlayer = TTTPlayer.of(event.player) ?: return - handler(tttPlayer) - } - } +open class TTTItemListener(private val tttItem: TTTItem): GameListener() { protected fun handle( event: EntityDamageByEntityEvent, handler: (damagerTTTPlayer: TTTPlayer, damagedTTTPlayer: TTTPlayer) -> Unit @@ -71,7 +21,7 @@ open class TTTItemListener( if ( damager is Player && damaged is Player && - damager.inventory.itemInMainHand.type == tttItem.itemStack.type + damager.inventory.itemInMainHand.type == tttItem.material ) { val damagerTTTPlayer = TTTPlayer.of(damager) ?: return val damagedTTTPlayer = TTTPlayer.of(damaged) ?: return @@ -79,33 +29,38 @@ open class TTTItemListener( } } - protected fun handle(event: InventoryClickEvent, handler: (tttPlayer: TTTPlayer) -> Unit) { - val whoClicked = event.whoClicked - if (whoClicked is Player) { - handler(TTTPlayer.of(whoClicked) ?: return) - } - } - - protected fun handle(event: InventoryCloseEvent, handler: (tttPlayer: TTTPlayer) -> Unit) { - val player = event.player - if (player is Player) { - handler(TTTPlayer.of(player) ?: return) - } - } - - protected fun handle(event: T, handler: (tttPlayer: TTTPlayer) -> Unit) { - handler(TTTPlayer.of(event.player) ?: return) - } - protected fun handle(event: PlayerItemConsumeEvent, handler: (tttPlayer: TTTPlayer) -> Unit) { - if (event.item.type == tttItem.itemStack.type) { + if (event.item.type == tttItem.material) { handler(TTTPlayer.of(event.player) ?: return) } } - data class ClickEventData( - val tttPlayer: TTTPlayer, - val itemStack: ItemStack, - val event: PlayerInteractEvent - ) + protected fun handleWithInstance(event: InventoryCloseEvent, handler: (instance: InstanceT) -> Unit) { + val player = event.player + if (player is Player) { + val tttPlayer = TTTPlayer.of(player) ?: return + val instance = tttItem.getInstance(tttPlayer) ?: return + handler(instance) + } + } + + protected fun handleWithInstance(event: InventoryClickEvent, handler: (instance: InstanceT) -> Unit) { + val whoClicked = event.whoClicked + if (whoClicked is Player) { + val tttPlayer = TTTPlayer.of(whoClicked) ?: return + val instance = tttItem.getInstance(tttPlayer) ?: return + handler(instance) + } + } + + protected fun handleWithInstance(event: PlayerEvent, handler: (instance: InstanceT) -> Unit) { + val player = event.player + val tttPlayer = TTTPlayer.of(player) ?: return + val instance = tttItem.getInstance(tttPlayer) ?: return + handler(instance) + } + + protected fun handle(event: TTTPlayerReviveEvent, handler: (instance: InstanceT) -> Unit) { + handler(tttItem.getInstance(event.tttPlayer) ?: return) + } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/CloakingDevice.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/CloakingDevice.kt index 72201d9..3507405 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/CloakingDevice.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/CloakingDevice.kt @@ -1,14 +1,13 @@ package de.moritzruth.spigot_ttt.game.items.impl import de.moritzruth.spigot_ttt.Resourcepack -import de.moritzruth.spigot_ttt.game.GameEndEvent -import de.moritzruth.spigot_ttt.game.items.Buyable -import de.moritzruth.spigot_ttt.game.items.Selectable +import de.moritzruth.spigot_ttt.game.items.ClickEvent import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.game.items.TTTItemListener -import de.moritzruth.spigot_ttt.game.players.* +import de.moritzruth.spigot_ttt.game.players.Role +import de.moritzruth.spigot_ttt.game.players.TTTPlayer +import de.moritzruth.spigot_ttt.game.players.roles import de.moritzruth.spigot_ttt.utils.applyMeta -import de.moritzruth.spigot_ttt.utils.startItemDamageProgress import org.bukkit.ChatColor import org.bukkit.SoundCategory import org.bukkit.event.EventHandler @@ -19,90 +18,67 @@ import org.bukkit.potion.PotionEffect import org.bukkit.potion.PotionEffectType import org.bukkit.scheduler.BukkitTask -object CloakingDevice: TTTItem, Buyable, Selectable { - override val itemStack = ItemStack(Resourcepack.Items.cloakingDevice).applyMeta { +object CloakingDevice: TTTItem( + type = Type.SPECIAL, + instanceType = Instance::class, + templateItemStack = ItemStack(Resourcepack.Items.cloakingDevice).applyMeta { setDisplayName("${ChatColor.GRAY}${ChatColor.MAGIC}###${ChatColor.RESET}${ChatColor.GRAY} Cloaking Device ${ChatColor.MAGIC}###") lore = listOf( "", "${ChatColor.GOLD}Macht dich unsichtbar" ) addItemFlags(ItemFlag.HIDE_ATTRIBUTES) - } + }, + shopInfo = ShopInfo( + buyableBy = roles(Role.TRAITOR, Role.JACKAL), + buyLimit = 1, + price = 2 + ) +) { + private const val WALK_SPEED_DECREASE = 0.1F private const val COOLDOWN = 10.0 - override val type = TTTItem.Type.SPECIAL - override val price = 2 - override val buyableBy = roles(Role.TRAITOR, Role.JACKAL) - override val buyLimit = 1 + class Instance: TTTItem.Instance(CloakingDevice) { + var enabled = false + private var cooldownTask: BukkitTask? = null - val isc = InversedStateContainer(State::class) - - override fun onSelect(tttPlayer: TTTPlayer) {} - override fun onDeselect(tttPlayer: TTTPlayer) = disable(tttPlayer) - - private fun enable(tttPlayer: TTTPlayer, itemStack: ItemStack) { - val state = isc.getOrCreate(tttPlayer) - - tttPlayer.player.apply { - isSprinting = false - walkSpeed = 0.1F - - addPotionEffect(PotionEffect(PotionEffectType.INVISIBILITY, 1000000, 0, false, false)) - playSound(location, Resourcepack.Sounds.Item.CloakingDevice.on, SoundCategory.PLAYERS, 1F, 1F) + override fun onRightClick(event: ClickEvent) { + if (cooldownTask == null) setEnabled(carrier!!, !enabled) } - state.enabled = true - state.itemStack = itemStack - } - - private fun disable(tttPlayer: TTTPlayer) { - val state = isc.get(tttPlayer) ?: return - if (!state.enabled) return - - tttPlayer.player.apply { - walkSpeed = 0.2F - removePotionEffect(PotionEffectType.INVISIBILITY) - playSound(location, Resourcepack.Sounds.Item.CloakingDevice.off, SoundCategory.PLAYERS, 1F, 1F) + override fun onCarrierRemoved(oldCarrier: TTTPlayer) { + setEnabled(oldCarrier, false) } - state.enabled = false + private fun setEnabled(tttPlayer: TTTPlayer, value: Boolean) { + if (value == enabled) return - val itemStack = state.itemStack - if (itemStack != null) { - state.cooldownTask = startItemDamageProgress(itemStack, COOLDOWN) { state.cooldownTask = null } - } - } + if (value) { + tttPlayer.walkSpeed -= WALK_SPEED_DECREASE + tttPlayer.player.apply { + isSprinting = false - override val listener = object : TTTItemListener(this, true) { - @EventHandler - fun onPlayerToggleSprint(event: PlayerToggleSprintEvent) = handle(event) { tttPlayer -> - if (event.isSprinting && isc.getOrCreate(tttPlayer).enabled) event.isCancelled = true - } - - @EventHandler - override fun onTTTPlayerDeath(event: TTTPlayerDeathEvent) { - super.onTTTPlayerDeath(event) - isc.get(event.tttPlayer)?.cooldownTask?.cancel() - } - - @EventHandler - fun onGameEnd(event: GameEndEvent) = isc.forEveryState { state, _ -> state.cooldownTask?.cancel() } - - override fun onRightClick(data: ClickEventData) { - val state = isc.getOrCreate(data.tttPlayer) - if (state.cooldownTask != null) return - - if (state.enabled) { - disable(data.tttPlayer) + addPotionEffect(PotionEffect(PotionEffectType.INVISIBILITY, 1000000, 0, false, false)) + playSound(location, Resourcepack.Sounds.Item.CloakingDevice.on, SoundCategory.PLAYERS, 1F, 1F) + } } else { - enable(data.tttPlayer, data.itemStack) + tttPlayer.walkSpeed += WALK_SPEED_DECREASE + tttPlayer.player.apply { + removePotionEffect(PotionEffectType.INVISIBILITY) + playSound(location, Resourcepack.Sounds.Item.CloakingDevice.off, SoundCategory.PLAYERS, 1F, 1F) + } + + // TODO: Show progress in level bar } + + enabled = value } } - class State: IState { - var enabled: Boolean = false - var cooldownTask: BukkitTask? = null - var itemStack: ItemStack? = null + override val listener = object : TTTItemListener(CloakingDevice) { + @EventHandler + fun onPlayerToggleSprint(event: PlayerToggleSprintEvent) = handleWithInstance(event) { instance -> + if (event.isSprinting && instance.enabled) event.isCancelled = true + } } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Defibrillator.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Defibrillator.kt index 574701d..1ccc694 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Defibrillator.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Defibrillator.kt @@ -1,13 +1,13 @@ package de.moritzruth.spigot_ttt.game.items.impl import de.moritzruth.spigot_ttt.Resourcepack -import de.moritzruth.spigot_ttt.game.GameEndEvent import de.moritzruth.spigot_ttt.game.GameManager import de.moritzruth.spigot_ttt.game.corpses.CorpseClickEvent -import de.moritzruth.spigot_ttt.game.items.Buyable import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.game.items.TTTItemListener -import de.moritzruth.spigot_ttt.game.players.* +import de.moritzruth.spigot_ttt.game.players.Role +import de.moritzruth.spigot_ttt.game.players.TTTPlayer +import de.moritzruth.spigot_ttt.game.players.roles import de.moritzruth.spigot_ttt.plugin import de.moritzruth.spigot_ttt.utils.* import org.bukkit.ChatColor @@ -20,49 +20,67 @@ import org.bukkit.scheduler.BukkitTask import java.time.Duration import java.time.Instant -object Defibrillator: TTTItem, Buyable { - private const val REVIVE_DURATION = 10.0 - - override val type = TTTItem.Type.SPECIAL - override val itemStack = ItemStack(Resourcepack.Items.defibrillator).applyMeta { +object Defibrillator: TTTItem( + type = Type.SPECIAL, + instanceType = Instance::class, + templateItemStack = ItemStack(Resourcepack.Items.defibrillator).applyMeta { setDisplayName("${ChatColor.RESET}${ChatColor.BOLD}Defibrillator") hideInfo() lore = listOf( "", "${ChatColor.GOLD}Belebe einen Spieler wieder" ) - } - override val buyableBy = roles(Role.TRAITOR, Role.DETECTIVE, Role.JACKAL) - override val price = 2 - override val buyLimit = 1 + }, + shopInfo = ShopInfo( + buyLimit = 1, + price = 2, + buyableBy = roles(Role.TRAITOR, Role.DETECTIVE, Role.JACKAL) + ) +) { + private const val REVIVE_DURATION = 10.0 - private val isc = InversedStateContainer(State::class) + class Instance: TTTItem.Instance(Defibrillator) { + var action: Action? = null + var bossBar = plugin.server.createBossBar( + "${ChatColor.BOLD}Defibrillator", + BarColor.GREEN, + BarStyle.SOLID + ) + + override fun onCarrierSet(carrier: TTTPlayer, isFirst: Boolean) { + bossBar.addPlayer(carrier.player) + } + + override fun onCarrierRemoved(oldCarrier: TTTPlayer) { + bossBar.removePlayer(oldCarrier.player) + } + + override fun reset() { + action?.reset() + stopSound() + } + } fun stopSound() = plugin.server.onlinePlayers.forEach { it.stopSound(Resourcepack.Sounds.Item.Defibrillator.use, SoundCategory.PLAYERS) } - override val listener = object : TTTItemListener(this, true) { + override val listener = object : TTTItemListener(this) { @EventHandler(ignoreCancelled = true) fun onCorpseClick(event: CorpseClickEvent) { - if (event.tttPlayer.player.inventory.itemInMainHand.type != itemStack.type) return + val instance = getInstance(event.tttPlayer.player.inventory.itemInMainHand) ?: return event.isCancelled = true - val state = isc.getOrCreate(event.tttPlayer) - state.bossBar.addPlayer(event.tttPlayer.player) - - when(val action = state.action) { - null -> state.action = Action.Reviving(event.tttPlayer, state) + when(val action = instance.action) { + null -> instance.action = Action.Reviving(event.tttPlayer, instance) is Action.Reviving -> { action.cancelTask.cancel() action.cancelTask = action.createCancelTask() val progress = action.duration / REVIVE_DURATION - if (progress >= 1) { try { event.tttCorpse.revive() - event.tttPlayer.player.sendActionBarMessage( "${ChatColor.BOLD}${event.tttCorpse.tttPlayer.player.displayName} " + "${ChatColor.GREEN}wurde wiederbelebt" @@ -70,25 +88,20 @@ object Defibrillator: TTTItem, Buyable { action.cancelTask.cancel() event.tttPlayer.player.inventory.removeTTTItemNextTick(Defibrillator) - state.reset(event.tttPlayer) - isc.remove(event.tttPlayer) } catch(e: TTTPlayer.AlreadyLivingException) { action.cancel() } - } else state.bossBar.progress = progress + } else instance.bossBar.progress = progress } is Action.Canceled -> noop() } } - - @EventHandler - fun onGameEnd(event: GameEndEvent) = isc.forEveryState { state, tttPlayer -> state.reset(tttPlayer) } } sealed class Action(val tttPlayer: TTTPlayer) { open fun reset() {} - class Reviving(tttPlayer: TTTPlayer, val state: State): Action(tttPlayer) { + class Reviving(tttPlayer: TTTPlayer, val instance: Instance): Action(tttPlayer) { var cancelTask = createCancelTask() private val startedAt: Instant = Instant.now() val duration get() = Duration.between(startedAt, Instant.now()).toMillis().toDouble() / 1000 @@ -99,7 +112,7 @@ object Defibrillator: TTTItem, Buyable { fun cancel() { cancelTask.cancel() - state.action = Canceled(tttPlayer) + instance.action = Canceled(tttPlayer, instance) } init { @@ -111,14 +124,14 @@ object Defibrillator: TTTItem, Buyable { 1F ) - state.bossBar.color = BarColor.GREEN - state.bossBar.addPlayer(tttPlayer.player) + instance.bossBar.color = BarColor.GREEN + instance.bossBar.isVisible = true } override fun reset() = cancelTask.cancel() } - class Canceled(tttPlayer: TTTPlayer): Action(tttPlayer) { + class Canceled(tttPlayer: TTTPlayer, val instance: Instance): Action(tttPlayer) { private var switches: Int = 0 private lateinit var task: BukkitTask @@ -132,16 +145,15 @@ object Defibrillator: TTTItem, Buyable { 1F ) - task = plugin.server.scheduler.runTaskTimer(plugin, fun() { - val state = isc.get(tttPlayer) ?: return@runTaskTimer - + plugin.server.scheduler.runTaskTimer(plugin, { task -> if (switches == SWITCHES_COUNT) { + this@Canceled.task = task task.cancel() - state.action = null - state.bossBar.removePlayer(tttPlayer.player) + instance.action = null + instance.bossBar.isVisible = false } else { - state.bossBar.progress = 1.0 - state.bossBar.color = if (switches % 2 == 0) BarColor.RED else BarColor.WHITE + instance.bossBar.progress = 1.0 + instance.bossBar.color = if (switches % 2 == 0) BarColor.RED else BarColor.WHITE switches += 1 } }, 0, secondsToTicks(0.2).toLong()) @@ -154,19 +166,4 @@ object Defibrillator: TTTItem, Buyable { } } } - - class State: IState { - var action: Action? = null - var bossBar = plugin.server.createBossBar( - "${ChatColor.BOLD}Defibrillator", - BarColor.GREEN, - BarStyle.SOLID - ) - - fun reset(tttPlayer: TTTPlayer) { - bossBar.removePlayer(tttPlayer.player) - action?.reset() - stopSound() - } - } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/EnderPearl.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/EnderPearl.kt index c3d772a..17523ea 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/EnderPearl.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/EnderPearl.kt @@ -1,27 +1,25 @@ package de.moritzruth.spigot_ttt.game.items.impl -import de.moritzruth.spigot_ttt.game.items.TTTItemListener +import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.game.players.Role import de.moritzruth.spigot_ttt.game.players.roles -import de.moritzruth.spigot_ttt.game.items.Buyable -import de.moritzruth.spigot_ttt.game.items.TTTItem +import de.moritzruth.spigot_ttt.utils.Probability import de.moritzruth.spigot_ttt.utils.applyMeta import org.bukkit.ChatColor import org.bukkit.Material import org.bukkit.inventory.ItemStack -object EnderPearl: TTTItem, Buyable { - override val type = TTTItem.Type.SPECIAL - override val itemStack = ItemStack(Material.ENDER_PEARL).applyMeta { +object EnderPearl : TTTItem( + instanceType = Instance::class, + type = Type.SPECIAL, + templateItemStack = ItemStack(Material.ENDER_PEARL).applyMeta { setDisplayName("${ChatColor.DARK_GREEN}Enderperle") - } - override val buyableBy = roles(Role.TRAITOR, Role.JACKAL, Role.DETECTIVE) - override val price = 1 - override val buyLimit: Int? = null - - override val listener = object : TTTItemListener(this, true) { - override fun onRightClick(data: ClickEventData) { - data.event.isCancelled = false - } - } + }, + spawnProbability = Probability.VERY_LOW, + shopInfo = ShopInfo( + buyableBy = roles(Role.TRAITOR, Role.JACKAL, Role.DETECTIVE), + price = 1 + ) +) { + class Instance: TTTItem.Instance(EnderPearl) } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/FakeCorpse.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/FakeCorpse.kt index d716f58..fa7bc28 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/FakeCorpse.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/FakeCorpse.kt @@ -2,10 +2,13 @@ package de.moritzruth.spigot_ttt.game.items.impl import de.moritzruth.spigot_ttt.Resourcepack import de.moritzruth.spigot_ttt.game.corpses.TTTCorpse -import de.moritzruth.spigot_ttt.game.items.Buyable +import de.moritzruth.spigot_ttt.game.items.ClickEvent import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.game.items.TTTItemListener -import de.moritzruth.spigot_ttt.game.players.* +import de.moritzruth.spigot_ttt.game.players.PlayerManager +import de.moritzruth.spigot_ttt.game.players.Role +import de.moritzruth.spigot_ttt.game.players.TTTPlayer +import de.moritzruth.spigot_ttt.game.players.roles import de.moritzruth.spigot_ttt.plugin import de.moritzruth.spigot_ttt.utils.applyMeta import de.moritzruth.spigot_ttt.utils.hideInfo @@ -21,24 +24,34 @@ import org.bukkit.inventory.Inventory import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.SkullMeta -object FakeCorpse: TTTItem, Buyable { - private val DISPLAY_NAME = "${ChatColor.YELLOW}${ChatColor.BOLD}Fake-Leiche" - - override val itemStack = ItemStack(Resourcepack.Items.fakeCorpse).applyMeta { - setDisplayName(DISPLAY_NAME) +object FakeCorpse: TTTItem( + type = Type.SPECIAL, + instanceType = Instance::class, + shopInfo = ShopInfo( + buyableBy = roles(Role.TRAITOR, Role.JACKAL), + buyLimit = 3, + price = 2 + ), + templateItemStack = ItemStack(Resourcepack.Items.fakeCorpse).applyMeta { + setDisplayName(FakeCorpse.DISPLAY_NAME) lore = listOf( - "", - "${ChatColor.GOLD}Spawnt eine Fake-Leiche", - "${ChatColor.GOLD}Rolle und Spieler auswählbar" + "", + "${ChatColor.GOLD}Spawnt eine Fake-Leiche", + "${ChatColor.GOLD}Rolle und Spieler auswählbar" ) hideInfo() } - override val type = TTTItem.Type.SPECIAL - override val buyableBy = roles(Role.TRAITOR, Role.JACKAL) - override val price = 2 - override val buyLimit: Int? = 3 +) { + private val DISPLAY_NAME = "${ChatColor.YELLOW}${ChatColor.BOLD}Fake-Leiche" - val isc = InversedStateContainer(State::class) + class Instance: TTTItem.Instance(FakeCorpse) { + var chosenRole: Role? = null + var choosePlayerInventory: Inventory? = null + + override fun onRightClick(event: ClickEvent) { + carrier!!.player.openInventory(chooseRoleInventory) + } + } private val chooseRoleInventory = plugin.server.createInventory( null, @@ -72,17 +85,17 @@ object FakeCorpse: TTTItem, Buyable { .toTypedArray()) } - override val listener = object : TTTItemListener(this, true) { - override fun onRightClick(data: ClickEventData) { - isc.getOrCreate(data.tttPlayer).chosenRole = null - data.tttPlayer.player.openInventory(chooseRoleInventory) - } - + override val listener = object : TTTItemListener(this) { @EventHandler fun onInventoryClick(event: InventoryClickEvent) = handle(event) { tttPlayer -> - val state = isc.getOrCreate(tttPlayer) + val instance = getInstance(tttPlayer) ?: return@handle - if (!setOf(state.choosePlayerInventory, chooseRoleInventory).contains(event.clickedInventory)) return@handle + if ( + !setOf( + instance.choosePlayerInventory, + chooseRoleInventory + ).contains(event.clickedInventory) + ) return@handle event.isCancelled = true val item = event.currentItem @@ -90,11 +103,12 @@ object FakeCorpse: TTTItem, Buyable { if (item != null && event.click == ClickType.LEFT) { when (event.clickedInventory) { chooseRoleInventory -> { - state.chosenRole = Role.values()[event.slot] - state.choosePlayerInventory = createChoosePlayerInventory() - tttPlayer.player.openInventory(state.choosePlayerInventory!!) + instance.chosenRole = Role.values()[event.slot] + val choosePlayerInventory = createChoosePlayerInventory() + instance.choosePlayerInventory = choosePlayerInventory + tttPlayer.player.openInventory(choosePlayerInventory) } - state.choosePlayerInventory -> { + instance.choosePlayerInventory -> { tttPlayer.player.closeInventory() val corpsePlayer = plugin.server.getPlayer((item.itemMeta as SkullMeta).owningPlayer!!.uniqueId)!! @@ -103,8 +117,7 @@ object FakeCorpse: TTTItem, Buyable { if (corpseTTTPlayer == null) { tttPlayer.player.sendActionBarMessage("${ChatColor.RED}Das hat nicht funktioniert") } else { - TTTCorpse.spawnFake(state.chosenRole!!, corpseTTTPlayer, tttPlayer.player.location) - + TTTCorpse.spawnFake(instance.chosenRole!!, corpseTTTPlayer, tttPlayer.player.location) tttPlayer.player.inventory.removeTTTItem(FakeCorpse) } } @@ -112,9 +125,4 @@ object FakeCorpse: TTTItem, Buyable { } } } - - class State: IState { - var chosenRole: Role? = null - var choosePlayerInventory: Inventory? = null - } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Fireball.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Fireball.kt index 80f275b..c0b70ff 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Fireball.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Fireball.kt @@ -1,12 +1,12 @@ package de.moritzruth.spigot_ttt.game.items.impl -import de.moritzruth.spigot_ttt.game.items.TTTItemListener import de.moritzruth.spigot_ttt.game.GameManager +import de.moritzruth.spigot_ttt.game.items.ClickEvent +import de.moritzruth.spigot_ttt.game.items.TTTItem +import de.moritzruth.spigot_ttt.game.items.TTTItemListener import de.moritzruth.spigot_ttt.game.players.Role import de.moritzruth.spigot_ttt.game.players.TTTPlayer import de.moritzruth.spigot_ttt.game.players.roles -import de.moritzruth.spigot_ttt.game.items.Buyable -import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.utils.applyMeta import de.moritzruth.spigot_ttt.utils.clearHeldItemSlot import de.moritzruth.spigot_ttt.utils.createKillExplosion @@ -18,44 +18,49 @@ import org.bukkit.entity.EntityType import org.bukkit.event.EventHandler import org.bukkit.event.entity.ExplosionPrimeEvent import org.bukkit.inventory.ItemStack +import java.util.* typealias FireballEntity = org.bukkit.entity.Fireball -object Fireball: TTTItem, Buyable { - override val type = TTTItem.Type.SPECIAL - override val itemStack = ItemStack(Material.FIRE_CHARGE).applyMeta { +object Fireball: TTTItem( + type = Type.SPECIAL, + instanceType = Instance::class, + templateItemStack = ItemStack(Material.FIRE_CHARGE).applyMeta { setDisplayName("${ChatColor.DARK_RED}${ChatColor.BOLD}Feuerball") - lore = listOf( "", "${ChatColor.GOLD}Wirf einen Feuerball" ) - } - override val buyableBy = roles(Role.TRAITOR, Role.JACKAL) - override val price = 1 - override val buyLimit: Int? = null + }, + shopInfo = ShopInfo( + buyableBy = roles(Role.TRAITOR, Role.JACKAL), + buyLimit = 0, + price = 1 + ) +) { + class Instance: TTTItem.Instance(Fireball) { + override fun onRightClick(event: ClickEvent) { + val carrier = carrier!! + carrier.player.inventory.clearHeldItemSlot() - val sendersByEntity = mutableMapOf() - - override val listener = object : TTTItemListener(this, true) { - override fun onRightClick(data: ClickEventData) { - data.tttPlayer.player.inventory.clearHeldItemSlot() - - val vector = data.tttPlayer.player.eyeLocation.toVector() - val location = vector.add(data.tttPlayer.player.eyeLocation.direction.multiply(1.2)) - .toLocation(data.tttPlayer.player.location.world!!) + val vector = carrier.player.eyeLocation.toVector() + val location = vector.add(carrier.player.eyeLocation.direction.multiply(1.2)) + .toLocation(carrier.player.location.world!!) val fireball = GameManager.world.spawnEntity(location, EntityType.FIREBALL) as FireballEntity - fireball.direction = data.tttPlayer.player.eyeLocation.direction - sendersByEntity[fireball] = data.tttPlayer + fireball.direction = carrier.player.eyeLocation.direction + sendersByEntity[fireball] = carrier } + } + val sendersByEntity = WeakHashMap() + + override val listener = object : TTTItemListener(this) { @EventHandler fun onExplosionPrime(event: ExplosionPrimeEvent) { val sender = sendersByEntity[event.entity] if (sender != null) { - sendersByEntity.remove(event.entity) event.isCancelled = true GameManager.world.playSound( diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/HealingPotion.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/HealingPotion.kt index 52a112c..7409aee 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/HealingPotion.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/HealingPotion.kt @@ -1,11 +1,12 @@ package de.moritzruth.spigot_ttt.game.items.impl +import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.game.items.TTTItemListener import de.moritzruth.spigot_ttt.game.players.Role import de.moritzruth.spigot_ttt.game.players.roles -import de.moritzruth.spigot_ttt.game.items.Buyable -import de.moritzruth.spigot_ttt.game.items.TTTItem +import de.moritzruth.spigot_ttt.utils.Probability import de.moritzruth.spigot_ttt.utils.applyMeta +import de.moritzruth.spigot_ttt.utils.applyTypedMeta import org.bukkit.ChatColor import org.bukkit.Material import org.bukkit.attribute.Attribute @@ -17,35 +18,35 @@ import org.bukkit.inventory.meta.PotionMeta import org.bukkit.potion.PotionData import org.bukkit.potion.PotionType -object HealingPotion: TTTItem, Buyable { - override val itemStack = ItemStack(Material.POTION).apply { - val potionMeta = itemMeta as PotionMeta - potionMeta.basePotionData = PotionData(PotionType.INSTANT_HEAL, false, true) - itemMeta = potionMeta - }.applyMeta { - setDisplayName("${ChatColor.LIGHT_PURPLE}Heiltrank") - lore = listOf( +object HealingPotion: TTTItem( + instanceType = Instance::class, + type = Type.SPECIAL, + spawnProbability = Probability.VERY_LOW, + templateItemStack = ItemStack(Material.POTION) + .applyTypedMeta { basePotionData = PotionData(PotionType.INSTANT_HEAL, false, true) } + .applyMeta { + setDisplayName("${ChatColor.LIGHT_PURPLE}Heiltrank") + lore = listOf( "", "${ChatColor.GOLD}Heilt dich voll" - ) + ) - addItemFlags(ItemFlag.HIDE_POTION_EFFECTS) - } - override val type = TTTItem.Type.SPECIAL - override val buyableBy = roles(Role.TRAITOR, Role.JACKAL, Role.DETECTIVE) - override val price = 1 - override val buyLimit = 2 - - override val listener = object : TTTItemListener(this, true) { + addItemFlags(ItemFlag.HIDE_POTION_EFFECTS) + }, + shopInfo = ShopInfo( + buyableBy = roles(Role.TRAITOR, Role.JACKAL, Role.DETECTIVE), + price = 1, + buyLimit = 2 + ) +) { + override val listener = object : TTTItemListener(this) { @EventHandler fun onPlayerItemConsume(event: PlayerItemConsumeEvent) = handle(event) { event.isCancelled = true event.player.inventory.clear(event.player.inventory.indexOf(event.item)) event.player.health = event.player.getAttribute(Attribute.GENERIC_MAX_HEALTH)?.value ?: 100.0 } - - override fun onRightClick(data: ClickEventData) { - data.event.isCancelled = false - } } + + class Instance: TTTItem.Instance(HealingPotion) } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/MartyrdomGrenade.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/MartyrdomGrenade.kt index 41eb1a3..0c6692f 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/MartyrdomGrenade.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/MartyrdomGrenade.kt @@ -1,13 +1,12 @@ package de.moritzruth.spigot_ttt.game.items.impl import de.moritzruth.spigot_ttt.Resourcepack -import de.moritzruth.spigot_ttt.game.GameEndEvent import de.moritzruth.spigot_ttt.game.GameManager -import de.moritzruth.spigot_ttt.game.items.Buyable -import de.moritzruth.spigot_ttt.game.items.PASSIVE import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.game.items.TTTItemListener -import de.moritzruth.spigot_ttt.game.players.* +import de.moritzruth.spigot_ttt.game.players.Role +import de.moritzruth.spigot_ttt.game.players.TTTPlayerTrueDeathEvent +import de.moritzruth.spigot_ttt.game.players.roles import de.moritzruth.spigot_ttt.plugin import de.moritzruth.spigot_ttt.utils.applyMeta import de.moritzruth.spigot_ttt.utils.createKillExplosion @@ -19,33 +18,39 @@ import org.bukkit.event.EventHandler import org.bukkit.inventory.ItemStack import org.bukkit.scheduler.BukkitTask -object MartyrdomGrenade: TTTItem, Buyable { - override val type = TTTItem.Type.SPECIAL - override val itemStack = ItemStack(Resourcepack.Items.martyrdomGrenade).applyMeta { +object MartyrdomGrenade: TTTItem( + type = Type.SPECIAL, + instanceType = Instance::class, + templateItemStack = ItemStack(Resourcepack.Items.martyrdomGrenade).applyMeta { hideInfo() - setDisplayName("${ChatColor.DARK_PURPLE}${ChatColor.BOLD}Märtyriumsgranate $PASSIVE") - + setDisplayName("${ChatColor.DARK_PURPLE}${ChatColor.BOLD}Märtyriumsgranate$PASSIVE_SUFFIX") lore = listOf( "", "${ChatColor.GOLD}Lasse bei deinem Tod", "${ChatColor.GOLD}eine Granate fallen" ) - } - override val buyableBy = roles(Role.TRAITOR) - override val buyLimit: Int? = null - override val price = 1 - val isc = InversedStateContainer(State::class) + }, + shopInfo = ShopInfo( + buyableBy = roles(Role.TRAITOR, Role.JACKAL), + price = 1 + ) +) { + class Instance: TTTItem.Instance(MartyrdomGrenade, true) { + var explodeTask: BukkitTask? = null - override fun onOwn(tttPlayer: TTTPlayer) { - isc.getOrCreate(tttPlayer) + override fun reset() { + explodeTask?.cancel() + explodeTask = null + } } - override val listener = object : TTTItemListener(this, true) { + override val listener = object : TTTItemListener(this) { @EventHandler fun onTTTPlayerTrueDeath(event: TTTPlayerTrueDeathEvent) { - val state = isc.get(event.tttPlayer) ?: return + val instance = getInstance(event.tttPlayer) ?: return + event.tttPlayer.removeItem(MartyrdomGrenade, false) - state.explodeTask = plugin.server.scheduler.runTaskLater(plugin, fun() { + instance.explodeTask = plugin.server.scheduler.runTaskLater(plugin, fun() { GameManager.world.playSound( event.location, Resourcepack.Sounds.grenadeExplode, @@ -57,15 +62,5 @@ object MartyrdomGrenade: TTTItem, Buyable { createKillExplosion(event.tttPlayer, event.location, 2.5) }, secondsToTicks(3).toLong()) } - - @EventHandler - fun onGameEnd(event: GameEndEvent) = isc.forEveryState { state, _ -> - state.explodeTask?.cancel() - state.explodeTask = null - } - } - - class State: IState { - var explodeTask: BukkitTask? = null } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Radar.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Radar.kt index 82bbba2..e8468ff 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Radar.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Radar.kt @@ -5,35 +5,29 @@ import com.comphenix.protocol.PacketType import com.comphenix.protocol.events.PacketAdapter import com.comphenix.protocol.events.PacketEvent import de.moritzruth.spigot_ttt.Resourcepack -import de.moritzruth.spigot_ttt.game.GameEndEvent -import de.moritzruth.spigot_ttt.game.items.Buyable -import de.moritzruth.spigot_ttt.game.items.PASSIVE import de.moritzruth.spigot_ttt.game.items.TTTItem -import de.moritzruth.spigot_ttt.game.items.TTTItemListener -import de.moritzruth.spigot_ttt.game.players.* +import de.moritzruth.spigot_ttt.game.players.PlayerManager +import de.moritzruth.spigot_ttt.game.players.Role +import de.moritzruth.spigot_ttt.game.players.TTTPlayer +import de.moritzruth.spigot_ttt.game.players.roles import de.moritzruth.spigot_ttt.plugin import de.moritzruth.spigot_ttt.utils.applyMeta import de.moritzruth.spigot_ttt.utils.hideInfo import org.bukkit.ChatColor import org.bukkit.boss.BarColor import org.bukkit.boss.BarStyle -import org.bukkit.boss.BossBar -import org.bukkit.event.EventHandler import org.bukkit.inventory.ItemStack import org.bukkit.scheduler.BukkitTask import java.time.Duration import java.time.Instant -import java.util.* import kotlin.experimental.and import kotlin.experimental.or -object Radar: TTTItem, Buyable { - private val DISPLAY_NAME = "${ChatColor.DARK_AQUA}${ChatColor.BOLD}Radar" - private const val ACTIVE_DURATION = 10 - private const val COOLDOWN_DURATION = 40 - - override val itemStack = ItemStack(Resourcepack.Items.radar).applyMeta { - setDisplayName("$DISPLAY_NAME $PASSIVE") +object Radar: TTTItem( + type = Type.SPECIAL, + instanceType = Instance::class, + templateItemStack = ItemStack(Resourcepack.Items.radar).applyMeta { + setDisplayName("${ChatColor.DARK_AQUA}${ChatColor.BOLD}Radar$PASSIVE_SUFFIX") lore = listOf( "", "${ChatColor.GOLD}Zeigt dir alle 30 Sekunden", @@ -42,75 +36,66 @@ object Radar: TTTItem, Buyable { ) hideInfo() - } - override val type = TTTItem.Type.SPECIAL - override val buyableBy: EnumSet = EnumSet.of(Role.TRAITOR, Role.DETECTIVE, Role.JACKAL) - override val price = 2 - override val buyLimit: Int? = null + }, + shopInfo = ShopInfo( + buyableBy = roles(Role.TRAITOR, Role.DETECTIVE, Role.JACKAL), + price = 2 + ) +) { + private const val ACTIVE_DURATION = 10 + private const val COOLDOWN_DURATION = 40 + private val BOSS_BAR_TITLE = "${ChatColor.DARK_AQUA}${ChatColor.BOLD}Radar" - val isc = InversedStateContainer(State::class) + class Instance: TTTItem.Instance(Radar) { + var active: Boolean = true + private var timestamp = Instant.now()!! + private val bossBar = plugin.server.createBossBar(BOSS_BAR_TITLE, BarColor.BLUE, BarStyle.SOLID) - override fun onOwn(tttPlayer: TTTPlayer) { - val state = isc.getOrCreate(tttPlayer) + private var task: BukkitTask = plugin.server.scheduler.runTaskTimer(plugin, fun() { + val duration = Duration.between(timestamp, Instant.now()).toMillis().toDouble() / 1000 - state.bossBar = plugin.server.createBossBar(DISPLAY_NAME, BarColor.BLUE, BarStyle.SOLID) - state.bossBar.addPlayer(tttPlayer.player) - - setActive(tttPlayer, true) - state.task = plugin.server.scheduler.runTaskTimer(plugin, fun() { - val duration = Duration.between(state.timestamp, Instant.now()).toMillis().toDouble() / 1000 - - if (state.active) { + if (active) { if (duration > ACTIVE_DURATION) { - setActive(tttPlayer, false) + active = false + bossBar.setTitle(BOSS_BAR_TITLE + "${ChatColor.WHITE} - ${ChatColor.GRAY}Cooldown") + carrier?.let { resendEntityMetadata(it) } + timestamp = Instant.now() } else { - state.bossBar.progress = 1.0 - duration / ACTIVE_DURATION + bossBar.progress = 1.0 - duration / ACTIVE_DURATION } } else { if (duration > COOLDOWN_DURATION) { - setActive(tttPlayer, true) + active = true + bossBar.setTitle(BOSS_BAR_TITLE + "${ChatColor.WHITE} - ${ChatColor.GREEN}Aktiv") + carrier?.let { resendEntityMetadata(it) } + timestamp = Instant.now() } else { - state.bossBar.progress = duration / COOLDOWN_DURATION + bossBar.progress = duration / COOLDOWN_DURATION } } - }, 0, 2) - } + }, 0, 1) - private fun setActive(tttPlayer: TTTPlayer, value: Boolean) { - val state = isc.getOrCreate(tttPlayer) + override fun onCarrierSet(carrier: TTTPlayer, isFirst: Boolean) { + bossBar.addPlayer(carrier.player) + if (active) resendEntityMetadata(carrier) + } - if (state.active != value) { - state.active = value - state.timestamp = Instant.now() + override fun onCarrierRemoved(oldCarrier: TTTPlayer) { + bossBar.removePlayer(oldCarrier.player) + if (active) resendEntityMetadata(oldCarrier) + } - if (value) { - state.bossBar.setTitle(DISPLAY_NAME + "${ChatColor.WHITE} - ${ChatColor.GREEN}Aktiv") - } else { - state.bossBar.setTitle(DISPLAY_NAME + "${ChatColor.WHITE} - ${ChatColor.GRAY}Cooldown") - } - - // Toggle sending the entity metadata - PlayerManager.tttPlayers.forEach { - if (it !== tttPlayer) { - tttPlayer.player.hidePlayer(plugin, it.player) - tttPlayer.player.showPlayer(plugin, it.player) - } - } + override fun reset() { + task.cancel() } } - override val listener = object : TTTItemListener(this, true) { - @EventHandler - fun onTTTPlayerTrueDeath(event: TTTPlayerTrueDeathEvent) { - isc.get(event.tttPlayer)?.reset(event.tttPlayer) - isc.remove(event.tttPlayer) - } - - @EventHandler - fun onGameEnd(event: GameEndEvent) = isc.forEveryState { state, tttPlayer -> state.reset(tttPlayer) } - - override fun onRightClick(data: ClickEventData) { - data.event.isCancelled = true + fun resendEntityMetadata(tttPlayer: TTTPlayer) { + PlayerManager.tttPlayers.forEach { + if (it !== tttPlayer) { + tttPlayer.player.hidePlayer(plugin, it.player) + tttPlayer.player.showPlayer(plugin, it.player) + } } } @@ -119,15 +104,17 @@ object Radar: TTTItem, Buyable { val receivingTTTPlayer = TTTPlayer.of(event.player) ?: return val packet = WrapperPlayServerEntityMetadata(event.packet) - val playerOfPacket = plugin.server.onlinePlayers.find { it.entityId == packet.entityID } ?: return - val tttPlayerOfPacket = TTTPlayer.of(playerOfPacket) ?: return + val tttPlayerOfPacket = plugin.server.onlinePlayers + .find { it.entityId == packet.entityID } + ?.let { TTTPlayer.of(it) } ?: return + val instance = getInstance(receivingTTTPlayer) ?: return if (tttPlayerOfPacket.alive) { // https://wiki.vg/Entity_metadata#Entity_Metadata_Format try { val modifiers = packet.metadata[0].value as Byte packet.metadata[0].value = - if (isc.get(receivingTTTPlayer)?.active == true) modifiers or 0x40.toByte() + if (instance.active) modifiers or 0x40.toByte() else modifiers and 0b10111111.toByte() } catch (ignored: Exception) { // Idk why this throws exceptions, but it works anyways @@ -135,18 +122,4 @@ object Radar: TTTItem, Buyable { } } } - - class State: IState { - var task: BukkitTask? = null - var active: Boolean = false - lateinit var timestamp: Instant - lateinit var bossBar: BossBar - - fun reset(tttPlayer: TTTPlayer) { - setActive(tttPlayer, false) - - task?.cancel() - bossBar.removePlayer(tttPlayer.player) - } - } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/SecondChance.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/SecondChance.kt index 726f65b..ec39e59 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/SecondChance.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/SecondChance.kt @@ -1,10 +1,7 @@ package de.moritzruth.spigot_ttt.game.items.impl import de.moritzruth.spigot_ttt.Resourcepack -import de.moritzruth.spigot_ttt.game.GameEndEvent import de.moritzruth.spigot_ttt.game.GameManager -import de.moritzruth.spigot_ttt.game.items.Buyable -import de.moritzruth.spigot_ttt.game.items.PASSIVE import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.game.items.TTTItemListener import de.moritzruth.spigot_ttt.game.players.* @@ -27,26 +24,76 @@ 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 = 10.0 - - override val type = TTTItem.Type.SPECIAL - override val itemStack = ItemStack(Resourcepack.Items.secondChance).applyMeta { - setDisplayName("$DISPLAY_NAME $PASSIVE") +object SecondChance: TTTItem( + type = Type.SPECIAL, + instanceType = Instance::class, + templateItemStack = ItemStack(Resourcepack.Items.secondChance).applyMeta { + setDisplayName("${ChatColor.GREEN}${ChatColor.BOLD}Second Chance$PASSIVE_SUFFIX") hideInfo() lore = listOf( "", "${ChatColor.GOLD}Du wirst mit einer 50%-Chance", "${ChatColor.GOLD}wiederbelebt, wenn du stirbst" ) + }, + shopInfo = ShopInfo( + buyableBy = roles(Role.TRAITOR, Role.JACKAL), + buyLimit = 1, + price = 2 + ) +) { + val ON_CORPSE = Resourcepack.Items.arrowDown + val ON_SPAWN = Resourcepack.Items.dot + private const val TIMEOUT = 10.0 + + class Instance: TTTItem.Instance(SecondChance, false) { + var preventRoundEnd = false; private set + var timeoutAction: TimeoutAction? = null + + fun possiblyTrigger() { + if (Random.nextBoolean()) trigger() + } + + private fun trigger() { + preventRoundEnd = true + timeoutAction = TimeoutAction(this) + } + + class TimeoutAction(private val instance: Instance) { + val deathLocation: Location = instance.requireCarrier().player.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(instance.requireCarrier().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) onTimeout() else bossBar.progress = 1.0 - progress + }, 0, 1) + + private fun onTimeout() { + try { + PlayerManager.letRemainingRoleGroupWin() + } catch (e: IllegalStateException) {} + + stop() + } + + fun stop() { + val carrier = instance.requireCarrier() + task.cancel() + carrier.player.apply { + closeInventory() + bossBar.removePlayer(this) + } + carrier.removeItem(SecondChance) + } + } } - 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, @@ -69,97 +116,42 @@ object SecondChance: TTTItem, Buyable { }) } - override fun onOwn(tttPlayer: TTTPlayer) { - isc.getOrCreate(tttPlayer) - } - - override val listener = object : TTTItemListener(this, true, false) { + override val listener = object : TTTItemListener(this) { @EventHandler fun onTTTPlayerTrueDeath(event: TTTPlayerTrueDeathEvent) { - val state = isc.get(event.tttPlayer) - if (state != null) { - if (true || Random.nextBoolean()) { - event.winnerRoleGroup = null - event.tttPlayer.player.openInventory(chooseSpawnInventory) - state.timeoutAction = TimeoutAction(event.tttPlayer, event.tttCorpse.location) - } - } - - isc.forEveryState { s, _ -> if (s.timeoutAction != null) event.winnerRoleGroup = null } + val instance = getInstance(event.tttPlayer) ?: return + instance.possiblyTrigger() + if (instancesByUUID.values.find { it.preventRoundEnd } != null) event.winnerRoleGroup = null } @EventHandler - fun onInventoryClose(event: InventoryCloseEvent) = handle(event) { tttPlayer -> + fun onInventoryClose(event: InventoryCloseEvent) { if (event.inventory == chooseSpawnInventory) { - if (isc.get(tttPlayer)?.timeoutAction != null) { - nextTick { if (isc.get(tttPlayer) != null) tttPlayer.player.openInventory(chooseSpawnInventory) } + handleWithInstance(event) { instance -> + nextTick { instance.carrier?.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 + fun onInventoryClick(event: InventoryClickEvent) { + if (event.clickedInventory != chooseSpawnInventory) return - val location = when (event.currentItem?.type) { - ON_SPAWN -> GameManager.world.spawnLocation - ON_CORPSE -> timeoutAction.deathLocation - else -> return@handle + handleWithInstance(event) { instance -> + val timeoutAction = instance.timeoutAction!! + + val location = when (event.currentItem?.type) { + ON_SPAWN -> GameManager.world.spawnLocation + ON_CORPSE -> timeoutAction.deathLocation + else -> return@handleWithInstance + } + + timeoutAction.stop() + instance.carrier!!.revive(location) } - - 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) onTimeout() else bossBar.progress = 1.0 - progress - }, 0, 1) - - private fun onTimeout() { - try { - PlayerManager.letRemainingRoleGroupWin() - } catch (e: IllegalStateException) {} - - stop() - } - - fun stop() { - isc.remove(tttPlayer) - task.cancel() - tttPlayer.player.closeInventory() - tttPlayer.removeItem(SecondChance) - bossBar.removePlayer(tttPlayer.player) - } - } - - class State: IState { - var timeoutAction: TimeoutAction? = null + fun onTTTPlayerRevive(event: TTTPlayerReviveEvent) = handle(event) { it.timeoutAction?.stop() } } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Teleporter.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Teleporter.kt index 60eadcd..f98daf6 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Teleporter.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/Teleporter.kt @@ -2,24 +2,23 @@ package de.moritzruth.spigot_ttt.game.items.impl import de.moritzruth.spigot_ttt.Resourcepack import de.moritzruth.spigot_ttt.game.GameManager -import de.moritzruth.spigot_ttt.game.items.Buyable +import de.moritzruth.spigot_ttt.game.items.ClickEvent import de.moritzruth.spigot_ttt.game.items.TTTItem -import de.moritzruth.spigot_ttt.game.items.TTTItemListener -import de.moritzruth.spigot_ttt.game.players.* +import de.moritzruth.spigot_ttt.game.players.PlayerManager +import de.moritzruth.spigot_ttt.game.players.Role +import de.moritzruth.spigot_ttt.game.players.TTTPlayer +import de.moritzruth.spigot_ttt.game.players.roles import de.moritzruth.spigot_ttt.utils.applyMeta -import de.moritzruth.spigot_ttt.utils.clearHeldItemSlot import de.moritzruth.spigot_ttt.utils.sendActionBarMessage import org.bukkit.ChatColor import org.bukkit.Sound -import org.bukkit.event.EventHandler -import org.bukkit.event.player.PlayerSwapHandItemsEvent import org.bukkit.inventory.ItemStack -object Teleporter: TTTItem, Buyable { - override val type = TTTItem.Type.SPECIAL - override val itemStack = ItemStack(Resourcepack.Items.teleporter).applyMeta { +object Teleporter: TTTItem( + type = Type.SPECIAL, + instanceType = Instance::class, + templateItemStack = ItemStack(Resourcepack.Items.teleporter).applyMeta { setDisplayName("${ChatColor.LIGHT_PURPLE}${ChatColor.BOLD}Teleporter") - lore = listOf( "", "${ChatColor.GOLD}Tausche die Positionen zweier Spieler", @@ -28,42 +27,28 @@ object Teleporter: TTTItem, Buyable { "", "${ChatColor.RED}Kann nur einmal verwendet werden" ) - } - override val buyableBy = roles(Role.TRAITOR, Role.JACKAL) - override val price = 1 - override val buyLimit = 1 - val isc = InversedStateContainer(State::class) + }, + shopInfo = ShopInfo( + buyableBy = roles(Role.TRAITOR, Role.JACKAL), + price = 1, + buyLimit = 1 + ) +) { + class Instance: TTTItem.Instance(Teleporter) { + private var teleportSelf = false - private fun getRandomPlayerToTeleport(vararg exclude: TTTPlayer): TTTPlayer? { - return try { - PlayerManager.tttPlayers.filter { !exclude.contains(it) && it.alive && !it.player.isSneaking }.random() - } catch (e: NoSuchElementException) { - null - } - } - - override val listener = object : TTTItemListener(this, true) { - @EventHandler - fun onPlayerSwapHandItems(event: PlayerSwapHandItemsEvent) = handle(event) { tttPlayer -> - val state = isc.getOrCreate(tttPlayer) - state.teleportSelf = !state.teleportSelf - - tttPlayer.player.sendActionBarMessage( - if (state.teleportSelf) "${ChatColor.AQUA}Mode: Teleportiere dich selbst" - else "${ChatColor.AQUA}Mode: Teleportiere jemand anderen" - ) - } - - override fun onRightClick(data: ClickEventData) { - val tttPlayer = data.tttPlayer - val state = isc.getOrCreate(tttPlayer) - - val firstPlayer = if (state.teleportSelf) { + override fun onRightClick(event: ClickEvent) { + val tttPlayer = carrier!! + val firstPlayer = if (teleportSelf) { if (!tttPlayer.player.isOnGround) { - tttPlayer.player.sendActionBarMessage("${ChatColor.RED}${ChatColor.BOLD}Du musst auf dem Boden stehen") + tttPlayer.player.sendActionBarMessage( + "${ChatColor.RED}${ChatColor.BOLD}Du musst auf dem Boden stehen" + ) null } else if (tttPlayer.player.isSneaking) { - tttPlayer.player.sendActionBarMessage("${ChatColor.RED}${ChatColor.BOLD}Du darfst nicht sneaken") + tttPlayer.player.sendActionBarMessage( + "${ChatColor.RED}${ChatColor.BOLD}Du darfst nicht sneaken" + ) null } else tttPlayer } else getRandomPlayerToTeleport(tttPlayer) @@ -76,7 +61,7 @@ object Teleporter: TTTItem, Buyable { firstPlayer.player.teleport(secondPlayer.player.location) secondPlayer.player.teleport(firstLocation) - tttPlayer.player.inventory.clearHeldItemSlot() + tttPlayer.removeItem(Teleporter) GameManager.world.playSound(firstPlayer.player.location, Sound.ENTITY_ENDERMAN_TELEPORT, 1F, 1F) GameManager.world.playSound(secondPlayer.player.location, Sound.ENTITY_ENDERMAN_TELEPORT, 1F, 1F) @@ -87,9 +72,22 @@ object Teleporter: TTTItem, Buyable { // Teleport failed tttPlayer.player.playSound(tttPlayer.player.location, Resourcepack.Sounds.error, 1F, 1F) } + + override fun onHandSwap() { + teleportSelf = !teleportSelf + + carrier!!.player.sendActionBarMessage( + if (teleportSelf) "${ChatColor.AQUA}Mode: Teleportiere dich selbst" + else "${ChatColor.AQUA}Mode: Teleportiere jemand anderen" + ) + } } - class State: IState { - var teleportSelf = false + private fun getRandomPlayerToTeleport(vararg exclude: TTTPlayer): TTTPlayer? { + return try { + PlayerManager.tttPlayers.filter { !exclude.contains(it) && it.alive && !it.player.isSneaking }.random() + } catch (e: NoSuchElementException) { + null + } } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/BaseballBat.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/BaseballBat.kt index 969d408..132bcae 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/BaseballBat.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/BaseballBat.kt @@ -1,17 +1,13 @@ package de.moritzruth.spigot_ttt.game.items.impl.weapons import de.moritzruth.spigot_ttt.Resourcepack -import de.moritzruth.spigot_ttt.game.items.TTTItemListener import de.moritzruth.spigot_ttt.game.GameManager -import de.moritzruth.spigot_ttt.game.players.Role -import de.moritzruth.spigot_ttt.game.players.TTTPlayer -import de.moritzruth.spigot_ttt.game.players.roles -import de.moritzruth.spigot_ttt.game.items.Buyable -import de.moritzruth.spigot_ttt.game.items.Selectable import de.moritzruth.spigot_ttt.game.items.TTTItem +import de.moritzruth.spigot_ttt.game.items.TTTItemListener +import de.moritzruth.spigot_ttt.game.players.Role +import de.moritzruth.spigot_ttt.game.players.roles import de.moritzruth.spigot_ttt.utils.applyMeta import de.moritzruth.spigot_ttt.utils.hideInfo -import de.moritzruth.spigot_ttt.utils.removeTTTItemNextTick import org.bukkit.ChatColor import org.bukkit.SoundCategory import org.bukkit.attribute.Attribute @@ -22,9 +18,10 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent import org.bukkit.inventory.ItemStack import org.bukkit.util.Vector -object BaseballBat: TTTItem, Buyable, Selectable { - override val type = TTTItem.Type.MELEE - override val itemStack = ItemStack(Resourcepack.Items.baseballBat).applyMeta { +object BaseballBat: TTTItem( + type = Type.SPECIAL, + instanceType = Instance::class, + templateItemStack = ItemStack(Resourcepack.Items.baseballBat).applyMeta { setDisplayName("${ChatColor.RESET}${ChatColor.BOLD}Baseball-Schläger") lore = listOf( "", @@ -41,22 +38,28 @@ object BaseballBat: TTTItem, Buyable, Selectable { -0.8, AttributeModifier.Operation.ADD_SCALAR )) - } - override val buyableBy = roles(Role.TRAITOR, Role.JACKAL) - override val price = 1 - override val buyLimit: Int? = null + }, + shopInfo = ShopInfo( + buyableBy = roles(Role.TRAITOR, Role.JACKAL), + price = 1 + ), + disableDamage = false +) { + const val WALK_SPEED_INCREASE = 0.1F - override fun onSelect(tttPlayer: TTTPlayer) { - tttPlayer.player.walkSpeed = 0.3F + class Instance: TTTItem.Instance(BaseballBat) { + override fun onSelect() { + carrier!!.walkSpeed += WALK_SPEED_INCREASE + } + + override fun onDeselect() { + carrier!!.walkSpeed -= WALK_SPEED_INCREASE + } } - override fun onDeselect(tttPlayer: TTTPlayer) { - tttPlayer.player.walkSpeed = 0.2F - } - - override val listener = object : TTTItemListener(this, false) { + override val listener = object : TTTItemListener(this) { @EventHandler(ignoreCancelled = true) - override fun onEntityDamageByEntity(event: EntityDamageByEntityEvent) = handle(event) { tttPlayer, _ -> + fun onEntityDamageByEntity(event: EntityDamageByEntityEvent) = handle(event) { tttPlayer, _ -> event.isCancelled = true if (event.damage != 1.0) return@handle // Cooldown on weapon @@ -64,7 +67,7 @@ object BaseballBat: TTTItem, Buyable, Selectable { val distance = tttPlayer.player.location.distance(damagedPlayer.location) if (distance < 2.5) { - tttPlayer.player.inventory.removeTTTItemNextTick(BaseballBat) + tttPlayer.removeItem(BaseballBat) GameManager.world.playSound( damagedPlayer.location, @@ -74,6 +77,8 @@ object BaseballBat: TTTItem, Buyable, Selectable { 1F ) + event.damage = 0.0 + val direction = tttPlayer.player.location.direction damagedPlayer.velocity = Vector(direction.x * 5, 8.0, direction.z * 5) } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/Knife.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/Knife.kt index c3502c5..f79f1cf 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/Knife.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/Knife.kt @@ -2,7 +2,6 @@ package de.moritzruth.spigot_ttt.game.items.impl.weapons import de.moritzruth.spigot_ttt.Resourcepack import de.moritzruth.spigot_ttt.game.GameManager -import de.moritzruth.spigot_ttt.game.items.Buyable import de.moritzruth.spigot_ttt.game.items.LoreHelper import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.game.items.TTTItemListener @@ -11,7 +10,6 @@ import de.moritzruth.spigot_ttt.game.players.Role import de.moritzruth.spigot_ttt.game.players.roles import de.moritzruth.spigot_ttt.utils.applyMeta import de.moritzruth.spigot_ttt.utils.hideInfo -import de.moritzruth.spigot_ttt.utils.removeTTTItemNextTick import org.bukkit.ChatColor import org.bukkit.Sound import org.bukkit.SoundCategory @@ -21,8 +19,10 @@ import org.bukkit.event.EventHandler import org.bukkit.event.entity.EntityDamageByEntityEvent import org.bukkit.inventory.ItemStack -object Knife: TTTItem, Buyable { - override val itemStack = ItemStack(Resourcepack.Items.knife).applyMeta { +object Knife: TTTItem( + type = Type.MELEE, + instanceType = Instance::class, + templateItemStack = ItemStack(Resourcepack.Items.knife).applyMeta { setDisplayName("${ChatColor.RED}${ChatColor.BOLD}Knife") lore = listOf( "", @@ -31,23 +31,26 @@ object Knife: TTTItem, Buyable { "${ChatColor.RED}Nur einmal verwendbar", "${ChatColor.RED}Nur aus nächster Nähe" ) - hideInfo() addAttributeModifier( Attribute.GENERIC_ATTACK_SPEED, AttributeModifier( - "_", - -0.9, - AttributeModifier.Operation.ADD_SCALAR - )) - } - override val buyableBy = roles(Role.TRAITOR, Role.JACKAL) - override val price = 1 - override val type = TTTItem.Type.MELEE - override val buyLimit = 1 + "_", + -0.9, + AttributeModifier.Operation.ADD_SCALAR + )) + }, + shopInfo = ShopInfo( + buyableBy = roles(Role.TRAITOR, Role.JACKAL), + price = 1, + buyLimit = 1 + ), + disableDamage = false +) { + class Instance: TTTItem.Instance(Knife) - override val listener = object : TTTItemListener(this, false) { + override val listener = object : TTTItemListener(this) { @EventHandler(ignoreCancelled = true) - override fun onEntityDamageByEntity(event: EntityDamageByEntityEvent) = handle(event) { damagerTTTPlayer, damagedTTTPlayer -> + fun onEntityDamageByEntity(event: EntityDamageByEntityEvent) = handle(event) { damagerTTTPlayer, damagedTTTPlayer -> event.isCancelled = true if (event.damage == 1.0) { @@ -77,7 +80,7 @@ object Knife: TTTItem, Buyable { 1F ) - damagerTTTPlayer.player.inventory.removeTTTItemNextTick(Knife) + damagerTTTPlayer.removeItem(Knife) } } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Deagle.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Deagle.kt index c8cf13e..5258deb 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Deagle.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Deagle.kt @@ -1,25 +1,24 @@ package de.moritzruth.spigot_ttt.game.items.impl.weapons.guns import de.moritzruth.spigot_ttt.Resourcepack -import de.moritzruth.spigot_ttt.game.items.Spawning -import de.moritzruth.spigot_ttt.game.items.TTTItem +import de.moritzruth.spigot_ttt.utils.Probability import de.moritzruth.spigot_ttt.utils.heartsToHealth import org.bukkit.ChatColor object Deagle: Gun( - stateClass = State::class, + type = Type.PISTOL_LIKE, + instanceType = Instance::class, + spawnProbability = Probability.NORMAL, displayName = "${ChatColor.BLUE}${ChatColor.BOLD}Deagle", damage = heartsToHealth(3.0), cooldown = 1.4, magazineSize = 8, reloadTime = 3.0, - itemMaterial = Resourcepack.Items.deagle, + material = Resourcepack.Items.deagle, shootSound = Resourcepack.Sounds.Item.Weapon.Deagle.fire, reloadSound = Resourcepack.Sounds.Item.Weapon.Deagle.reload -), Spawning { - override val type = TTTItem.Type.PISTOL_LIKE - - class State: Gun.State(magazineSize) +) { + class Instance: Gun.Instance(Deagle) } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Glock.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Glock.kt index 2dc3184..c3905dd 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Glock.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Glock.kt @@ -1,25 +1,24 @@ package de.moritzruth.spigot_ttt.game.items.impl.weapons.guns import de.moritzruth.spigot_ttt.Resourcepack -import de.moritzruth.spigot_ttt.game.items.Spawning -import de.moritzruth.spigot_ttt.game.items.TTTItem +import de.moritzruth.spigot_ttt.utils.Probability import de.moritzruth.spigot_ttt.utils.heartsToHealth import org.bukkit.ChatColor object Glock: Gun( - stateClass = State::class, + type = Type.PISTOL_LIKE, + instanceType = Instance::class, displayName = "${ChatColor.YELLOW}${ChatColor.BOLD}Glock", damage = heartsToHealth(1.5), + spawnProbability = Probability.NORMAL, cooldown = 0.3, magazineSize = 20, reloadTime = 2.0, - itemMaterial = Resourcepack.Items.glock, + material = Resourcepack.Items.glock, shootSound = Resourcepack.Sounds.Item.Weapon.Glock.fire, reloadSound = Resourcepack.Sounds.Item.Weapon.Glock.reload -), Spawning { - override val type = TTTItem.Type.PISTOL_LIKE - - class State: Gun.State(magazineSize) +) { + class Instance: Gun.Instance(Glock) } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Gun.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Gun.kt index acc1ae3..19c9dc5 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Gun.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Gun.kt @@ -1,89 +1,99 @@ package de.moritzruth.spigot_ttt.game.items.impl.weapons.guns import de.moritzruth.spigot_ttt.Resourcepack -import de.moritzruth.spigot_ttt.game.GameEndEvent import de.moritzruth.spigot_ttt.game.GameManager -import de.moritzruth.spigot_ttt.game.GamePhase -import de.moritzruth.spigot_ttt.game.items.* -import de.moritzruth.spigot_ttt.game.players.* +import de.moritzruth.spigot_ttt.game.items.ClickEvent +import de.moritzruth.spigot_ttt.game.items.LoreHelper +import de.moritzruth.spigot_ttt.game.items.TTTItem +import de.moritzruth.spigot_ttt.game.players.DeathReason +import de.moritzruth.spigot_ttt.game.players.TTTPlayer +import de.moritzruth.spigot_ttt.utils.Probability import de.moritzruth.spigot_ttt.utils.applyMeta -import de.moritzruth.spigot_ttt.utils.nextTick -import de.moritzruth.spigot_ttt.utils.startItemDamageProgress +import de.moritzruth.spigot_ttt.utils.hideInfo +import de.moritzruth.spigot_ttt.utils.startProgressTask import org.bukkit.* -import org.bukkit.entity.Item import org.bukkit.entity.Player -import org.bukkit.event.EventHandler -import org.bukkit.inventory.ItemFlag import org.bukkit.inventory.ItemStack -import org.bukkit.inventory.meta.Damageable -import org.bukkit.inventory.meta.ItemMeta import org.bukkit.scheduler.BukkitTask -import java.time.Duration import java.time.Instant import kotlin.reflect.KClass -typealias ClickAction = org.bukkit.event.block.Action - abstract class Gun( - private val stateClass: KClass, + type: Type, + instanceType: KClass, + spawnProbability: Probability? = null, + shopInfo: ShopInfo? = null, + material: Material, displayName: String, - additionalLore: List? = null, + itemLore: List? = null, + appendLore: Boolean = true, val damage: Double, val cooldown: Double, val magazineSize: Int, val reloadTime: Double, - val itemMaterial: Material, val shootSound: String, val reloadSound: String -): TTTItem, Selectable, DropHandler { - override val itemStack = ItemStack(itemMaterial).applyMeta { +): TTTItem( + type = type, + instanceType = instanceType, + spawnProbability = spawnProbability, + shopInfo = shopInfo, + templateItemStack = ItemStack(material).applyMeta { setDisplayName(displayName) - lore = listOf( - "", - "${ChatColor.GRAY}Schaden: ${LoreHelper.damage(if (damage < 0) null else (damage / 2))}", - "${ChatColor.GRAY}Cooldown: ${LoreHelper.cooldown(cooldown)}", - "${ChatColor.GRAY}Magazin: ${LoreHelper.uses(magazineSize)} Schuss" - ) + run { - if (additionalLore == null) emptyList() - else listOf("") + additionalLore + lore = + if (appendLore) listOf( + "", + "${ChatColor.GRAY}Schaden: ${LoreHelper.damage(if (damage < 0) null else (damage / 2))}", + "${ChatColor.GRAY}Cooldown: ${LoreHelper.cooldown(cooldown)}", + "${ChatColor.GRAY}Magazin: ${LoreHelper.uses(magazineSize)} Schuss" + ) + run { + if (itemLore == null) emptyList() + else listOf("") + itemLore + } + else itemLore ?: emptyList() + + hideInfo() + } +) { + open class Instance(val gun: Gun): TTTItem.Instance(gun) { + var currentAction: Action? = null + var remainingShots: Int = gun.magazineSize + set(value) { + field = value + setCarrierLevel() + } + + private fun setCarrierLevel() { + if (isSelected) carrier!!.player.level = remainingShots } - addItemFlags(ItemFlag.HIDE_ATTRIBUTES) - } + private fun shoot() { + val tttPlayer = requireCarrier() + if (!onBeforeShoot()) return - val isc = InversedStateContainer(stateClass) + if (remainingShots == 0) { + GameManager.world.playSound( + tttPlayer.player.location, + Resourcepack.Sounds.Item.Weapon.Generic.emptyMagazine, + SoundCategory.PLAYERS, + 1F, + 1F + ) - protected fun updateLevel(tttPlayer: TTTPlayer, state: State = isc.getOrCreate(tttPlayer)) { - tttPlayer.player.level = state.remainingShots - } + return + } - fun shoot(tttPlayer: TTTPlayer, itemStack: ItemStack, state: State = isc.getOrCreate(tttPlayer)) { - if (!onBeforeShoot(tttPlayer, itemStack, state)) return + GameManager.world.playSound(tttPlayer.player.location, gun.shootSound, SoundCategory.PLAYERS, 1F, 1F) - if (state.remainingShots == 0) { - GameManager.world.playSound( - tttPlayer.player.location, - Resourcepack.Sounds.Item.Weapon.Generic.emptyMagazine, - SoundCategory.PLAYERS, - 1F, - 1F - ) - return - } + remainingShots-- - GameManager.world.playSound(tttPlayer.player.location, shootSound, SoundCategory.PLAYERS, 1F, 1F) - - state.remainingShots-- - updateLevel(tttPlayer) - - if (GameManager.phase == GamePhase.COMBAT) { val rayTraceResult = GameManager.world.rayTrace( - tttPlayer.player.eyeLocation, - tttPlayer.player.eyeLocation.direction, - 200.0, - FluidCollisionMode.ALWAYS, - true, - 0.01 + tttPlayer.player.eyeLocation, + tttPlayer.player.eyeLocation.direction, + 200.0, + FluidCollisionMode.ALWAYS, + true, + 0.01 ) { it !== tttPlayer.player } if (rayTraceResult !== null) { @@ -100,167 +110,124 @@ abstract class Gun( } } } + + currentAction = Action.Cooldown(this) } - state.currentAction = Action.Cooldown(this, itemStack, state) - } + open fun reload() { + val carrier = requireCarrier() + if (currentAction != null) throw ActionInProgressError() + if (remainingShots == gun.magazineSize) return - open fun reload(tttPlayer: TTTPlayer, itemStack: ItemStack, state: State = isc.getOrCreate(tttPlayer)) { - if (state.currentAction != null) throw ActionInProgressError() - if (state.remainingShots == magazineSize) return + currentAction = Action.Reloading(this) - state.currentAction = Action.Reloading(this, itemStack, state, tttPlayer).also { it.start() } - - GameManager.world.playSound(tttPlayer.player.location, reloadSound, SoundCategory.PLAYERS, 1F, 1F) - } - - open fun computeActualDamage(tttPlayer: TTTPlayer, receiver: Player) = if (damage < 0 ) 1000.0 else damage - - open fun onBeforeShoot(tttPlayer: TTTPlayer, item: ItemStack, state: State = isc.getOrCreate(tttPlayer)): Boolean { - if (state.currentAction !== null) throw ActionInProgressError() - return true - } - - open fun onHit(tttPlayer: TTTPlayer, hitTTTPlayer: TTTPlayer) { - val actualDamage = computeActualDamage(tttPlayer, hitTTTPlayer.player) - - hitTTTPlayer.damage(actualDamage, DeathReason.Item(this), tttPlayer, true) - tttPlayer.player.playSound(tttPlayer.player.location, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, SoundCategory.MASTER, 2f, 1.2f) - hitTTTPlayer.player.velocity = tttPlayer.player.location.direction.multiply( - (actualDamage / 20).coerceAtMost(3.0) - ) - } - - override fun onSelect(tttPlayer: TTTPlayer) { - updateLevel(tttPlayer) - } - - override fun onDeselect(tttPlayer: TTTPlayer) { - tttPlayer.player.level = 0 - - val state = isc.get(tttPlayer) ?: return - val currentAction = state.currentAction - - if (currentAction is Action.Reloading) { - state.currentAction = null - currentAction.task.cancel() - - val meta = currentAction.itemStack.itemMeta as Damageable - meta.damage = 0 - currentAction.itemStack.itemMeta = meta as ItemMeta - } - } - - override fun onDrop(tttPlayer: TTTPlayer, itemEntity: Item): Boolean { - val state = isc.get(tttPlayer) ?: return true - - when(val currentAction = state.currentAction) { - is Action.Reloading -> { - state.currentAction = null - currentAction.task.cancel() - } - is Action.Cooldown -> { - currentAction.pause() - } + GameManager.world.playSound( + carrier.player.location, + gun.reloadSound, + SoundCategory.PLAYERS, + 1F, + 1F + ) } - itemEntity.setItemStack(itemStack.clone()) - - ItemManager.droppedItemStates[itemEntity.entityId] = state - isc.remove(tttPlayer) - return true - } - - override fun onPickup(tttPlayer: TTTPlayer, itemEntity: Item) { - val state = ItemManager.droppedItemStates[itemEntity.entityId] as State? - - if (state != null) { - tttPlayer.stateContainer.put(stateClass, state) - val currentAction = state.currentAction ?: return - - nextTick { currentAction.itemStack = tttPlayer.player.inventory.find { it.type == itemEntity.itemStack.type }!! - - if (currentAction is Action.Cooldown) { - currentAction.resume() - } } + open fun computeActualDamage(receiver: TTTPlayer): Double { + requireCarrier() // Only to keep parity with possible override + return if (gun.damage < 0 ) 1000.0 else gun.damage } - } - override val listener = object : TTTItemListener(this, true) { - override fun onLeftClick(data: ClickEventData) { + /** + * @return Whether the gun will really shoot + */ + open fun onBeforeShoot(): Boolean { + if (currentAction !== null) throw ActionInProgressError() + return true + } + + open fun onHit(tttPlayer: TTTPlayer, hitTTTPlayer: TTTPlayer) { + val actualDamage = computeActualDamage(hitTTTPlayer) + hitTTTPlayer.damage(actualDamage, DeathReason.Item(gun), tttPlayer, true) + tttPlayer.player.playSound( + tttPlayer.player.location, + Sound.ENTITY_EXPERIENCE_ORB_PICKUP, + SoundCategory.MASTER, + 2f, + 1.2f + ) + hitTTTPlayer.player.velocity = tttPlayer.player.location.direction.multiply( + (actualDamage / 20).coerceAtMost(3.0) + ) + } + + override fun onLeftClick(event: ClickEvent) { try { - reload(data.tttPlayer, data.event.item!!) + reload() } catch (e: ActionInProgressError) {} } - override fun onRightClick(data: ClickEventData) { + override fun onRightClick(event: ClickEvent) { try { - shoot(data.tttPlayer, data.event.item!!) + shoot() } catch (e: ActionInProgressError) {} } - @EventHandler - fun onTTTPlayerDeath(event: TTTPlayerTrueDeathEvent) = isc.get(event.tttPlayer)?.reset() + protected open fun onMovedOutOfHand(tttPlayer: TTTPlayer) { + tttPlayer.player.level = 0 + tttPlayer.player.exp = 0F - @EventHandler - fun onGameEnd(event: GameEndEvent) = isc.forEveryState { state, _ -> state.reset() } + val action = currentAction + if (action is Action.Reloading) { + currentAction = null + action.cancel() + } + } + + override fun onCarrierSet(carrier: TTTPlayer, isFirst: Boolean) { + setCarrierLevel() + } + + override fun onSelect() { + setCarrierLevel() + } + + override fun onCarrierRemoved(oldCarrier: TTTPlayer) { + onMovedOutOfHand(oldCarrier) + } + + override fun onDeselect() { + onMovedOutOfHand(carrier!!) + } } - class ActionInProgressError: RuntimeException("The gun has an ongoing action which may not be canceled") + class ActionInProgressError: RuntimeException("The gun has an ongoing action") - abstract class State(magazineSize: Int): IState { - var currentAction: Action? = null - var remainingShots = magazineSize - - fun reset() { currentAction?.reset() } - } - - sealed class Action(var itemStack: ItemStack) { + sealed class Action(val instance: Instance) { val startedAt = Instant.now()!! - abstract var task: BukkitTask; protected set + abstract val task: BukkitTask - open fun reset() { + open fun cancel() { task.cancel() } - open class Reloading( - private val gun: Gun, - itemStack: ItemStack, - protected val state: State, - protected val tttPlayer: TTTPlayer - ): Action(itemStack) { - override lateinit var task: BukkitTask + open class Reloading(instance: Instance): Action(instance) { + override val task = createProgressTask() - open fun start() { - task = startItemDamageProgress(itemStack, gun.reloadTime) { - state.currentAction = null - state.remainingShots = gun.magazineSize - gun.updateLevel(tttPlayer, state) - } + protected open fun createProgressTask() = startProgressTask(instance.gun.reloadTime) { data -> + val exp = if (data.isComplete) { + instance.remainingShots = instance.gun.magazineSize + instance.currentAction = null + 0F + } else data.progress.toFloat() + if (instance.isSelected) instance.carrier!!.player.exp = exp } } - class Cooldown(private val gun: Gun, itemStack: ItemStack, private val state: State): Action(itemStack) { - override var task = startTask() - private var pausedProgress: Double? = null - - private fun startTask() = startItemDamageProgress( - itemStack = itemStack, - duration = gun.cooldown, - startProgress = pausedProgress ?: 0.0 - ) { - state.currentAction = null - } - - fun resume() { - if (task.isCancelled) task = startTask() - } - - fun pause() { - if (!task.isCancelled) { - task.cancel() - pausedProgress = (Duration.between(startedAt, Instant.now()).toMillis().toDouble() / 1000) / gun.cooldown - } + class Cooldown(instance: Instance): Action(instance) { + override val task = startProgressTask(instance.gun.cooldown) { data -> + val exp = if (data.isComplete) { + instance.currentAction = null + 0F + } else data.progress.toFloat() + if (instance.isSelected) instance.carrier!!.player.exp = exp } } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Pistol.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Pistol.kt index 9a1bc32..88ca791 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Pistol.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Pistol.kt @@ -1,25 +1,24 @@ package de.moritzruth.spigot_ttt.game.items.impl.weapons.guns import de.moritzruth.spigot_ttt.Resourcepack -import de.moritzruth.spigot_ttt.game.items.Spawning -import de.moritzruth.spigot_ttt.game.items.TTTItem +import de.moritzruth.spigot_ttt.utils.Probability import de.moritzruth.spigot_ttt.utils.heartsToHealth import org.bukkit.ChatColor object Pistol: Gun( - stateClass = State::class, + type = Type.PISTOL_LIKE, + instanceType = Instance::class, + spawnProbability = Probability.NORMAL, displayName = "${ChatColor.YELLOW}${ChatColor.BOLD}Pistol", damage = heartsToHealth(2.5), cooldown = 0.8, magazineSize = 10, reloadTime = 2.0, - itemMaterial = Resourcepack.Items.pistol, + material = Resourcepack.Items.pistol, shootSound = Resourcepack.Sounds.Item.Weapon.Pistol.fire, reloadSound = Resourcepack.Sounds.Item.Weapon.Pistol.reload -), Spawning { - override val type = TTTItem.Type.PISTOL_LIKE - - class State: Gun.State(magazineSize) +) { + class Instance: Gun.Instance(Pistol) } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Rifle.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Rifle.kt index d7cdff5..e2aa73e 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Rifle.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Rifle.kt @@ -1,25 +1,24 @@ package de.moritzruth.spigot_ttt.game.items.impl.weapons.guns import de.moritzruth.spigot_ttt.Resourcepack -import de.moritzruth.spigot_ttt.game.items.Spawning -import de.moritzruth.spigot_ttt.game.items.TTTItem +import de.moritzruth.spigot_ttt.utils.Probability import de.moritzruth.spigot_ttt.utils.heartsToHealth import org.bukkit.ChatColor object Rifle: Gun( - stateClass = State::class, + type = Type.HEAVY_WEAPON, + instanceType = Instance::class, + spawnProbability = Probability.NORMAL, displayName = "${ChatColor.YELLOW}${ChatColor.BOLD}Rifle", damage = heartsToHealth(0.8), cooldown = 0.15, magazineSize = 40, reloadTime = 2.0, - itemMaterial = Resourcepack.Items.rifle, + material = Resourcepack.Items.rifle, shootSound = Resourcepack.Sounds.Item.Weapon.Rifle.fire, reloadSound = Resourcepack.Sounds.Item.Weapon.Rifle.reload -), Spawning { - override val type = TTTItem.Type.HEAVY_WEAPON - - class State: Gun.State(magazineSize) +) { + class Instance: Gun.Instance(Rifle) } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Shotgun.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Shotgun.kt index baa9d9d..667f6ca 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Shotgun.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/Shotgun.kt @@ -2,122 +2,99 @@ package de.moritzruth.spigot_ttt.game.items.impl.weapons.guns import de.moritzruth.spigot_ttt.Resourcepack import de.moritzruth.spigot_ttt.game.GameManager -import de.moritzruth.spigot_ttt.game.items.Spawning -import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.game.players.TTTPlayer import de.moritzruth.spigot_ttt.plugin +import de.moritzruth.spigot_ttt.utils.Probability import de.moritzruth.spigot_ttt.utils.heartsToHealth import de.moritzruth.spigot_ttt.utils.secondsToTicks -import de.moritzruth.spigot_ttt.utils.startItemDamageProgress +import de.moritzruth.spigot_ttt.utils.startProgressTask import org.bukkit.ChatColor import org.bukkit.SoundCategory -import org.bukkit.entity.Player -import org.bukkit.inventory.ItemStack -import org.bukkit.inventory.meta.Damageable -import org.bukkit.inventory.meta.ItemMeta import org.bukkit.scheduler.BukkitTask private const val RELOAD_TIME_PER_BULLET = 0.5 private const val MAGAZINE_SIZE = 8 object Shotgun: Gun( - stateClass = State::class, + type = Type.HEAVY_WEAPON, + instanceType = Instance::class, + spawnProbability = Probability.LOW, displayName = "${ChatColor.YELLOW}${ChatColor.BOLD}Shotgun", damage = heartsToHealth(3.0), cooldown = 0.9, magazineSize = MAGAZINE_SIZE, reloadTime = RELOAD_TIME_PER_BULLET * MAGAZINE_SIZE, - itemMaterial = Resourcepack.Items.shotgun, - additionalLore = listOf("${ChatColor.RED}Weniger Schaden auf Distanz"), + material = Resourcepack.Items.shotgun, + itemLore = listOf("${ChatColor.RED}Weniger Schaden auf Distanz"), shootSound = Resourcepack.Sounds.Item.Weapon.Shotgun.fire, reloadSound = Resourcepack.Sounds.Item.Weapon.Shotgun.reload -), Spawning { - override val type = TTTItem.Type.HEAVY_WEAPON +) { + class Instance: Gun.Instance(Shotgun) { + override fun computeActualDamage(receiver: TTTPlayer): Double { + val distance = requireCarrier().player.location.distance(receiver.player.location) - override fun computeActualDamage(tttPlayer: TTTPlayer, receiver: Player): Double { - val distance = tttPlayer.player.location.distance(receiver.location) - - return when { - distance <= 1 -> heartsToHealth(10.0) - distance >= 14 -> 0.5 - distance > 8 -> heartsToHealth(1.5) - else -> heartsToHealth(damage) - } - } - - override fun onDeselect(tttPlayer: TTTPlayer) { - tttPlayer.player.level = 0 - val state = (isc.get(tttPlayer) ?: return) as State - - val currentAction = state.currentAction - - if (currentAction is ReloadingAction) { - state.currentAction = null - currentAction.task.cancel() - currentAction.updateTask.cancel() - - val meta = currentAction.itemStack.itemMeta as Damageable - meta.damage = 0 - currentAction.itemStack.itemMeta = meta as ItemMeta - } - } - - override fun reload(tttPlayer: TTTPlayer, itemStack: ItemStack, state: Gun.State) { - val ownState = state as State - if (ownState.currentAction != null) throw ActionInProgressError() - if (ownState.remainingShots == magazineSize) return - - ownState.currentAction = ReloadingAction(itemStack, ownState, tttPlayer).also { it.start() } - } - - override fun onBeforeShoot(tttPlayer: TTTPlayer, item: ItemStack, state: Gun.State): Boolean { - val ownState = state as State - if (ownState.remainingShots == 0) return true - - when(val currentAction = ownState.currentAction) { - is Action.Cooldown -> throw ActionInProgressError() - is ReloadingAction -> { - currentAction.reset() - ownState.currentAction = null - - val damageMeta = item.itemMeta!! as Damageable - damageMeta.damage = 0 - item.itemMeta = damageMeta as ItemMeta + return when { + distance <= 1 -> heartsToHealth(10.0) + distance >= 14 -> 0.5 + distance > 8 -> heartsToHealth(1.5) + else -> heartsToHealth(damage) } } - return true - } + override fun onMovedOutOfHand(tttPlayer: TTTPlayer) { + tttPlayer.player.level = 0 + tttPlayer.player.exp = 0F - class State: Gun.State(magazineSize) - - private class ReloadingAction(itemStack: ItemStack, state: State, tttPlayer: TTTPlayer): Action.Reloading(Shotgun, itemStack, state, tttPlayer) { - lateinit var updateTask: BukkitTask - - override fun reset() { - task.cancel() - updateTask.cancel() + val action = currentAction + if (action is ReloadingAction) { + currentAction = null + action.cancel() + } } - override fun start() { - task = startItemDamageProgress( - itemStack, - reloadTime, - state.remainingShots.toDouble() / magazineSize - ) { state.currentAction = null } + override fun reload() { + if (currentAction != null) throw ActionInProgressError() + if (remainingShots == magazineSize) return + currentAction = ReloadingAction(this) + } + override fun onBeforeShoot(): Boolean { + if (remainingShots == 0) return true + + when(val action = currentAction) { + is Action.Cooldown -> throw ActionInProgressError() + is ReloadingAction -> action.cancel() + } + + return true + } + } + + private class ReloadingAction(instance: Instance): Action.Reloading(instance) { + override fun createProgressTask(): BukkitTask = startProgressTask( + instance.gun.reloadTime, + startAt = instance.remainingShots.toDouble() / instance.gun.magazineSize + ) { data -> + val exp = if (data.isComplete) { + instance.currentAction = null + 0F + } else data.progress.toFloat() + if (instance.isSelected) instance.carrier!!.player.exp = exp + } + + private var updateTask: BukkitTask? = null + + init { updateTask = plugin.server.scheduler.runTaskTimer(plugin, fun() { - state.remainingShots++ - updateLevel(tttPlayer) + instance.remainingShots++ + GameManager.world.playSound(instance.carrier!!.player.location, reloadSound, SoundCategory.PLAYERS, 1F, 1F) + if (instance.remainingShots == magazineSize) updateTask?.cancel() + }, secondsToTicks(RELOAD_TIME_PER_BULLET).toLong(), secondsToTicks(RELOAD_TIME_PER_BULLET).toLong()) + } - GameManager.world.playSound(tttPlayer.player.location, reloadSound, SoundCategory.PLAYERS, 1F, 1F) - - if (state.remainingShots == magazineSize) { - this.updateTask.cancel() - } - }, - secondsToTicks(RELOAD_TIME_PER_BULLET).toLong(), - secondsToTicks(RELOAD_TIME_PER_BULLET).toLong()) + override fun cancel() { + task.cancel() + updateTask?.cancel() } } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/SidekickDeagle.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/SidekickDeagle.kt index d06c905..38f18a3 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/SidekickDeagle.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/impl/weapons/guns/SidekickDeagle.kt @@ -1,74 +1,55 @@ package de.moritzruth.spigot_ttt.game.items.impl.weapons.guns import de.moritzruth.spigot_ttt.Resourcepack -import de.moritzruth.spigot_ttt.game.items.Buyable -import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.game.players.Role import de.moritzruth.spigot_ttt.game.players.TTTPlayer import de.moritzruth.spigot_ttt.game.players.roles -import de.moritzruth.spigot_ttt.utils.applyMeta -import de.moritzruth.spigot_ttt.utils.hideInfo import de.moritzruth.spigot_ttt.utils.sendActionBarMessage import org.bukkit.ChatColor -import org.bukkit.entity.Item -import org.bukkit.inventory.ItemStack object SidekickDeagle: Gun( - stateClass = State::class, + type = Type.SPECIAL, + instanceType = Instance::class, + shopInfo = ShopInfo( + buyableBy = roles(Role.JACKAL), + buyLimit = 1, + price = 1 + ), displayName = "${ChatColor.AQUA}${ChatColor.BOLD}Sidekick Deagle", + itemLore = listOf( + "", + "${ChatColor.GOLD}Mache einen Spieler zu deinem Sidekick", + "", + "${ChatColor.RED}Nur ein Schuss" + ), + appendLore = false, damage = 0.1, // Not really cooldown = 1.0, magazineSize = 1, reloadTime = 0.0, - itemMaterial = Resourcepack.Items.sidekickDeagle, + material = Resourcepack.Items.sidekickDeagle, shootSound = Resourcepack.Sounds.Item.Weapon.Deagle.fire, reloadSound = Resourcepack.Sounds.Item.Weapon.Deagle.reload -), Buyable { - override val buyableBy = roles(Role.JACKAL) - override val price = 1 - override val type = TTTItem.Type.PISTOL_LIKE - override val buyLimit = 1 - - override val itemStack = ItemStack(Resourcepack.Items.sidekickDeagle).applyMeta { - hideInfo() - setDisplayName("${ChatColor.AQUA}${ChatColor.BOLD}Sidekick Deagle") - lore = listOf( - "", - "${ChatColor.GOLD}Mache einen Spieler zu deinem Sidekick", - "", - "${ChatColor.RED}Nur ein Schuss" - ) - } - - override fun reload(tttPlayer: TTTPlayer, itemStack: ItemStack, state: Gun.State) { - tttPlayer.player.sendActionBarMessage("${ChatColor.RED}Du kannst diese Waffe nicht nachladen") - } - - override fun onHit(tttPlayer: TTTPlayer, hitTTTPlayer: TTTPlayer) { - hitTTTPlayer.changeRole(Role.SIDEKICK) - } - - override fun onDrop(tttPlayer: TTTPlayer, itemEntity: Item): Boolean { - val state = isc.get(tttPlayer) ?: return true - - return if (tttPlayer.role != Role.JACKAL || state.remainingShots == 0) { - isc.remove(tttPlayer) - itemEntity.remove() - state.currentAction?.task?.cancel() - true - } else false - } - - override fun onBeforeShoot(tttPlayer: TTTPlayer, item: ItemStack, state: Gun.State): Boolean { - if (tttPlayer.role != Role.JACKAL) { - tttPlayer.player.sendActionBarMessage("${ChatColor.RED}Diese Waffe kann nur der Jackal benutzen") - return false +) { + class Instance: Gun.Instance(SidekickDeagle) { + override fun reload() { + requireCarrier().player.sendActionBarMessage("${ChatColor.RED}Du kannst diese Waffe nicht nachladen") } - return super.onBeforeShoot(tttPlayer, item, state) - } + override fun onHit(tttPlayer: TTTPlayer, hitTTTPlayer: TTTPlayer) { + hitTTTPlayer.changeRole(Role.SIDEKICK) + } - class State: Gun.State(magazineSize) + override fun onBeforeShoot(): Boolean { + val tttPlayer = requireCarrier() + if (tttPlayer.role != Role.JACKAL) { + tttPlayer.player.sendActionBarMessage("${ChatColor.RED}Diese Waffe kann nur der Jackal benutzen") + return false + } + + return super.onBeforeShoot() + } + } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/shop/Shop.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/shop/Shop.kt index 180af82..16ec627 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/shop/Shop.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/shop/Shop.kt @@ -1,6 +1,5 @@ package de.moritzruth.spigot_ttt.game.items.shop -import de.moritzruth.spigot_ttt.game.items.Buyable import de.moritzruth.spigot_ttt.game.items.ItemManager import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.game.players.TTTPlayer @@ -16,7 +15,9 @@ object Shop { }.toList() private val ITEMS_PER_PAGE = SHOP_SLOTS.count() - private fun getBuyableItems(tttPlayer: TTTPlayer) = ItemManager.ITEMS.filter { it is Buyable && it.buyableBy.contains(tttPlayer.role) }.toSet() + private fun getBuyableItems(tttPlayer: TTTPlayer) = ItemManager.ITEMS + .filter { it.shopInfo?.run { buyableBy.contains(tttPlayer.role) } ?: false } + .toSet() fun setItems(tttPlayer: TTTPlayer) { clear(tttPlayer) @@ -26,12 +27,12 @@ object Shop { if (!itemsIterator.hasNext()) break val tttItem = itemsIterator.next() - if (tttItem !is Buyable) throw Error("Item is not buyable") + val shopInfo = tttItem.shopInfo!! - tttPlayer.player.inventory.setItem(index, tttItem.itemStack.clone().applyMeta { + tttPlayer.player.inventory.setItem(index, tttItem.templateItemStack.clone().applyMeta { val displayNameSuffix = if (isOutOfStock(tttPlayer, tttItem)) "${ChatColor.RED}Ausverkauft" - else "$${tttItem.price}" + else "$${shopInfo.price}" setDisplayName("$displayName${ChatColor.RESET} - ${ChatColor.BOLD}$displayNameSuffix") }) @@ -42,8 +43,8 @@ object Shop { for(index in 9..35) tttPlayer.player.inventory.clear(index) // All slots except the hotbar and armor } - fun isOutOfStock(tttPlayer: TTTPlayer, tttItem: TTTItem): Boolean { - if (tttItem !is Buyable) throw Error("Item is not buyable") - return tttItem.buyLimit != null && tttPlayer.boughtItems.filter { it == tttItem }.count() >= tttItem.buyLimit!! + fun isOutOfStock(tttPlayer: TTTPlayer, tttItem: TTTItem<*>): Boolean { + val shopInfo = tttItem.shopInfo ?: throw Error("Item is not buyable") + return shopInfo.buyLimit != 0 && tttPlayer.boughtItems.filter { it == tttItem }.count() >= shopInfo.buyLimit } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/shop/ShopListener.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/shop/ShopListener.kt index 5e53859..87fdb2d 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/shop/ShopListener.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/items/shop/ShopListener.kt @@ -1,26 +1,21 @@ package de.moritzruth.spigot_ttt.game.items.shop import de.moritzruth.spigot_ttt.Settings -import de.moritzruth.spigot_ttt.game.items.Buyable +import de.moritzruth.spigot_ttt.game.GameListener import de.moritzruth.spigot_ttt.game.items.ItemManager import de.moritzruth.spigot_ttt.game.players.PlayerManager import de.moritzruth.spigot_ttt.game.players.TTTPlayer import de.moritzruth.spigot_ttt.game.players.TTTPlayerTrueDeathEvent import de.moritzruth.spigot_ttt.utils.sendActionBarMessage import org.bukkit.ChatColor -import org.bukkit.entity.Player import org.bukkit.event.EventHandler -import org.bukkit.event.Listener import org.bukkit.event.inventory.ClickType import org.bukkit.event.inventory.InventoryClickEvent -object ShopListener: Listener { +object ShopListener: GameListener() { @EventHandler(ignoreCancelled = true) - fun onInventoryClick(event: InventoryClickEvent) { - if (event.whoClicked !is Player) return - val tttPlayer = TTTPlayer.of(event.whoClicked as Player) ?: return - - if (event.click === ClickType.CREATIVE || event.clickedInventory?.holder != event.whoClicked) return + fun onInventoryClick(event: InventoryClickEvent) = handle(event) { tttPlayer -> + if (event.click === ClickType.CREATIVE || event.clickedInventory?.holder != event.whoClicked) return@handle event.isCancelled = true val itemStack = event.currentItem @@ -30,21 +25,21 @@ object ShopListener: Listener { event.clickedInventory?.holder == tttPlayer.player && Shop.SHOP_SLOTS.contains(event.slot) ) { - val tttItem = ItemManager.getItemByItemStack(itemStack) - if (tttItem === null || tttItem !is Buyable || !tttItem.buyableBy.contains(tttPlayer.role)) return + val tttItem = ItemManager.getTTTItemByItemStack(itemStack) ?: return@handle + val shopMeta = tttItem.shopInfo + if (shopMeta == null || !shopMeta.buyableBy.contains(tttPlayer.role)) return@handle when { Shop.isOutOfStock(tttPlayer, tttItem) -> tttPlayer.player.sendActionBarMessage("${ChatColor.RED}Dieses Item ist ausverkauft") - tttPlayer.credits < tttItem.price -> + tttPlayer.credits < tttItem.shopInfo.price -> tttPlayer.player.sendActionBarMessage("${ChatColor.RED}Du hast nicht genug Credits") else -> try { tttPlayer.addItem(tttItem) tttPlayer.boughtItems.add(tttItem) - tttPlayer.credits -= tttItem.price - + tttPlayer.credits -= shopMeta.price Shop.setItems(tttPlayer) } catch (e: TTTPlayer.AlreadyHasItemException) { tttPlayer.player.sendActionBarMessage("${ChatColor.RED}Du hast dieses Item bereits") @@ -63,8 +58,9 @@ object ShopListener: Listener { PlayerManager.tttPlayers .filter { it.role.canOwnCredits && it.role.group == killer.role.group } .forEach { - it.credits += Settings.creditsPerKill - it.player.sendActionBarMessage("${ChatColor.GREEN}Du hast ${Settings.creditsPerKill} Credit(s) erhalten") + val creditsPerKill = Settings.creditsPerKill + it.credits += creditsPerKill + it.player.sendActionBarMessage("${ChatColor.GREEN}Du hast $creditsPerKill Credit(s) erhalten") } } } diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/DeathReason.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/DeathReason.kt index 7093c91..62fe56c 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/DeathReason.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/DeathReason.kt @@ -10,5 +10,5 @@ sealed class DeathReason(val displayText: String) { object DROWNED: DeathReason("Ertrunken") object FIRE: DeathReason("Verbrannt") object POISON: DeathReason("Vergiftet") - class Item(val item: TTTItem): DeathReason("Getötet mit: ${item.itemStack.itemMeta!!.displayName}") + class Item(val item: TTTItem<*>): DeathReason("Getötet mit: ${item.templateItemStack.itemMeta!!.displayName}") } 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 deleted file mode 100644 index 5278897..0000000 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/game/players/StateContainer.kt +++ /dev/null @@ -1,45 +0,0 @@ -package de.moritzruth.spigot_ttt.game.players - -import com.google.common.collect.MutableClassToInstanceMap -import kotlin.reflect.KClass -import kotlin.reflect.KVisibility - -interface IState - -class StateContainer(private val tttPlayer: TTTPlayer) { - private val instances = MutableClassToInstanceMap.create() - - fun getOrCreate(stateClass: KClass): T = - get(stateClass) ?: run { - val parameterlessConstructor = stateClass.constructors - .find { it.parameters.isEmpty() && it.visibility == KVisibility.PUBLIC } - ?: throw NoSuchMethodException("The stateClass has no public parameterless constructor") - - parameterlessConstructor.call().also { instances[stateClass.java] = it } - } - - fun get(stateClass: KClass): T? = instances.getInstance(stateClass.java) - - fun has(stateClass: KClass): Boolean = instances.containsKey(stateClass.java) - - fun put(stateClass: KClass, value: T) { - if (instances.containsKey(stateClass.java)) - throw IllegalStateException("There is already a state instance in this container") - - instances[stateClass.java] = value - } - - fun remove(stateClass: KClass) = instances.remove(stateClass.java) -} - -class InversedStateContainer(private val stateClass: KClass) { - fun getOrCreate(tttPlayer: TTTPlayer) = tttPlayer.stateContainer.getOrCreate(stateClass) - fun get(tttPlayer: TTTPlayer) = tttPlayer.stateContainer.get(stateClass) - fun remove(tttPlayer: TTTPlayer) = tttPlayer.stateContainer.remove(stateClass) - - val tttPlayers get() = PlayerManager.tttPlayers.filter { it.stateContainer.has(stateClass) } - - 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 27f34fa..15a5e1a 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 @@ -7,20 +7,25 @@ import de.moritzruth.spigot_ttt.game.GameManager import de.moritzruth.spigot_ttt.game.GamePhase import de.moritzruth.spigot_ttt.game.ScoreboardHelper import de.moritzruth.spigot_ttt.game.classes.TTTClass +import de.moritzruth.spigot_ttt.game.classes.TTTClassCompanion import de.moritzruth.spigot_ttt.game.corpses.TTTCorpse import de.moritzruth.spigot_ttt.game.items.ItemManager -import de.moritzruth.spigot_ttt.game.items.Selectable import de.moritzruth.spigot_ttt.game.items.TTTItem import de.moritzruth.spigot_ttt.game.items.shop.Shop import de.moritzruth.spigot_ttt.utils.* -import org.bukkit.* +import org.bukkit.ChatColor +import org.bukkit.GameMode +import org.bukkit.Location +import org.bukkit.SoundCategory import org.bukkit.entity.Player import kotlin.properties.Delegates -class TTTPlayer(player: Player, role: Role, val tttClass: TTTClass = TTTClass.None) { +class TTTPlayer(player: Player, role: Role, val tttClass: TTTClassCompanion = TTTClass.None) { var alive = true var player by Delegates.observable(player) { _, _, _ -> adjustPlayer() } + val tttClassInstance = tttClass.createInstance(this) + var role = role private set(value) { if (value !== field) { @@ -33,34 +38,20 @@ class TTTPlayer(player: Player, role: Role, val tttClass: TTTClass = TTTClass.No } val roleHistory = mutableListOf() - var itemInHand by Delegates.observable(null) { _, oldItem, newItem -> - if (oldItem !== newItem) onItemInHandChanged(oldItem, newItem) - } - var walkSpeed get() = player.walkSpeed set(value) { player.walkSpeed = value } var credits by Delegates.observable(Settings.initialCredits) { _, _, _ -> scoreboard.updateCredits() } - val boughtItems = mutableListOf() + val boughtItems = mutableListOf>() val scoreboard = TTTScoreboard(this) - val stateContainer = StateContainer(this) init { adjustPlayer() scoreboard.initialize() - tttClass.onInit(this) - } - - private fun onItemInHandChanged(oldItem: TTTItem?, newItem: TTTItem?) { - if (oldItem !== null && oldItem is Selectable) { - oldItem.onDeselect(this) - } - - if (newItem !== null && newItem is Selectable) { - newItem.onSelect(this) - } + tttClassInstance.tttPlayer = this + tttClassInstance.init() } fun damage(damage: Double, reason: DeathReason, damager: TTTPlayer, scream: Boolean = true) { @@ -159,7 +150,9 @@ class TTTPlayer(player: Player, role: Role, val tttClass: TTTClass = TTTClass.No player.scoreboard = scoreboard.scoreboard } - private fun getOwningTTTItems() = player.inventory.hotbarContents.mapNotNull { it?.run { ItemManager.getItemByItemStack(this) } } + fun getOwningTTTItemInstances() = player.inventory.hotbarContents + .filterNotNull() + .mapNotNull { ItemManager.getInstanceByItemStack(it) } fun changeRole(newRole: Role, notify: Boolean = true) { roleHistory.add(role) @@ -194,12 +187,6 @@ class TTTPlayer(player: Player, role: Role, val tttClass: TTTClass = TTTClass.No player.spigot().respawn() } - itemInHand?.let { - if (it is Selectable) { - it.onDeselect(this) - } - } - player.gameMode = GameMode.SURVIVAL player.activePotionEffects.forEach { player.removePotionEffect(it.type) } player.health = 20.0 @@ -208,44 +195,39 @@ class TTTPlayer(player: Player, role: Role, val tttClass: TTTClass = TTTClass.No player.exp = 0F player.allowFlight = player.gameMode == GameMode.CREATIVE player.foodLevel = 20 - player.inventory.clear() + + tttClassInstance.reset() } - fun updateItemInHand() { - val itemStack = player.inventory.itemInMainHand - this.itemInHand = - if (itemStack.type === Material.AIR) null - else ItemManager.getItemByItemStack(itemStack) + fun checkAddItemPreconditions(tttItem: TTTItem<*>) { + val owningTTTItemInstances = getOwningTTTItemInstances() + if (owningTTTItemInstances.find { it.tttItem === tttItem } != null) throw AlreadyHasItemException() + + val maxItemsOfTypeInInventory = tttItem.type.maxItemsOfTypeInInventory + if ( + maxItemsOfTypeInInventory != null && + owningTTTItemInstances.filter { it.tttItem.type == tttItem.type }.count() >= maxItemsOfTypeInInventory + ) throw TooManyItemsOfTypeException() } - fun checkAddItemPreconditions(item: TTTItem) { - val owningTTTItems = getOwningTTTItems() - - if (owningTTTItems.contains(item)) { - throw AlreadyHasItemException() - } - - val maxItemsOfTypeInInventory = item.type.maxItemsOfTypeInInventory - if (maxItemsOfTypeInInventory !== null && owningTTTItems.filter { it.type === item.type }.count() >= maxItemsOfTypeInInventory) { - throw TooManyItemsOfTypeException() - } - } class AlreadyHasItemException: Exception("The player already owns this item") - class TooManyItemsOfTypeException: Exception("The player already owns too much items of this type") - fun addItem(item: TTTItem) { + fun addItem(item: TTTItem<*>) { checkAddItemPreconditions(item) - player.inventory.addItem(item.itemStack.clone()) - item.onOwn(this) - updateItemInHand() + val instance = item.createInstance() + player.inventory.addItem(instance.createItemStack()) + instance.carrier = this } - fun removeItem(item: TTTItem) { + fun removeItem(item: TTTItem<*>, removeInstance: Boolean = true) { + item.getInstance(this)?.let { + it.carrier = null + if (removeInstance) item.instancesByUUID.remove(it.uuid) + } + player.inventory.removeTTTItem(item) - item.onRemove(this) - updateItemInHand() } fun addDefaultClassItems() = tttClass.defaultItems.forEach { addItem(it) } 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 a999e35..970ebff 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/utils/InventoryExtensions.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/utils/InventoryExtensions.kt @@ -9,11 +9,12 @@ fun Inventory.setAllToItem(indexes: Iterable, itemStack: ItemStack) { indexes.forEach { setItem(it, itemStack) } } -fun Inventory.removeTTTItem(tttItem: TTTItem) { - val index = indexOfFirst { it?.type == tttItem.itemStack.type } +fun Inventory.removeTTTItem(tttItem: TTTItem<*>) { + val index = indexOfFirst { it?.type == tttItem.material } if (index != -1) clear(index) } -fun Inventory.removeTTTItemNextTick(tttItem: TTTItem) = nextTick { removeTTTItem(tttItem) } + +fun Inventory.removeTTTItemNextTick(tttItem: TTTItem<*>) = nextTick { removeTTTItem(tttItem) } fun PlayerInventory.clearHeldItemSlot() = clear(heldItemSlot) diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/utils/ItemDamageProgress.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/utils/ItemDamageProgress.kt deleted file mode 100644 index 698ad08..0000000 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/utils/ItemDamageProgress.kt +++ /dev/null @@ -1,38 +0,0 @@ -package de.moritzruth.spigot_ttt.utils - -import de.moritzruth.spigot_ttt.plugin -import org.bukkit.inventory.ItemStack -import org.bukkit.inventory.meta.Damageable -import org.bukkit.inventory.meta.ItemMeta -import org.bukkit.scheduler.BukkitTask -import java.time.Instant -import kotlin.math.roundToInt - -fun startItemDamageProgress(itemStack: ItemStack, duration: Double, startProgress: Double = 0.0, fromRight: Boolean = false, onFinish: () -> Unit): BukkitTask { - val startedAt = Instant.now().toEpochMilli() - - lateinit var task: BukkitTask - - task = plugin.server.scheduler.runTaskTimer(plugin, fun() { - val secondsElapsed = (Instant.now().toEpochMilli() - startedAt) / 1000.0 - val progress = secondsElapsed / duration + startProgress - - val maxDurability = itemStack.type.maxDurability - val damageMeta = itemStack.itemMeta!! as Damageable - - if (fromRight) { - damageMeta.damage = (maxDurability * progress).roundToInt() - } else { - damageMeta.damage = maxDurability - (maxDurability * progress).roundToInt() - } - - itemStack.itemMeta = damageMeta as ItemMeta - - if (progress >= 1) { - task.cancel() - onFinish() - } - }, 0, 1) - - return task -} diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/utils/ItemStackExtensions.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/utils/ItemStackExtensions.kt index e137400..7a83a16 100644 --- a/src/main/kotlin/de/moritzruth/spigot_ttt/utils/ItemStackExtensions.kt +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/utils/ItemStackExtensions.kt @@ -7,3 +7,11 @@ fun ItemStack.applyMeta(fn: ItemMeta.() -> Unit): ItemStack { itemMeta = itemMeta!!.apply(fn) return this } + +@Suppress("UNCHECKED_CAST") +fun ItemStack.applyTypedMeta(fn: T.() -> Unit): ItemStack { + val typedMeta = itemMeta as T + typedMeta.fn() + itemMeta = itemMeta as ItemMeta + return this +} diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/utils/Probability.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/utils/Probability.kt new file mode 100644 index 0000000..a4e5251 --- /dev/null +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/utils/Probability.kt @@ -0,0 +1,7 @@ +package de.moritzruth.spigot_ttt.utils + +enum class Probability { + VERY_LOW, + LOW, + NORMAL +} diff --git a/src/main/kotlin/de/moritzruth/spigot_ttt/utils/ProgressTask.kt b/src/main/kotlin/de/moritzruth/spigot_ttt/utils/ProgressTask.kt new file mode 100644 index 0000000..4f6e55e --- /dev/null +++ b/src/main/kotlin/de/moritzruth/spigot_ttt/utils/ProgressTask.kt @@ -0,0 +1,41 @@ +package de.moritzruth.spigot_ttt.utils + +import de.moritzruth.spigot_ttt.plugin +import org.bukkit.scheduler.BukkitTask +import java.time.Duration +import java.time.Instant +import kotlin.math.min + +data class TickData( + val elapsedSeconds: Double, + val progress: Double, + val isComplete: Boolean, + val trueProgress: Double +) + +/** + * @param duration Duration in seconds + */ +fun startProgressTask(duration: Double, startAt: Double = 0.0, onTick: (data: TickData) -> Unit): BukkitTask { + val startedAt = Instant.now() + + var task: BukkitTask? = null + task = plugin.server.scheduler.runTaskTimer(plugin, fun() { + val elapsedSeconds: Double = Duration.between(startedAt, Instant.now()).toMillis().toDouble() / 1000 + val progress: Double = (elapsedSeconds / duration) + startAt + val data = TickData( + elapsedSeconds = elapsedSeconds, + progress = min(a = progress, b = 1.0), + trueProgress = progress, + isComplete = progress >= 1.0 + ) + + if (data.isComplete) { + task?.cancel() + } + + onTick(data) + + }, 0, 1) + return task +}