1
0
Fork 0

Change the way items (and classes) work

This commit is contained in:
Moritz Ruth 2020-06-19 14:59:53 +02:00
parent d4b337e661
commit 87d0e6c43c
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
47 changed files with 1483 additions and 1516 deletions

View file

@ -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 <T: PlayerEvent> handle(event: T, handler: (tttPlayer: TTTPlayer) -> Unit) {
handler(TTTPlayer.of(event.player) ?: return)
}
}

View file

@ -31,13 +31,13 @@ object GameManager {
val destroyedBlocks = mutableMapOf<Location, Material>()
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()

View file

@ -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()
}
}
}
}

View file

@ -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<TTTItem> = 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()
}
}

View file

@ -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<out TTTClass>,
val defaultItems: Set<TTTItem<*>> = 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
}

View file

@ -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<TTTClass> {
val set: MutableSet<TTTClass> = TTT_CLASSES.toMutableSet()
fun createClassesIterator(count: Int): Iterator<TTTClassCompanion> {
val set: MutableSet<TTTClassCompanion> = TTT_CLASSES.toMutableSet()
val playersWithoutClass = count - TTT_CLASSES.size
if (playersWithoutClass > 0) set.addAll(Collections.nCopies(playersWithoutClass, TTTClass.None))

View file

@ -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)
)
}

View file

@ -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 } }
}
}
}
}
}

View file

@ -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)
)
}

View file

@ -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
}
}
}
}

View file

@ -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
}
}
}
}

View file

@ -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

View file

@ -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) {

View file

@ -0,0 +1,5 @@
package de.moritzruth.spigot_ttt.game.items
class ClickEvent {
var isCancelled = true
}

View file

@ -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<TTTItem> = setOf(
Pistol,
Knife, Glock, Deagle, Shotgun, SidekickDeagle,
BaseballBat,
CloakingDevice, Rifle,
EnderPearl, Radar, HealingPotion, Fireball,
Teleporter, MartyrdomGrenade, FakeCorpse, Defibrillator, SecondChance
val ITEMS: Set<TTTItem<*>> = setOf(
Deagle, Glock, Pistol, Rifle, SidekickDeagle, BaseballBat, Knife, CloakingDevice, Defibrillator,
EnderPearl, FakeCorpse, Fireball, HealingPotion, MartyrdomGrenade, Radar, SecondChance, Teleporter,
Shotgun, Radar, SecondChance
)
val droppedItemStates = mutableMapOf<Int, IState>()
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)
}
}
}
}
}

View file

@ -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<Location> {
return spawnLocationsConfig.getStringList(CONFIG_PATH).map {
@ -25,14 +26,19 @@ object ItemSpawner {
}
fun spawnWeapons() {
var itemIterator = spawningItems.shuffled().iterator()
val spawningItems = mutableListOf<TTTItem<*>>()
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())
}
}

View file

@ -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<InstanceT: TTTItem.Instance>(
val type: Type,
val templateItemStack: ItemStack,
val instanceType: KClass<out InstanceT>,
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<Role>
val price: Int
val buyLimit: Int?
}
val instancesByUUID = mutableMapOf<UUID, InstanceT>()
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<Role>,
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() {}
}
}

View file

@ -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<InstanceT: TTTItem.Instance>(private val tttItem: TTTItem<InstanceT>): 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 <T: PlayerEvent> 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)
}
}

View file

@ -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<CloakingDevice.Instance>(
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<Instance>(CloakingDevice) {
@EventHandler
fun onPlayerToggleSprint(event: PlayerToggleSprintEvent) = handleWithInstance(event) { instance ->
if (event.isSprinting && instance.enabled) event.isCancelled = true
}
}
}

View file

@ -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<Defibrillator.Instance>(
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<Instance>(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()
}
}
}

View file

@ -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<EnderPearl.Instance>(
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)
}

View file

@ -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<FakeCorpse.Instance>(
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<Instance>(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
}
}

View file

@ -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<Fireball.Instance>(
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<FireballEntity, TTTPlayer>()
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<FireballEntity, TTTPlayer>()
override val listener = object : TTTItemListener<Instance>(this) {
@EventHandler
fun onExplosionPrime(event: ExplosionPrimeEvent) {
val sender = sendersByEntity[event.entity]
if (sender != null) {
sendersByEntity.remove(event.entity)
event.isCancelled = true
GameManager.world.playSound(

View file

@ -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<HealingPotion.Instance>(
instanceType = Instance::class,
type = Type.SPECIAL,
spawnProbability = Probability.VERY_LOW,
templateItemStack = ItemStack(Material.POTION)
.applyTypedMeta<PotionMeta> { 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<Instance>(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)
}

View file

@ -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<MartyrdomGrenade.Instance>(
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<Instance>(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
}
}

View file

@ -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<Radar.Instance>(
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<Role> = 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)
}
}
}

View file

@ -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<SecondChance.Instance>(
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<Instance>(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() }
}
}

View file

@ -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<Teleporter.Instance>(
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
}
}
}

View file

@ -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<BaseballBat.Instance>(
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<Instance>(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)
}

View file

@ -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<Knife.Instance>(
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<Instance>(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)
}
}
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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<out State>,
type: Type,
instanceType: KClass<out Instance>,
spawnProbability: Probability? = null,
shopInfo: ShopInfo? = null,
material: Material,
displayName: String,
additionalLore: List<String>? = null,
itemLore: List<String>? = 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<Gun.Instance>(
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
}
}
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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()
}
}
}

View file

@ -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()
}
}
}

View file

@ -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
}
}

View file

@ -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")
}
}
}

View file

@ -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}")
}

View file

@ -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<IState>()
fun <T: IState> getOrCreate(stateClass: KClass<T>): 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 <T: IState> get(stateClass: KClass<T>): T? = instances.getInstance(stateClass.java)
fun <T: IState> has(stateClass: KClass<T>): Boolean = instances.containsKey(stateClass.java)
fun <T: IState> put(stateClass: KClass<out T>, value: T) {
if (instances.containsKey(stateClass.java))
throw IllegalStateException("There is already a state instance in this container")
instances[stateClass.java] = value
}
fun <T: IState> remove(stateClass: KClass<T>) = instances.remove(stateClass.java)
}
class InversedStateContainer<T: IState>(private val stateClass: KClass<T>) {
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) } }
}
}

View file

@ -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<Role>()
var itemInHand by Delegates.observable<TTTItem?>(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<TTTItem>()
val boughtItems = mutableListOf<TTTItem<*>>()
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) }

View file

@ -9,11 +9,12 @@ fun Inventory.setAllToItem(indexes: Iterable<Int>, 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)

View file

@ -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
}

View file

@ -7,3 +7,11 @@ fun ItemStack.applyMeta(fn: ItemMeta.() -> Unit): ItemStack {
itemMeta = itemMeta!!.apply(fn)
return this
}
@Suppress("UNCHECKED_CAST")
fun <T: Any> ItemStack.applyTypedMeta(fn: T.() -> Unit): ItemStack {
val typedMeta = itemMeta as T
typedMeta.fn()
itemMeta = itemMeta as ItemMeta
return this
}

View file

@ -0,0 +1,7 @@
package de.moritzruth.spigot_ttt.utils
enum class Probability {
VERY_LOW,
LOW,
NORMAL
}

View file

@ -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
}