First commit
This commit is contained in:
commit
5ced725265
57 changed files with 2259 additions and 0 deletions
BIN
libs/ActionBarAPI.jar
Normal file
BIN
libs/ActionBarAPI.jar
Normal file
Binary file not shown.
BIN
libs/CorpseReborn.jar
Normal file
BIN
libs/CorpseReborn.jar
Normal file
Binary file not shown.
13
src/main/kotlin/de/moritzruth/spigot_ttt/CommandManager.kt
Normal file
13
src/main/kotlin/de/moritzruth/spigot_ttt/CommandManager.kt
Normal file
|
@ -0,0 +1,13 @@
|
|||
package de.moritzruth.spigot_ttt
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.AbortCommand
|
||||
import de.moritzruth.spigot_ttt.game.StartCommand
|
||||
import de.moritzruth.spigot_ttt.items.AddItemSpawnCommand
|
||||
|
||||
object CommandManager {
|
||||
fun initializeCommands() {
|
||||
StartCommand()
|
||||
AbortCommand()
|
||||
AddItemSpawnCommand()
|
||||
}
|
||||
}
|
22
src/main/kotlin/de/moritzruth/spigot_ttt/CustomItems.kt
Normal file
22
src/main/kotlin/de/moritzruth/spigot_ttt/CustomItems.kt
Normal file
|
@ -0,0 +1,22 @@
|
|||
package de.moritzruth.spigot_ttt
|
||||
|
||||
import org.bukkit.Material
|
||||
|
||||
object CustomItems {
|
||||
val textureless = Material.WHITE_STAINED_GLASS_PANE
|
||||
val deathReason = Material.GRAY_STAINED_GLASS_PANE
|
||||
|
||||
// Roles
|
||||
val innocent = Material.GREEN_STAINED_GLASS_PANE
|
||||
val detective = Material.YELLOW_STAINED_GLASS_PANE
|
||||
val traitor = Material.RED_STAINED_GLASS_PANE
|
||||
val jackal = Material.LIGHT_BLUE_STAINED_GLASS_PANE
|
||||
val sidekick = Material.LIGHT_BLUE_STAINED_GLASS_PANE
|
||||
|
||||
// Weapons
|
||||
val deagle = Material.GOLDEN_HOE
|
||||
val glock = Material.STONE_HOE
|
||||
val pistol = Material.IRON_HOE
|
||||
val shotgun = Material.IRON_AXE
|
||||
val knife = Material.IRON_SWORD
|
||||
}
|
43
src/main/kotlin/de/moritzruth/spigot_ttt/TTTPlugin.kt
Normal file
43
src/main/kotlin/de/moritzruth/spigot_ttt/TTTPlugin.kt
Normal file
|
@ -0,0 +1,43 @@
|
|||
package de.moritzruth.spigot_ttt
|
||||
|
||||
import de.moritzruth.spigot_ttt.discord.DiscordBot
|
||||
import de.moritzruth.spigot_ttt.game.GameManager
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.plugin.java.JavaPlugin
|
||||
|
||||
class TTTPlugin: JavaPlugin() {
|
||||
init {
|
||||
pluginInstance = this
|
||||
}
|
||||
|
||||
override fun onEnable() {
|
||||
saveDefaultConfig()
|
||||
|
||||
CommandManager.initializeCommands()
|
||||
GameManager.initialize()
|
||||
|
||||
DiscordBot.start()
|
||||
}
|
||||
|
||||
override fun onDisable() {
|
||||
GameManager.resetWorld()
|
||||
DiscordBot.stop()
|
||||
}
|
||||
|
||||
fun broadcast(message: String, withPrefix: Boolean = true) {
|
||||
if (withPrefix) {
|
||||
server.broadcastMessage(prefix + message)
|
||||
} else {
|
||||
server.broadcastMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val prefix = "${ChatColor.WHITE}●${ChatColor.RESET} "
|
||||
}
|
||||
}
|
||||
|
||||
private var pluginInstance: TTTPlugin? = null
|
||||
|
||||
val plugin: TTTPlugin
|
||||
get() = pluginInstance ?: throw Error("The plugin is not initialized yet (How are you even calling this???)")
|
148
src/main/kotlin/de/moritzruth/spigot_ttt/discord/DiscordBot.kt
Normal file
148
src/main/kotlin/de/moritzruth/spigot_ttt/discord/DiscordBot.kt
Normal file
|
@ -0,0 +1,148 @@
|
|||
package de.moritzruth.spigot_ttt.discord
|
||||
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import de.moritzruth.spigot_ttt.utils.randomNumber
|
||||
import de.moritzruth.spigot_ttt.utils.secondsToTicks
|
||||
import de.moritzruth.spigot_ttt.utils.toTrimmedString
|
||||
import net.dv8tion.jda.api.EmbedBuilder
|
||||
import net.dv8tion.jda.api.JDA
|
||||
import net.dv8tion.jda.api.JDABuilder
|
||||
import net.dv8tion.jda.api.OnlineStatus
|
||||
import net.dv8tion.jda.api.entities.Activity
|
||||
import net.dv8tion.jda.api.entities.User
|
||||
import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.scheduler.BukkitTask
|
||||
import java.awt.Color
|
||||
import java.util.*
|
||||
|
||||
object DiscordBot: ListenerAdapter() {
|
||||
private val MC_PREFIX = "${ChatColor.GRAY}[${ChatColor.AQUA}Discord${ChatColor.GRAY}]${ChatColor.RESET} "
|
||||
|
||||
private val validations = mutableSetOf<ValidationData>()
|
||||
|
||||
lateinit var jda: JDA
|
||||
private val guild get() = jda.guilds[0]!!
|
||||
|
||||
fun start() {
|
||||
jda = JDABuilder.createDefault(plugin.config.getString("discord-token")).build()
|
||||
|
||||
jda.presence.setStatus(OnlineStatus.ONLINE)
|
||||
jda.presence.activity = Activity.of(Activity.ActivityType.CUSTOM_STATUS, "Schick mir eine PN mit deinem Minecraft-Namen")
|
||||
|
||||
jda.addEventListener(this)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
jda.shutdownNow()
|
||||
}
|
||||
|
||||
override fun onPrivateMessageReceived(event: PrivateMessageReceivedEvent) {
|
||||
if (event.author == jda.selfUser) return
|
||||
|
||||
val data = validations.find { it.userID == event.author.id }
|
||||
if (data == null) {
|
||||
val player = plugin.server.getPlayerExact(event.message.contentRaw)
|
||||
|
||||
if (player == null) {
|
||||
event.message.privateChannel.sendMessage(EmbedBuilder()
|
||||
.setTitle("Dieser Spieler existiert nicht oder ist nicht online.")
|
||||
.setColor(Color.RED)
|
||||
.build()
|
||||
).queue()
|
||||
} else {
|
||||
val existingConnectionOfPlayer = DiscordConnections.getByPlayerUUID(player.uniqueId)
|
||||
val avatarURL = "https://crafatar.com/avatars/${player.uniqueId.toTrimmedString()}"
|
||||
|
||||
if (existingConnectionOfPlayer != null) {
|
||||
if (existingConnectionOfPlayer.userID == event.author.id) {
|
||||
event.message.privateChannel.sendMessage(EmbedBuilder()
|
||||
.setTitle("Du hast diesen Account bereits verknüpft.")
|
||||
.setColor(Color.GREEN)
|
||||
.setThumbnail(avatarURL)
|
||||
.build()
|
||||
).queue()
|
||||
} else {
|
||||
event.message.privateChannel.sendMessage(EmbedBuilder()
|
||||
.setTitle("Dieser Spieler hat bereits einen Account verknüpft.")
|
||||
.setDescription("Du kannst die Verknüpfung widerrufen, indem du `/discord revoke` in den Chat schreibst.")
|
||||
.setColor(Color.decode("#ffe414"))
|
||||
.setThumbnail(avatarURL)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
println("https://crafatar.com/avatars/${player.uniqueId.toTrimmedString()}")
|
||||
|
||||
val embedBuilder = EmbedBuilder()
|
||||
.setTitle("Perfekt! Jetzt schick mir den Bestätigungscode, den du soeben in Minecraft erhalten hast.")
|
||||
.setColor(Color.decode("#0095ff"))
|
||||
.setThumbnail(avatarURL)
|
||||
|
||||
if (DiscordConnections.getByUserID(event.author.id) != null) {
|
||||
embedBuilder.setDescription("Hinweis: Du hast bereits einen Account verknüpft. Diese Verknüpfung wird gelöscht, wenn du einen neuen Account verbindest.")
|
||||
}
|
||||
|
||||
val nextStepMessagePromise = event.message.privateChannel.sendMessage(
|
||||
embedBuilder.build()
|
||||
).submit()
|
||||
|
||||
val newData = ValidationData(player.uniqueId, event.author.id, randomNumber(1000, 9999).toShort())
|
||||
|
||||
newData.timeoutTask = plugin.server.scheduler.runTaskLater(plugin, fun() {
|
||||
validations.remove(newData)
|
||||
|
||||
player.sendMessage("${MC_PREFIX}${ChatColor.RED}Dein Bestätigungscode ist abgelaufen.")
|
||||
nextStepMessagePromise.getNow(null)?.editMessage(EmbedBuilder()
|
||||
.setTitle("Abgelaufen.")
|
||||
.setColor(Color.LIGHT_GRAY)
|
||||
.build())?.queue()
|
||||
}, secondsToTicks(30).toLong())
|
||||
|
||||
validations.add(newData)
|
||||
|
||||
player.sendMessage("${MC_PREFIX}Dein Bestätigungscode (läuft in 30 Sekunden ab): ${ChatColor.GOLD}${ChatColor.BOLD}${newData.code}")
|
||||
}
|
||||
} else {
|
||||
if (data.code.toString() == event.message.contentRaw) {
|
||||
validations.remove(data)
|
||||
data.timeoutTask.cancel()
|
||||
|
||||
event.message.privateChannel.sendMessage(EmbedBuilder()
|
||||
.setTitle("Dein Minecraft-Account ist jetzt verknüpft.")
|
||||
.setColor(Color.GREEN)
|
||||
.build()
|
||||
).queue()
|
||||
|
||||
plugin.server.getPlayer(data.playerUUID)?.sendMessage(
|
||||
"${MC_PREFIX}Dein Minecraft-Account ist jetzt mit dem Discord-Account " +
|
||||
"${ChatColor.GOLD}${event.author.name}#${event.author.discriminator} " +
|
||||
"${ChatColor.RESET}verknüpft.")
|
||||
|
||||
DiscordConnections.add(DiscordConnection(data.playerUUID, data.userID))
|
||||
} else {
|
||||
event.message.privateChannel.sendMessage(EmbedBuilder()
|
||||
.setTitle("Der Code ist leider falsch.")
|
||||
.setColor(Color.RED)
|
||||
.build()
|
||||
).queue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setMuted(user: User, muted: Boolean) {
|
||||
guild.getMember(user)?.mute(muted)?.queue()
|
||||
}
|
||||
|
||||
data class ValidationData(
|
||||
val playerUUID: UUID,
|
||||
val userID: String,
|
||||
val code: Short
|
||||
) {
|
||||
lateinit var timeoutTask: BukkitTask
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package de.moritzruth.spigot_ttt.discord
|
||||
|
||||
import java.util.*
|
||||
|
||||
data class DiscordConnection(val playerUUID: UUID, val userID: String) {
|
||||
fun collidesWith(other: DiscordConnection): Boolean {
|
||||
return other.userID == userID || other.playerUUID == playerUUID
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package de.moritzruth.spigot_ttt.discord
|
||||
|
||||
import de.moritzruth.spigot_ttt.utils.ConfigurationFile
|
||||
import java.util.*
|
||||
|
||||
object DiscordConnections {
|
||||
private val config = ConfigurationFile("discord-connections")
|
||||
|
||||
private val connections = config.getStringList("connections").map {
|
||||
val (playerUUIDString, userID) = it.split("|")
|
||||
DiscordConnection(UUID.fromString(playerUUIDString)!!, userID)
|
||||
}.toMutableList()
|
||||
|
||||
private fun saveConnections() {
|
||||
config.set("connections", connections.map { "${it.playerUUID}|${it.userID}" })
|
||||
config.save()
|
||||
}
|
||||
|
||||
fun add(connection: DiscordConnection) {
|
||||
val colliding = connections.find { it.collidesWith(connection) }
|
||||
if (colliding != null) {
|
||||
throw CollisionException(connection, colliding)
|
||||
}
|
||||
|
||||
connections.add(connection)
|
||||
saveConnections()
|
||||
}
|
||||
|
||||
fun getByUserID(userID: String) = connections.find { it.userID == userID }
|
||||
fun getByPlayerUUID(playerUUID: UUID) = connections.find { it.playerUUID == playerUUID }
|
||||
|
||||
class CollisionException(
|
||||
val connection: DiscordConnection,
|
||||
val collidingConnection: DiscordConnection
|
||||
): RuntimeException("There is already a connection for this player or this Discord user")
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package de.moritzruth.spigot_ttt.discord
|
||||
|
||||
import de.moritzruth.spigot_ttt.discord.DiscordBot.jda
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import net.dv8tion.jda.api.entities.User
|
||||
import org.bukkit.entity.Player
|
||||
import java.util.*
|
||||
|
||||
object DiscordInterface {
|
||||
fun getPlayerByUserID(userID: String): Player? {
|
||||
val connection = DiscordConnections.getByUserID(userID) ?: return null
|
||||
return plugin.server.getPlayer(connection.playerUUID)
|
||||
}
|
||||
|
||||
fun getUserByPlayerUUID(playerUUID: UUID): User? {
|
||||
val connection = DiscordConnections.getByPlayerUUID(playerUUID) ?: return null
|
||||
return jda.getUserById(connection.userID)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package de.moritzruth.spigot_ttt.game
|
||||
|
||||
import de.moritzruth.spigot_ttt.TTTPlugin
|
||||
import de.moritzruth.spigot_ttt.utils.NoOpTabCompleter
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.command.CommandExecutor
|
||||
import org.bukkit.command.CommandSender
|
||||
|
||||
class AbortCommand: CommandExecutor {
|
||||
init {
|
||||
plugin.getCommand("abort")?.let {
|
||||
it.setExecutor(this)
|
||||
it.tabCompleter = NoOpTabCompleter()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
|
||||
if (GameManager.phase === null) {
|
||||
sender.sendMessage("${TTTPlugin.prefix}${ChatColor.RED}Zurzeit läuft kein Spiel.")
|
||||
} else {
|
||||
GameManager.abortGame(true)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
116
src/main/kotlin/de/moritzruth/spigot_ttt/game/GameManager.kt
Normal file
116
src/main/kotlin/de/moritzruth/spigot_ttt/game/GameManager.kt
Normal file
|
@ -0,0 +1,116 @@
|
|||
package de.moritzruth.spigot_ttt.game
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.players.PlayerManager
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import de.moritzruth.spigot_ttt.game.players.corpses.CorpseManager
|
||||
import de.moritzruth.spigot_ttt.items.ItemManager
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import de.moritzruth.spigot_ttt.shop.Shop
|
||||
import de.moritzruth.spigot_ttt.shop.ShopListener
|
||||
|
||||
object GameManager {
|
||||
var phase: GamePhase? = null
|
||||
private set(value) {
|
||||
field = value
|
||||
ScoreboardHelper.forEveryScoreboard { it.updateEverything(); it.showCorrectSidebarScoreboard() }
|
||||
}
|
||||
|
||||
val world = plugin.server.getWorld("world")!!
|
||||
|
||||
fun initialize() {
|
||||
ItemManager.registerListeners()
|
||||
GeneralGameEventsListener.register()
|
||||
ShopListener.register()
|
||||
}
|
||||
|
||||
fun letRoleWin(role: TTTPlayer.Role?) {
|
||||
ensurePhase(GamePhase.COMBAT)
|
||||
GameMessenger.win(role)
|
||||
phase = GamePhase.OVER
|
||||
Timers.cancelCurrentTask()
|
||||
ScoreboardHelper.forEveryScoreboard { it.updateTeams() }
|
||||
|
||||
PlayerManager.tttPlayers.forEach {
|
||||
it.setMuted(false)
|
||||
Shop.hide(it)
|
||||
}
|
||||
|
||||
Timers.startOverPhaseTimer(plugin.config.getInt("duration.over", 10)) {
|
||||
phase = null
|
||||
resetWorld()
|
||||
|
||||
PlayerManager.tttPlayers.forEach(TTTPlayer::resetAfterGameEnd)
|
||||
PlayerManager.reset()
|
||||
}
|
||||
|
||||
plugin.broadcast("", false)
|
||||
GameMessenger.roles()
|
||||
}
|
||||
|
||||
fun resetWorld() {
|
||||
CorpseManager.destroyAll()
|
||||
ItemManager.removeItemEntities()
|
||||
}
|
||||
|
||||
fun abortGame(broadcast: Boolean = false) {
|
||||
if (phase === null) throw IllegalStateException("The game is not running")
|
||||
|
||||
phase = null
|
||||
Timers.cancelCurrentTask()
|
||||
resetWorld()
|
||||
|
||||
PlayerManager.tttPlayers.forEach(TTTPlayer::resetAfterGameEnd)
|
||||
PlayerManager.reset()
|
||||
|
||||
if (broadcast) {
|
||||
GameMessenger.aborted()
|
||||
}
|
||||
}
|
||||
|
||||
fun startPreparingPhase() {
|
||||
ensurePhase(null)
|
||||
|
||||
if (PlayerManager.availablePlayers.count() < plugin.config.getInt("min-players", 4)) {
|
||||
throw NotEnoughPlayersException()
|
||||
}
|
||||
|
||||
PlayerManager.createTTTPlayers()
|
||||
phase = GamePhase.PREPARING
|
||||
|
||||
PlayerManager.tttPlayers.forEach { it.reset(); it.teleportToSpawn() }
|
||||
GameMessenger.preparingPhaseStarted()
|
||||
Timers.playTimerSound()
|
||||
ItemManager.spawnWeapons()
|
||||
|
||||
Timers.startPreparingPhaseTimer(plugin.config.getInt("duration.preparing", 20)) {
|
||||
startCombatPhase()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startCombatPhase() {
|
||||
ensurePhase(GamePhase.PREPARING)
|
||||
|
||||
phase = GamePhase.COMBAT
|
||||
|
||||
PlayerManager.tttPlayers.forEach { Shop.show(it) }
|
||||
|
||||
ScoreboardHelper.forEveryScoreboard { it.updateTeams() }
|
||||
|
||||
GameMessenger.combatPhaseStarted()
|
||||
Timers.playTimerSound()
|
||||
|
||||
Timers.startCombatPhaseTimer(plugin.config.getInt("duration.combat", 480)) {
|
||||
if (PlayerManager.stillLivingRoles.contains(TTTPlayer.Role.INNOCENT)) {
|
||||
letRoleWin(TTTPlayer.Role.INNOCENT)
|
||||
} else {
|
||||
letRoleWin(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ensurePhase(phase: GamePhase?) {
|
||||
if (this.phase !== phase) throw IllegalStateException("The game must be in $phase phase")
|
||||
}
|
||||
|
||||
class NotEnoughPlayersException: Exception("There are not enough players to start the game")
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package de.moritzruth.spigot_ttt.game
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.players.PlayerManager
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import de.moritzruth.spigot_ttt.utils.secondsToTicks
|
||||
import org.bukkit.ChatColor
|
||||
|
||||
object GameMessenger {
|
||||
fun preparingPhaseStarted() = plugin.broadcast("${ChatColor.GREEN}Die Vorbereitungsphase hat begonnen.")
|
||||
|
||||
fun remainingPreparingPhaseTime(remainingSeconds: Int) {
|
||||
if (remainingSeconds > 0) {
|
||||
if (remainingSeconds == 1) {
|
||||
plugin.broadcast("${ChatColor.GREEN}Die Vorbereitungsphase endet in ${ChatColor.WHITE}1 ${ChatColor.GREEN}Sekunde")
|
||||
} else {
|
||||
plugin.broadcast("${ChatColor.GREEN}Die Vorbereitungsphase endet in ${ChatColor.WHITE}$remainingSeconds ${ChatColor.GREEN}Sekunden")
|
||||
}
|
||||
} else {
|
||||
throw IllegalArgumentException("remainingSeconds must be positive and not 0")
|
||||
}
|
||||
}
|
||||
|
||||
fun combatPhaseStarted() = plugin.broadcast("${ChatColor.GREEN}Die Kampfphase hat begonnen.")
|
||||
|
||||
fun remainingRoundTime(remainingMinutes: Int) {
|
||||
if (remainingMinutes > 0) {
|
||||
if (remainingMinutes == 1) {
|
||||
plugin.broadcast("${ChatColor.GREEN}Das Spiel endet in ${ChatColor.WHITE}1 ${ChatColor.GREEN}Minute")
|
||||
} else {
|
||||
plugin.broadcast("${ChatColor.GREEN}Das Spiel endet in ${ChatColor.WHITE}$remainingMinutes ${ChatColor.GREEN}Minuten")
|
||||
}
|
||||
} else {
|
||||
throw IllegalArgumentException("remainingMinutes must be positive and not 0")
|
||||
}
|
||||
}
|
||||
|
||||
fun win(winnerRole: TTTPlayer.Role?) {
|
||||
val winner = when(winnerRole) {
|
||||
null -> {
|
||||
plugin.broadcast("Niemand hat gewonnen")
|
||||
PlayerManager.tttPlayers.forEach {
|
||||
it.player.sendTitle("Unentschieden", null, secondsToTicks(0.5), secondsToTicks(5), secondsToTicks(1))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
TTTPlayer.Role.JACKAL, TTTPlayer.Role.SIDEKICK -> "Der ${TTTPlayer.Role.JACKAL.chatColor}Jackal"
|
||||
TTTPlayer.Role.TRAITOR -> "Die ${TTTPlayer.Role.TRAITOR.chatColor}Traitor"
|
||||
TTTPlayer.Role.INNOCENT, TTTPlayer.Role.DETECTIVE -> "Die ${TTTPlayer.Role.INNOCENT.chatColor}Innocents"
|
||||
}
|
||||
|
||||
val winnerMessage = when(winnerRole) {
|
||||
TTTPlayer.Role.JACKAL, TTTPlayer.Role.SIDEKICK -> "hat gewonnen"
|
||||
TTTPlayer.Role.TRAITOR, TTTPlayer.Role.INNOCENT, TTTPlayer.Role.DETECTIVE -> "haben gewonnen"
|
||||
}
|
||||
|
||||
plugin.broadcast("${ChatColor.GOLD}$winner ${ChatColor.GOLD}${winnerMessage}")
|
||||
PlayerManager.tttPlayers.forEach {
|
||||
it.player.sendTitle("${ChatColor.GOLD}$winner", "${ChatColor.GOLD}$winnerMessage", secondsToTicks(0.5), secondsToTicks(5), secondsToTicks(1))
|
||||
}
|
||||
}
|
||||
|
||||
fun abortedPlayerLeft() {
|
||||
plugin.broadcast("${ChatColor.RED}Das Spiel wurde abgebrochen, da ein Spieler den Server verlassen hat.")
|
||||
}
|
||||
|
||||
fun aborted() {
|
||||
plugin.broadcast("${ChatColor.RED}Das Spiel wurde abgebrochen.")
|
||||
}
|
||||
|
||||
fun corpseIdentified(by: String, who: String, role: TTTPlayer.Role) {
|
||||
plugin.broadcast("$by ${ChatColor.GOLD}hat die Leiche von ${ChatColor.WHITE}$who ${ChatColor.GOLD}identifiziert. Er/Sie war ${role.coloredDisplayName}")
|
||||
}
|
||||
|
||||
fun roles() {
|
||||
val playersByRole = PlayerManager.getPlayersByRole()
|
||||
val roles = playersByRole.keys.sortedBy(TTTPlayer.Role::position)
|
||||
|
||||
for (role in roles) {
|
||||
val entries = playersByRole.getValue(role).map { tttPlayer ->
|
||||
tttPlayer.player.displayName.run {
|
||||
if (tttPlayer.roleHistory.count() != 0)
|
||||
this + " (${tttPlayer.roleHistory.joinToString(", ") { it.coloredDisplayName }})"
|
||||
else this
|
||||
}
|
||||
}
|
||||
|
||||
plugin.broadcast(" ${role.coloredDisplayName}: ${entries.joinToString(", ")}", false)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package de.moritzruth.spigot_ttt.game
|
||||
|
||||
enum class GamePhase(val displayName: String) {
|
||||
PREPARING("Vorbereitung"),
|
||||
COMBAT("Kampf"),
|
||||
OVER("Ende")
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
package de.moritzruth.spigot_ttt.game
|
||||
|
||||
import de.moritzruth.spigot_ttt.TTTPlugin
|
||||
import de.moritzruth.spigot_ttt.game.players.PlayerManager
|
||||
import de.moritzruth.spigot_ttt.game.players.corpses.CorpseManager
|
||||
import de.moritzruth.spigot_ttt.items.ItemManager
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
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.BlockBreakEvent
|
||||
import org.bukkit.event.block.BlockPlaceEvent
|
||||
import org.bukkit.event.entity.EntityDamageEvent
|
||||
import org.bukkit.event.entity.EntityPickupItemEvent
|
||||
import org.bukkit.event.entity.FoodLevelChangeEvent
|
||||
import org.bukkit.event.entity.PlayerDeathEvent
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent
|
||||
import org.bukkit.event.player.PlayerItemHeldEvent
|
||||
import org.bukkit.event.player.PlayerJoinEvent
|
||||
import org.bukkit.event.player.PlayerQuitEvent
|
||||
import org.golde.bukkit.corpsereborn.CorpseAPI.events.CorpseClickEvent
|
||||
import java.time.Instant
|
||||
|
||||
object GeneralGameEventsListener: Listener {
|
||||
fun register() {
|
||||
plugin.server.pluginManager.registerEvents(this, plugin)
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onPlayerJoin(event: PlayerJoinEvent) {
|
||||
event.joinMessage = "${TTTPlugin.prefix}${event.player.displayName} ${ChatColor.GOLD}hat das Spiel betreten."
|
||||
PlayerManager.onPlayerJoin(event.player)
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onPlayerQuit(event: PlayerQuitEvent) {
|
||||
event.quitMessage = "${TTTPlugin.prefix}${event.player.displayName} ${ChatColor.GOLD}hat das Spiel verlassen."
|
||||
PlayerManager.onPlayerQuit(event.player)
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onFoodLevelChange(event: FoodLevelChangeEvent) {
|
||||
event.foodLevel = 20
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
fun onEntityDamageLow(event: EntityDamageEvent) {
|
||||
if (GameManager.phase !== GamePhase.COMBAT) {
|
||||
event.isCancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
fun onEntityDamageHighest(event: EntityDamageEvent) {
|
||||
if (event.entity !is Player) return
|
||||
val tttPlayer = PlayerManager.getTTTPlayer(event.entity as Player) ?: return
|
||||
|
||||
if (tttPlayer.player.health - event.finalDamage <= 0) {
|
||||
tttPlayer.kill()
|
||||
|
||||
// gameManager.playerManager.tttPlayers.forEach {
|
||||
// it.player.playSound(tttPlayer.player.location, Sound.ENTITY_PLAYER_DEATH, SoundCategory.PLAYERS, 2f, 1f)
|
||||
// }
|
||||
|
||||
event.damage = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onPlayerDeath(event: PlayerDeathEvent) {
|
||||
event.deathMessage = null
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onBlockPlace(event: BlockPlaceEvent) {
|
||||
if (event.player.gameMode !== GameMode.CREATIVE) event.isCancelled = true
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onBlockBreak(event: BlockBreakEvent) {
|
||||
if (event.player.gameMode !== GameMode.CREATIVE) event.isCancelled = true
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onCorpseClick(event: CorpseClickEvent) {
|
||||
// bug: always ClickType.UNKNOWN
|
||||
// if (event.clickType !== ClickType.RIGHT) return
|
||||
|
||||
val tttCorpse = CorpseManager.getTTTCorpse(event.corpse)
|
||||
|
||||
if (tttCorpse !== null) {
|
||||
if(Instant.now().toEpochMilli() - tttCorpse.timestamp.toEpochMilli() < 200) return
|
||||
|
||||
event.clicker.openInventory(tttCorpse.inventory)
|
||||
tttCorpse.identify(event.clicker)
|
||||
}
|
||||
|
||||
event.isCancelled = true
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onAsyncPlayerChat(event: AsyncPlayerChatEvent) {
|
||||
val senderTTTPlayer = PlayerManager.getTTTPlayer(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
|
||||
fun onPlayerItemHeld(event: PlayerItemHeldEvent) {
|
||||
val tttPlayer = PlayerManager.getTTTPlayer(event.player) ?: return
|
||||
|
||||
val itemStack = event.player.inventory.getItem(event.newSlot)
|
||||
|
||||
tttPlayer.itemInHand =
|
||||
if (itemStack == null || itemStack.type === Material.AIR) null
|
||||
else ItemManager.getItemByItemStack(itemStack)
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onEntityPickupItem(event: EntityPickupItemEvent) {
|
||||
if (event.entity !is Player) {
|
||||
return
|
||||
}
|
||||
|
||||
val player = event.entity as Player
|
||||
val tttPlayer = PlayerManager.getTTTPlayer(player) ?: return
|
||||
|
||||
val tttItem = ItemManager.getItemByItemStack(event.item.itemStack)
|
||||
|
||||
if (tttItem != null) {
|
||||
if (kotlin.runCatching { tttPlayer.checkAddItemPreconditions(tttItem) }.isSuccess) {
|
||||
plugin.server.scheduler.runTask(plugin, fun() {
|
||||
tttPlayer.updateItemInHand()
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
event.isCancelled = true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package de.moritzruth.spigot_ttt.game
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.players.PlayerManager
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTScoreboard
|
||||
|
||||
object ScoreboardHelper {
|
||||
fun forEveryScoreboard(action: (tttScoreboard: TTTScoreboard) -> Unit) {
|
||||
PlayerManager.tttPlayers.forEach {
|
||||
action(it.scoreboard)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package de.moritzruth.spigot_ttt.game
|
||||
|
||||
import de.moritzruth.spigot_ttt.TTTPlugin
|
||||
import de.moritzruth.spigot_ttt.utils.NoOpTabCompleter
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.command.CommandExecutor
|
||||
import org.bukkit.command.CommandSender
|
||||
|
||||
class StartCommand: CommandExecutor {
|
||||
init {
|
||||
plugin.getCommand("start")?.let {
|
||||
it.setExecutor(this)
|
||||
it.tabCompleter = NoOpTabCompleter()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
|
||||
if (GameManager.phase === null) {
|
||||
try {
|
||||
GameManager.startPreparingPhase()
|
||||
} catch (e: GameManager.NotEnoughPlayersException) {
|
||||
sender.sendMessage("${ChatColor.RED}Es sind nicht genügend Spieler online.")
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("${TTTPlugin.prefix}${ChatColor.RED}Das Spiel läuft bereits.")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
66
src/main/kotlin/de/moritzruth/spigot_ttt/game/Timers.kt
Normal file
66
src/main/kotlin/de/moritzruth/spigot_ttt/game/Timers.kt
Normal file
|
@ -0,0 +1,66 @@
|
|||
package de.moritzruth.spigot_ttt.game
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.players.PlayerManager
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import de.moritzruth.spigot_ttt.utils.secondsToTicks
|
||||
import org.bukkit.Sound
|
||||
import org.bukkit.scheduler.BukkitTask
|
||||
|
||||
object Timers {
|
||||
private var bukkitTask: BukkitTask? = null
|
||||
var remainingSeconds: Int = 0
|
||||
|
||||
private val isCountdownRunning get() = bukkitTask != null
|
||||
|
||||
fun cancelCurrentTask() {
|
||||
bukkitTask?.cancel()
|
||||
bukkitTask = null
|
||||
}
|
||||
|
||||
fun startPreparingPhaseTimer(duration: Int, onFinished: () -> Unit) {
|
||||
runCountdown(duration, onFinished) { remainingSeconds ->
|
||||
when (remainingSeconds) {
|
||||
in 1..5, 10, 30 -> {
|
||||
playTimerSound()
|
||||
GameMessenger.remainingPreparingPhaseTime(remainingSeconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startCombatPhaseTimer(duration: Int, onFinished: () -> Unit) {
|
||||
runCountdown(duration, onFinished) { remainingSeconds ->
|
||||
if (remainingSeconds % 60 == 0) {
|
||||
playTimerSound()
|
||||
GameMessenger.remainingRoundTime(remainingSeconds / 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startOverPhaseTimer(duration: Int, onFinished: () -> Unit) {
|
||||
runCountdown(duration, onFinished) {}
|
||||
}
|
||||
|
||||
fun playTimerSound() {
|
||||
PlayerManager.tttPlayers.forEach { it.player.playSound(it.player.location, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f) }
|
||||
}
|
||||
|
||||
private fun runCountdown(durationSeconds: Int, onFinished: () -> Unit, onSecond: (remainingSeconds: Int) -> Unit) {
|
||||
if (isCountdownRunning) {
|
||||
throw IllegalStateException("Only one countdown can be active at a time")
|
||||
}
|
||||
|
||||
remainingSeconds = durationSeconds
|
||||
|
||||
bukkitTask = plugin.server.scheduler.runTaskTimer(plugin, Runnable {
|
||||
ScoreboardHelper.forEveryScoreboard { it.updatePhaseAndTime() }
|
||||
if (remainingSeconds == 0) {
|
||||
cancelCurrentTask()
|
||||
onFinished()
|
||||
} else {
|
||||
onSecond(remainingSeconds)
|
||||
remainingSeconds -= 1
|
||||
}
|
||||
}, 0, secondsToTicks(1).toLong())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package de.moritzruth.spigot_ttt.game.players
|
||||
|
||||
import de.moritzruth.spigot_ttt.items.TTTItem
|
||||
|
||||
sealed class DeathReason(val displayText: String) {
|
||||
object DISCONNECTED: DeathReason("Verbindung unterbrochen")
|
||||
object SUICIDE: DeathReason("Suizid")
|
||||
class Item(val item: TTTItem): DeathReason("Getötet mit: ${item.displayName}")
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package de.moritzruth.spigot_ttt.game.players
|
||||
|
||||
import de.moritzruth.spigot_ttt.TTTPlugin
|
||||
import de.moritzruth.spigot_ttt.game.GameManager
|
||||
import de.moritzruth.spigot_ttt.game.GameMessenger
|
||||
import de.moritzruth.spigot_ttt.game.GamePhase
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import de.moritzruth.spigot_ttt.utils.noop
|
||||
import de.moritzruth.spigot_ttt.utils.teleportPlayerToWorldSpawn
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.GameMode
|
||||
import org.bukkit.entity.Player
|
||||
|
||||
object PlayerManager {
|
||||
val tttPlayers= mutableListOf<TTTPlayer>()
|
||||
|
||||
val availablePlayers get() = plugin.server.onlinePlayers.filter { it.gameMode === GameMode.SURVIVAL }
|
||||
val stillLivingRoles get() = tttPlayers.filter { it.alive }.map { it.role }.distinct()
|
||||
|
||||
fun getTTTPlayer(player: Player) = tttPlayers.find { it.player === player }
|
||||
|
||||
fun getPlayersByRole() = mutableMapOf<TTTPlayer.Role, MutableSet<TTTPlayer>>()
|
||||
.apply { tttPlayers.forEach { getOrPut(it.role) { mutableSetOf() }.add(it) } }
|
||||
.toMap()
|
||||
|
||||
fun reset() {
|
||||
tttPlayers.clear()
|
||||
}
|
||||
|
||||
fun letRemainingRoleWin() {
|
||||
GameManager.ensurePhase(GamePhase.COMBAT)
|
||||
|
||||
println(stillLivingRoles)
|
||||
if (stillLivingRoles.count() == 1) {
|
||||
GameManager.letRoleWin(stillLivingRoles[0])
|
||||
}
|
||||
}
|
||||
|
||||
fun onPlayerJoin(player: Player) {
|
||||
val tttPlayer = tttPlayers.find { it.player.uniqueId == player.uniqueId }
|
||||
|
||||
if (tttPlayer == null) {
|
||||
if (GameManager.phase == null) {
|
||||
teleportPlayerToWorldSpawn(player)
|
||||
player.gameMode = GameMode.SURVIVAL
|
||||
} else {
|
||||
player.gameMode = GameMode.SURVIVAL
|
||||
player.sendMessage("${TTTPlugin.prefix}${ChatColor.GREEN}Du schaust jetzt zu.")
|
||||
}
|
||||
} else {
|
||||
tttPlayer.player = player
|
||||
|
||||
player.sendMessage("${TTTPlugin.prefix}${ChatColor.RED}Du bist gestorben, da du das Spiel verlassen hast.")
|
||||
when(GameManager.phase) {
|
||||
GamePhase.PREPARING -> noop() // Never happens
|
||||
GamePhase.COMBAT -> {
|
||||
player.gameMode = GameMode.SPECTATOR
|
||||
}
|
||||
GamePhase.OVER -> {
|
||||
tttPlayer.teleportToSpawn()
|
||||
player.gameMode = GameMode.SURVIVAL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onPlayerQuit(player: Player) {
|
||||
val tttPlayer = getTTTPlayer(player) ?: return
|
||||
|
||||
when(GameManager.phase) {
|
||||
GamePhase.PREPARING -> {
|
||||
GameManager.abortGame()
|
||||
GameMessenger.abortedPlayerLeft()
|
||||
}
|
||||
GamePhase.COMBAT -> {
|
||||
tttPlayer.kill()
|
||||
}
|
||||
GamePhase.OVER -> noop()
|
||||
}
|
||||
}
|
||||
|
||||
fun createTTTPlayers() {
|
||||
val playersWithoutRole = availablePlayers.toMutableSet()
|
||||
|
||||
val playerCount = playersWithoutRole.count()
|
||||
val traitorCount: Int = if (playerCount <= 4) 1 else playerCount / 4
|
||||
|
||||
if (playerCount >= plugin.config.getInt("min-players-for.detective", 5)) {
|
||||
val player = playersWithoutRole.random()
|
||||
tttPlayers.add(TTTPlayer(player, TTTPlayer.Role.DETECTIVE))
|
||||
playersWithoutRole.remove(player)
|
||||
}
|
||||
|
||||
if (playerCount >= plugin.config.getInt("min-players-for.jackal", 6)) {
|
||||
val player = playersWithoutRole.random()
|
||||
tttPlayers.add(TTTPlayer(player, TTTPlayer.Role.JACKAL))
|
||||
playersWithoutRole.remove(player)
|
||||
}
|
||||
|
||||
for (index in 1..traitorCount) {
|
||||
val player = playersWithoutRole.random()
|
||||
tttPlayers.add(TTTPlayer(player, TTTPlayer.Role.TRAITOR))
|
||||
playersWithoutRole.remove(player)
|
||||
}
|
||||
|
||||
playersWithoutRole.forEach { tttPlayers.add(TTTPlayer(it, TTTPlayer.Role.INNOCENT)) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package de.moritzruth.spigot_ttt.game.players
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
interface State
|
||||
|
||||
class StateContainer {
|
||||
private val instances = mutableSetOf<State>()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T: State> get(stateClass: KClass<T>, createInstance: () -> T): T {
|
||||
return (instances.find { stateClass.isInstance(it) } ?: createInstance().also {
|
||||
instances.add(it)
|
||||
}) as T
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
instances.clear()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
package de.moritzruth.spigot_ttt.game.players
|
||||
|
||||
import de.moritzruth.spigot_ttt.CustomItems
|
||||
import de.moritzruth.spigot_ttt.discord.DiscordBot
|
||||
import de.moritzruth.spigot_ttt.discord.DiscordInterface
|
||||
import de.moritzruth.spigot_ttt.game.GameManager
|
||||
import de.moritzruth.spigot_ttt.game.GamePhase
|
||||
import de.moritzruth.spigot_ttt.game.players.corpses.CorpseManager
|
||||
import de.moritzruth.spigot_ttt.items.ItemManager
|
||||
import de.moritzruth.spigot_ttt.items.SelectableItem
|
||||
import de.moritzruth.spigot_ttt.items.TTTItem
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import de.moritzruth.spigot_ttt.shop.Shop
|
||||
import de.moritzruth.spigot_ttt.utils.hotbarContents
|
||||
import de.moritzruth.spigot_ttt.utils.teleportPlayerToWorldSpawn
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.GameMode
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.entity.Player
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class TTTPlayer(player: Player, role: Role) {
|
||||
var alive = true
|
||||
var lastDeathReason: DeathReason? = null
|
||||
|
||||
var player by Delegates.observable(player) { _, _, player -> player.scoreboard = scoreboard.scoreboard }
|
||||
|
||||
var role by Delegates.observable(role) { _, _, _ -> scoreboard.updateRole() }
|
||||
val roleHistory = mutableListOf<Role>()
|
||||
|
||||
var itemInHand by Delegates.observable<TTTItem?>(null) { _, oldItem, newItem -> onItemInHandChanged(oldItem, newItem) }
|
||||
var credits by Delegates.observable(10) { _, _, _ -> scoreboard.updateCredits() }
|
||||
|
||||
val scoreboard = TTTScoreboard(this)
|
||||
val stateContainer = StateContainer()
|
||||
|
||||
private val discordUser get() = DiscordInterface.getUserByPlayerUUID(player.uniqueId)
|
||||
|
||||
init {
|
||||
player.scoreboard = scoreboard.scoreboard
|
||||
scoreboard.initialize()
|
||||
}
|
||||
|
||||
private fun onItemInHandChanged(oldItem: TTTItem?, newItem: TTTItem?) {
|
||||
if (oldItem !== null && oldItem is SelectableItem) {
|
||||
oldItem.onDeselect(this)
|
||||
}
|
||||
|
||||
if (newItem !== null && newItem is SelectableItem) {
|
||||
newItem.onSelect(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun kill(reason: DeathReason = DeathReason.SUICIDE) {
|
||||
GameManager.ensurePhase(GamePhase.COMBAT)
|
||||
|
||||
player.gameMode = GameMode.SPECTATOR
|
||||
alive = false
|
||||
lastDeathReason = reason
|
||||
CorpseManager.spawn(this, reason)
|
||||
|
||||
Shop.hide(this)
|
||||
setMuted(true)
|
||||
|
||||
PlayerManager.letRemainingRoleWin()
|
||||
}
|
||||
|
||||
fun resetAfterGameEnd() {
|
||||
if (!alive) {
|
||||
teleportToSpawn()
|
||||
}
|
||||
|
||||
// Required to be delayed because of a Minecraft bug which sometimes turns players invisible
|
||||
plugin.server.scheduler.runTask(plugin) { ->
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
if (player.isDead) {
|
||||
player.spigot().respawn()
|
||||
}
|
||||
|
||||
itemInHand?.apply {
|
||||
if (this is SelectableItem) {
|
||||
this.onDeselect(this@TTTPlayer)
|
||||
}
|
||||
}
|
||||
|
||||
stateContainer.clear()
|
||||
|
||||
setMuted(false)
|
||||
|
||||
alive = true
|
||||
player.gameMode = GameMode.SURVIVAL
|
||||
player.activePotionEffects.forEach { player.removePotionEffect(it.type) }
|
||||
player.health = 20.0
|
||||
|
||||
player.inventory.clear()
|
||||
}
|
||||
|
||||
fun teleportToSpawn() {
|
||||
teleportPlayerToWorldSpawn(player)
|
||||
}
|
||||
|
||||
fun setMuted(muted: Boolean) {
|
||||
val discordUser = discordUser
|
||||
|
||||
if (discordUser != null) {
|
||||
DiscordBot.setMuted(discordUser, muted)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateItemInHand() {
|
||||
val itemStack = player.inventory.itemInMainHand
|
||||
this.itemInHand =
|
||||
if (itemStack.type === Material.AIR) null
|
||||
else ItemManager.getItemByItemStack(itemStack)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
fun addItem(item: TTTItem) {
|
||||
checkAddItemPreconditions(item)
|
||||
player.inventory.addItem(item.itemStack.clone())
|
||||
}
|
||||
|
||||
class AlreadyHasItemException: Exception("The player already owns this item")
|
||||
class TooManyItemsOfTypeException: Exception("The player already owns too much items of this type")
|
||||
|
||||
fun getOwningTTTItems() = player.inventory.hotbarContents.mapNotNull { it?.run { ItemManager.getItemByItemStack(this) } }
|
||||
|
||||
enum class Role(
|
||||
val chatColor: ChatColor,
|
||||
val displayName: String,
|
||||
val iconItemMaterial: Material
|
||||
) {
|
||||
INNOCENT(ChatColor.GREEN, "Innocent", CustomItems.innocent),
|
||||
DETECTIVE(ChatColor.YELLOW, "Detective", CustomItems.detective),
|
||||
TRAITOR(ChatColor.RED, "Traitor", CustomItems.traitor),
|
||||
JACKAL(ChatColor.AQUA, "Jackal", CustomItems.jackal),
|
||||
SIDEKICK(ChatColor.AQUA, "Sidekick", CustomItems.sidekick);
|
||||
|
||||
val coloredDisplayName = "$chatColor$displayName${ChatColor.RESET}"
|
||||
|
||||
val isInnocentRole get() = this === INNOCENT || this === DETECTIVE
|
||||
val isJackalRole get() = this === JACKAL || this === SIDEKICK
|
||||
|
||||
val position by lazy { values().indexOf(this) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
package de.moritzruth.spigot_ttt.game.players
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.GameManager
|
||||
import de.moritzruth.spigot_ttt.game.GamePhase
|
||||
import de.moritzruth.spigot_ttt.game.Timers
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import de.moritzruth.spigot_ttt.shop.Shop
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.scoreboard.DisplaySlot
|
||||
import org.bukkit.scoreboard.RenderType
|
||||
import org.bukkit.scoreboard.Scoreboard
|
||||
import org.bukkit.scoreboard.Team
|
||||
|
||||
class TTTScoreboard(private val tttPlayer: TTTPlayer) {
|
||||
val scoreboard: Scoreboard = plugin.server.scoreboardManager!!.newScoreboard
|
||||
|
||||
fun initialize() {
|
||||
scoreboard.registerNewTeam(ValueTeam.ROLE.teamName).addEntry(ValueTeam.ROLE.entry)
|
||||
scoreboard.registerNewTeam(ValueTeam.PHASE_AND_TIME.teamName).addEntry(ValueTeam.PHASE_AND_TIME.entry)
|
||||
scoreboard.registerNewTeam(ValueTeam.CREDITS.teamName).addEntry(ValueTeam.CREDITS.entry)
|
||||
|
||||
scoreboard.registerNewObjective(
|
||||
INACTIVE_OBJECTIVE,
|
||||
"dummy",
|
||||
"${ChatColor.GOLD}TTT",
|
||||
RenderType.INTEGER
|
||||
).apply {
|
||||
val lines = mutableListOf(
|
||||
" ".repeat(20),
|
||||
"${ChatColor.GRAY}Inaktiv",
|
||||
" "
|
||||
)
|
||||
|
||||
lines.reversed().forEachIndexed { index, line -> getScore(line).score = index }
|
||||
}
|
||||
|
||||
scoreboard.registerNewObjective(
|
||||
ACTIVE_OBJECTIVE,
|
||||
"dummy",
|
||||
"${ChatColor.GOLD}TTT",
|
||||
RenderType.INTEGER
|
||||
).apply {
|
||||
val lines = mutableListOf(
|
||||
" ".repeat(20),
|
||||
ValueTeam.PHASE_AND_TIME.entry,
|
||||
" ",
|
||||
ValueTeam.ROLE.entry,
|
||||
" "
|
||||
)
|
||||
|
||||
lines.reversed().forEachIndexed { index, line -> getScore(line).score = index }
|
||||
}
|
||||
|
||||
scoreboard.registerNewObjective(
|
||||
ACTIVE_WITH_CREDITS_OBJECTIVE,
|
||||
"dummy",
|
||||
"${ChatColor.GOLD}TTT",
|
||||
RenderType.INTEGER
|
||||
).apply {
|
||||
val lines = mutableListOf(
|
||||
" ".repeat(20),
|
||||
ValueTeam.PHASE_AND_TIME.entry,
|
||||
" ",
|
||||
ValueTeam.ROLE.entry,
|
||||
ValueTeam.CREDITS.entry,
|
||||
" "
|
||||
)
|
||||
|
||||
lines.reversed().forEachIndexed { index, line -> getScore(line).score = index }
|
||||
}
|
||||
|
||||
scoreboard.registerNewTeam(SPECIAL_TEAM_NAME).apply {
|
||||
setAllowFriendlyFire(true)
|
||||
setCanSeeFriendlyInvisibles(true)
|
||||
setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.ALWAYS)
|
||||
}
|
||||
|
||||
scoreboard.registerNewTeam(DEFAULT_TEAM_NAME).apply {
|
||||
setAllowFriendlyFire(true)
|
||||
setCanSeeFriendlyInvisibles(false)
|
||||
setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.ALWAYS)
|
||||
setOption(Team.Option.DEATH_MESSAGE_VISIBILITY, Team.OptionStatus.NEVER)
|
||||
setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.NEVER)
|
||||
}
|
||||
|
||||
updateEverything()
|
||||
showCorrectSidebarScoreboard()
|
||||
}
|
||||
|
||||
fun updatePhaseAndTime() {
|
||||
val phase = GameManager.phase
|
||||
|
||||
if (phase === null) {
|
||||
setValue(ValueTeam.PHASE_AND_TIME, "Inaktiv")
|
||||
} else {
|
||||
val remainingSeconds = Timers.remainingSeconds
|
||||
val seconds = remainingSeconds % 60
|
||||
val minutes = (remainingSeconds - seconds) / 60
|
||||
val minutesString = minutes.toString().padStart(2, '0')
|
||||
val secondsString = seconds.toString().padStart(2, '0')
|
||||
|
||||
setValue(ValueTeam.PHASE_AND_TIME, "${ChatColor.GOLD}${phase.displayName}: ${ChatColor.WHITE}$minutesString:$secondsString")
|
||||
}
|
||||
}
|
||||
|
||||
fun updateRole() {
|
||||
when (GameManager.phase) {
|
||||
null -> setValue(ValueTeam.ROLE, "")
|
||||
GamePhase.PREPARING -> setValue(ValueTeam.ROLE, "Du bist: ${ChatColor.MAGIC}xxxxxxxx")
|
||||
GamePhase.COMBAT, GamePhase.OVER -> setValue(ValueTeam.ROLE, "Du bist: " + tttPlayer.role.coloredDisplayName.toUpperCase())
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTeams() {
|
||||
val defaultTeam = scoreboard.getTeam(DEFAULT_TEAM_NAME)!!
|
||||
val phase = GameManager.phase
|
||||
|
||||
if (phase === GamePhase.COMBAT) {
|
||||
if (tttPlayer.role.isJackalRole || tttPlayer.role === TTTPlayer.Role.TRAITOR) {
|
||||
val specialTeam = scoreboard.getTeam(SPECIAL_TEAM_NAME)!!
|
||||
specialTeam.color = tttPlayer.role.chatColor
|
||||
|
||||
PlayerManager.tttPlayers.forEach {
|
||||
if ((tttPlayer.role.isJackalRole && it.role.isJackalRole) || (tttPlayer.role === TTTPlayer.Role.TRAITOR && it.role === TTTPlayer.Role.TRAITOR)) {
|
||||
specialTeam.addEntry(it.player.displayName)
|
||||
} else {
|
||||
defaultTeam.addEntry(it.player.displayName)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
defaultTeam.setOption(Team.Option.NAME_TAG_VISIBILITY, if (phase === null) Team.OptionStatus.ALWAYS else Team.OptionStatus.NEVER)
|
||||
|
||||
PlayerManager.tttPlayers.forEach {
|
||||
defaultTeam.addEntry(it.player.displayName)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCredits() {
|
||||
setValue(ValueTeam.CREDITS, "${ChatColor.GREEN}Credits: ${ChatColor.WHITE}${tttPlayer.credits}")
|
||||
}
|
||||
|
||||
fun updateEverything() {
|
||||
updatePhaseAndTime()
|
||||
updateRole()
|
||||
updateTeams()
|
||||
updateCredits()
|
||||
}
|
||||
|
||||
fun showCorrectSidebarScoreboard() {
|
||||
setSidebar(when {
|
||||
GameManager.phase === null -> INACTIVE_OBJECTIVE
|
||||
GameManager.phase !== GamePhase.PREPARING && Shop.getBuyableItems(tttPlayer).isNotEmpty() -> ACTIVE_WITH_CREDITS_OBJECTIVE
|
||||
else -> ACTIVE_OBJECTIVE
|
||||
})
|
||||
}
|
||||
|
||||
private fun setSidebar(objectiveName: String) {
|
||||
scoreboard.getObjective(objectiveName)!!.displaySlot = DisplaySlot.SIDEBAR
|
||||
}
|
||||
|
||||
private fun setValue(valueTeam: ValueTeam, value: String) {
|
||||
scoreboard.getTeam(valueTeam.teamName)!!.prefix = value
|
||||
}
|
||||
|
||||
private enum class ValueTeam(val teamName: String, val entry: String) {
|
||||
PHASE_AND_TIME("_phase-and-time", "${ChatColor.AQUA}"),
|
||||
ROLE("_role", "${ChatColor.BLACK}"),
|
||||
CREDITS("_credits", "${ChatColor.GOLD}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SPECIAL_TEAM_NAME = "special"
|
||||
private const val DEFAULT_TEAM_NAME = "default"
|
||||
private const val INACTIVE_OBJECTIVE = "1"
|
||||
private const val ACTIVE_OBJECTIVE = "2"
|
||||
private const val ACTIVE_WITH_CREDITS_OBJECTIVE = "3"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package de.moritzruth.spigot_ttt.game.players.corpses
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.players.DeathReason
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import org.golde.bukkit.corpsereborn.nms.Corpses
|
||||
|
||||
object CorpseManager {
|
||||
val corpses= mutableListOf<TTTCorpse>()
|
||||
|
||||
fun getTTTCorpse(corpse: Corpses.CorpseData): TTTCorpse? {
|
||||
return corpses.find { it.corpse === corpse }
|
||||
}
|
||||
|
||||
fun spawn(tttPlayer: TTTPlayer, reason: DeathReason) {
|
||||
corpses.add(TTTCorpse(tttPlayer.player, tttPlayer.player.location, tttPlayer.role, reason))
|
||||
}
|
||||
|
||||
fun destroyAll() = corpses.forEach(TTTCorpse::destroy)
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package de.moritzruth.spigot_ttt.game.players.corpses
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.GameMessenger
|
||||
import de.moritzruth.spigot_ttt.game.players.DeathReason
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import de.moritzruth.spigot_ttt.CustomItems
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.Location
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.inventory.InventoryType
|
||||
import org.bukkit.inventory.ItemFlag
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.golde.bukkit.corpsereborn.CorpseAPI.CorpseAPI
|
||||
import org.golde.bukkit.corpsereborn.nms.Corpses
|
||||
import java.time.Instant
|
||||
|
||||
class TTTCorpse(private val player: Player, location: Location, private val role: TTTPlayer.Role, private val reason: DeathReason) {
|
||||
val corpse: Corpses.CorpseData?
|
||||
val inventory = player.server.createInventory(null, InventoryType.HOPPER, "${role.chatColor}${player.displayName}")
|
||||
var identified = false
|
||||
val timestamp: Instant = Instant.now()
|
||||
|
||||
init {
|
||||
inventory.setItem(0, ItemStack(role.iconItemMaterial).apply {
|
||||
val meta = itemMeta!!
|
||||
meta.setDisplayName(role.coloredDisplayName)
|
||||
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES)
|
||||
meta.lore = listOf("${ChatColor.GRAY}Rolle")
|
||||
|
||||
itemMeta = meta
|
||||
})
|
||||
|
||||
val reasonItem = if (reason is DeathReason.Item) reason.item.itemStack.clone() else ItemStack(CustomItems.deathReason)
|
||||
reasonItem.itemMeta = reasonItem.itemMeta!!.apply {
|
||||
setDisplayName("${ChatColor.RESET}" + reason.displayText)
|
||||
addItemFlags(ItemFlag.HIDE_ATTRIBUTES)
|
||||
lore = listOf("${ChatColor.GRAY}Grund des Todes")
|
||||
}
|
||||
|
||||
inventory.setItem(1, reasonItem)
|
||||
|
||||
corpse = CorpseAPI.spawnCorpse(player, location)
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
if (corpse !== null) CorpseAPI.removeCorpse(corpse)
|
||||
}
|
||||
|
||||
fun identify(by: Player) {
|
||||
if (identified) return
|
||||
|
||||
GameMessenger.corpseIdentified(by.displayName, player.displayName, role)
|
||||
identified = true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package de.moritzruth.spigot_ttt.items
|
||||
|
||||
import de.moritzruth.spigot_ttt.TTTPlugin
|
||||
import de.moritzruth.spigot_ttt.utils.NoOpTabCompleter
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.command.CommandExecutor
|
||||
import org.bukkit.command.CommandSender
|
||||
import org.bukkit.entity.Player
|
||||
|
||||
class AddItemSpawnCommand: CommandExecutor {
|
||||
init {
|
||||
plugin.getCommand("additemspawn")?.let {
|
||||
it.setExecutor(this)
|
||||
it.tabCompleter = NoOpTabCompleter()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
|
||||
if (sender !is Player) {
|
||||
sender.sendMessage("${ChatColor.RED}Dieser Befehl kann nur von Spielern verwendet werden.")
|
||||
return true
|
||||
}
|
||||
|
||||
ItemManager.addItemSpawnLocation(sender.location)
|
||||
sender.sendMessage("${TTTPlugin.prefix}${ChatColor.GREEN}Ein Waffenspawn wurde an deiner Position hinzugefügt.")
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package de.moritzruth.spigot_ttt.items
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.GameManager
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.deagle.GoldenDeagle
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.glock.Glock
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.pistol.Pistol
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.shotgun.Shotgun
|
||||
import de.moritzruth.spigot_ttt.items.weapons.knife.Knife
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import de.moritzruth.spigot_ttt.utils.ConfigurationFile
|
||||
import org.bukkit.Location
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.entity.Item
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
object ItemManager {
|
||||
private const val CONFIG_PATH = "spawn-locations"
|
||||
|
||||
private val spawnLocationsConfig = ConfigurationFile("spawnLocations")
|
||||
|
||||
val items: Set<TTTItem> = setOf(Pistol, Knife, Glock, GoldenDeagle, Shotgun, GoldenDeagle)
|
||||
private val spawningItems = items.filter(TTTItem::spawning)
|
||||
|
||||
fun registerListeners() {
|
||||
for (item in items) {
|
||||
plugin.server.pluginManager.registerEvents(item.listener, plugin)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getItemByMaterial(material: Material) = items.find { tttItem -> material === tttItem.itemStack.type }
|
||||
fun getItemByItemStack(itemStack: ItemStack) = getItemByMaterial(itemStack.type)
|
||||
|
||||
private fun getSpawnLocations(): Set<Location> {
|
||||
return spawnLocationsConfig.getStringList(CONFIG_PATH).map {
|
||||
val (x, y, z) = it.split(":").map(String::toDouble)
|
||||
Location(GameManager.world, x, y, z)
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
private fun setSpawnLocations(spawnLocations: Set<Location>) {
|
||||
spawnLocationsConfig.set(CONFIG_PATH, spawnLocations.map {
|
||||
"${it.x}:${it.y}:${it.z}"
|
||||
})
|
||||
}
|
||||
|
||||
fun spawnWeapons() {
|
||||
for (location in getSpawnLocations()) {
|
||||
GameManager.world.dropItem(location, spawningItems.random().itemStack.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fun removeItemEntities() {
|
||||
GameManager.world.getEntitiesByClass(Item::class.java).forEach {
|
||||
it.remove()
|
||||
}
|
||||
}
|
||||
|
||||
fun addItemSpawnLocation(location: Location) {
|
||||
val spawnLocations = getSpawnLocations().toMutableSet()
|
||||
|
||||
spawnLocations.add(roundLocationToHalfBlock(location))
|
||||
setSpawnLocations(spawnLocations)
|
||||
spawnLocationsConfig.save()
|
||||
}
|
||||
|
||||
private fun roundLocationToHalfBlock(location: Location) = Location(location.world, roundToHalf(location.x), roundToHalf(location.y), roundToHalf(location.z))
|
||||
|
||||
private fun roundToHalf(number: Double): Double = (number * 2).roundToInt() / 2.0
|
||||
}
|
32
src/main/kotlin/de/moritzruth/spigot_ttt/items/TTTItem.kt
Normal file
32
src/main/kotlin/de/moritzruth/spigot_ttt/items/TTTItem.kt
Normal file
|
@ -0,0 +1,32 @@
|
|||
package de.moritzruth.spigot_ttt.items
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import java.util.*
|
||||
|
||||
interface SelectableItem: TTTItem {
|
||||
fun onSelect(tttPlayer: TTTPlayer)
|
||||
fun onDeselect(tttPlayer: TTTPlayer)
|
||||
}
|
||||
|
||||
interface BuyableItem: TTTItem {
|
||||
val buyableBy: EnumSet<TTTPlayer.Role>
|
||||
val price: Int
|
||||
}
|
||||
|
||||
interface TTTItem {
|
||||
val displayName: String
|
||||
val listener: Listener
|
||||
val itemStack: ItemStack
|
||||
val spawning: Boolean
|
||||
val type: Type
|
||||
|
||||
enum class Type(val maxItemsOfTypeInInventory: Int?) {
|
||||
NORMAL_WEAPON(2),
|
||||
SPECIAL_WEAPON(2),
|
||||
OTHER(null);
|
||||
|
||||
val position by lazy { values().indexOf(this) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package de.moritzruth.spigot_ttt.items
|
||||
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
||||
import org.bukkit.event.player.PlayerInteractEvent
|
||||
|
||||
fun EntityDamageByEntityEvent.isRelevant(itemName: String): Boolean =
|
||||
damager is Player &&
|
||||
entity is Player &&
|
||||
(damager as Player).inventory.itemInMainHand.itemMeta?.displayName == itemName
|
||||
|
||||
fun PlayerInteractEvent.isRelevant(itemName: String): Boolean = item?.itemMeta?.displayName == itemName
|
|
@ -0,0 +1,30 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons
|
||||
|
||||
import org.bukkit.ChatColor
|
||||
|
||||
object LoreHelper {
|
||||
fun damage(damage: Double?): String {
|
||||
if (damage == null) {
|
||||
return "${ChatColor.GREEN}∞"
|
||||
}
|
||||
|
||||
if (damage <= 0) {
|
||||
throw IllegalArgumentException("damage must be higher than 0")
|
||||
}
|
||||
|
||||
return "${ChatColor.RED}$damage"
|
||||
}
|
||||
|
||||
fun magazineSize(maxUses: Int?) = when {
|
||||
maxUses == null -> "${ChatColor.GREEN}∞"
|
||||
maxUses == 1 -> "${ChatColor.RED}1"
|
||||
maxUses <= 5 -> "${ChatColor.YELLOW}$maxUses"
|
||||
else -> "${ChatColor.GREEN}${maxUses}"
|
||||
}
|
||||
|
||||
fun cooldown(cooldown: Double) = when {
|
||||
cooldown <= 1 -> "${ChatColor.GREEN}${cooldown}s"
|
||||
cooldown > 3 -> "${ChatColor.RED}${cooldown}s"
|
||||
else -> "${ChatColor.GREEN}${cooldown}s"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.guns
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.GameManager
|
||||
import de.moritzruth.spigot_ttt.game.GamePhase
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import de.moritzruth.spigot_ttt.items.SelectableItem
|
||||
import de.moritzruth.spigot_ttt.items.TTTItem
|
||||
import de.moritzruth.spigot_ttt.items.weapons.LoreHelper
|
||||
import de.moritzruth.spigot_ttt.utils.startItemDamageProgress
|
||||
import org.bukkit.*
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.inventory.ItemFlag
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.inventory.meta.ItemMeta
|
||||
|
||||
abstract class Gun<StateT: GunState>: TTTItem, SelectableItem {
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
override val listener: Listener = GunListener(this)
|
||||
|
||||
abstract val damage: Double
|
||||
abstract val cooldown: Double
|
||||
abstract val reloadTime: Double
|
||||
abstract val magazineSize: Int
|
||||
abstract val itemMaterial: Material
|
||||
abstract val recoil: Int
|
||||
|
||||
abstract fun getState(tttPlayer: TTTPlayer): StateT
|
||||
|
||||
open fun computeActualDamage(tttPlayer: TTTPlayer, receiver: Player) = damage
|
||||
|
||||
protected fun getItemMeta(itemStack: ItemStack): ItemMeta {
|
||||
val meta = itemStack.itemMeta!!
|
||||
meta.setDisplayName(displayName)
|
||||
meta.lore = listOf(
|
||||
"",
|
||||
"${ChatColor.GRAY}Schaden: ${LoreHelper.damage(damage)}",
|
||||
"${ChatColor.GRAY}Cooldown: ${LoreHelper.cooldown(cooldown)}",
|
||||
"${ChatColor.GRAY}Magazin: ${LoreHelper.magazineSize(magazineSize)} Schuss"
|
||||
)
|
||||
|
||||
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES)
|
||||
return meta
|
||||
}
|
||||
|
||||
protected fun updateLevel(tttPlayer: TTTPlayer, state: StateT = getState(tttPlayer)) {
|
||||
tttPlayer.player.level = state.remainingShots
|
||||
}
|
||||
|
||||
private fun applyRecoil(player: Player) {
|
||||
val location = player.location
|
||||
location.pitch -= recoil
|
||||
player.teleport(location)
|
||||
}
|
||||
|
||||
open fun onBeforeShoot(tttPlayer: TTTPlayer, item: ItemStack, state: StateT = getState(tttPlayer)) {
|
||||
if (state.cooldownOrReloadTask !== null) throw ActionInProgressError()
|
||||
}
|
||||
|
||||
fun shoot(tttPlayer: TTTPlayer, item: ItemStack, state: StateT = getState(tttPlayer)) {
|
||||
onBeforeShoot(tttPlayer, item, state)
|
||||
|
||||
if (state.remainingShots == 0) {
|
||||
GameManager.world.playSound(tttPlayer.player.location, Sound.BLOCK_ANVIL_PLACE, SoundCategory.PLAYERS, 1f, 1.3f)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Add sound
|
||||
GameManager.world.playSound(tttPlayer.player.location, Sound.BLOCK_IRON_DOOR_OPEN, SoundCategory.PLAYERS, 2f, 1.3f)
|
||||
|
||||
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
|
||||
) { it !== tttPlayer.player }
|
||||
|
||||
if (rayTraceResult !== null) {
|
||||
val entity = rayTraceResult.hitEntity
|
||||
|
||||
if (entity is Player) {
|
||||
val actualDamage = computeActualDamage(tttPlayer, entity)
|
||||
|
||||
entity.damage(actualDamage)
|
||||
tttPlayer.player.playSound(tttPlayer.player.location, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, SoundCategory.MASTER, 2f, 1.2f)
|
||||
entity.velocity = tttPlayer.player.location.direction.multiply(actualDamage / 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyRecoil(tttPlayer.player)
|
||||
|
||||
state.cooldownOrReloadTask = startItemDamageProgress(item, cooldown) {
|
||||
state.cooldownOrReloadTask = null
|
||||
}
|
||||
}
|
||||
|
||||
open fun reload(tttPlayer: TTTPlayer, item: ItemStack, state: StateT = getState(tttPlayer)) {
|
||||
if (state.cooldownOrReloadTask !== null) throw ActionInProgressError()
|
||||
if (state.remainingShots == magazineSize) return
|
||||
|
||||
state.cooldownOrReloadTask = startItemDamageProgress(item, reloadTime) {
|
||||
state.cooldownOrReloadTask = null
|
||||
state.remainingShots = magazineSize
|
||||
updateLevel(tttPlayer, state)
|
||||
}
|
||||
|
||||
// TODO: Add sound
|
||||
}
|
||||
|
||||
override fun onSelect(tttPlayer: TTTPlayer) {
|
||||
updateLevel(tttPlayer)
|
||||
}
|
||||
|
||||
override fun onDeselect(tttPlayer: TTTPlayer) {
|
||||
tttPlayer.player.level = 0
|
||||
}
|
||||
|
||||
class ActionInProgressError: RuntimeException("The gun is on cooldown or reloading")
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.guns
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.players.PlayerManager
|
||||
import de.moritzruth.spigot_ttt.items.isRelevant
|
||||
import de.moritzruth.spigot_ttt.utils.noop
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.event.block.Action
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
||||
import org.bukkit.event.player.PlayerInteractEvent
|
||||
|
||||
class GunListener(private val gun: Gun<*>): Listener {
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
fun onEntityDamageByEntity(event: EntityDamageByEntityEvent) {
|
||||
if (event.isRelevant(gun.displayName)) event.isCancelled = true
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onPlayerInteract(event: PlayerInteractEvent) {
|
||||
if (!event.isRelevant(gun.displayName)) return
|
||||
val tttPlayer = PlayerManager.getTTTPlayer(event.player) ?: return
|
||||
|
||||
try {
|
||||
when(event.action) {
|
||||
Action.LEFT_CLICK_AIR, Action.LEFT_CLICK_BLOCK -> gun.reload(tttPlayer, event.item!!)
|
||||
Action.RIGHT_CLICK_AIR, Action.RIGHT_CLICK_BLOCK -> gun.shoot(tttPlayer, event.item!!)
|
||||
else -> noop()
|
||||
}
|
||||
} catch (e: Gun.ActionInProgressError) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.guns
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.players.State
|
||||
import org.bukkit.scheduler.BukkitTask
|
||||
|
||||
open class GunState(magazineSize: Int): State {
|
||||
var cooldownOrReloadTask: BukkitTask? = null
|
||||
var remainingShots = magazineSize
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.guns.deagle
|
||||
|
||||
import de.moritzruth.spigot_ttt.CustomItems
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer.Role.DETECTIVE
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer.Role.TRAITOR
|
||||
import de.moritzruth.spigot_ttt.items.BuyableItem
|
||||
import de.moritzruth.spigot_ttt.items.TTTItem
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.Gun
|
||||
import de.moritzruth.spigot_ttt.utils.heartsToHealth
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import java.util.*
|
||||
|
||||
object Deagle: Gun<DeagleState>(), BuyableItem {
|
||||
override val spawning = true
|
||||
override val displayName = "${ChatColor.BLUE}${ChatColor.BOLD}Deagle"
|
||||
override val damage = heartsToHealth(5.0)
|
||||
override val cooldown = 1.4
|
||||
override val magazineSize = 8
|
||||
override val reloadTime = 3.0
|
||||
override val itemMaterial = CustomItems.deagle
|
||||
override val itemStack = ItemStack(itemMaterial).apply {
|
||||
itemMeta = getItemMeta(this)
|
||||
}
|
||||
override val recoil = 10
|
||||
override val buyableBy: EnumSet<TTTPlayer.Role> = EnumSet.of(TRAITOR, DETECTIVE)
|
||||
override val price = 1
|
||||
override val type = TTTItem.Type.NORMAL_WEAPON
|
||||
|
||||
override fun getState(tttPlayer: TTTPlayer) = tttPlayer.stateContainer.get(DeagleState::class) { DeagleState() }
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.guns.deagle
|
||||
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.GunState
|
||||
|
||||
class DeagleState: GunState(Deagle.magazineSize)
|
|
@ -0,0 +1,28 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.guns.glock
|
||||
|
||||
import de.moritzruth.spigot_ttt.CustomItems
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import de.moritzruth.spigot_ttt.items.TTTItem
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.Gun
|
||||
import de.moritzruth.spigot_ttt.utils.heartsToHealth
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.inventory.ItemStack
|
||||
|
||||
object Glock: Gun<GlockState>() {
|
||||
override val spawning = true
|
||||
override val displayName = "${ChatColor.YELLOW}${ChatColor.BOLD}Glock"
|
||||
override val damage = heartsToHealth(1.5)
|
||||
override val cooldown = 0.3
|
||||
override val magazineSize = 20
|
||||
override val reloadTime = 2.0
|
||||
override val itemMaterial = CustomItems.glock
|
||||
override val itemStack = ItemStack(itemMaterial).apply {
|
||||
itemMeta = getItemMeta(this)
|
||||
}
|
||||
override val recoil = 2
|
||||
override val type = TTTItem.Type.NORMAL_WEAPON
|
||||
|
||||
override fun getState(tttPlayer: TTTPlayer) = tttPlayer.stateContainer.get(GlockState::class) { GlockState() }
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.guns.glock
|
||||
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.GunState
|
||||
|
||||
class GlockState: GunState(Glock.magazineSize)
|
|
@ -0,0 +1,43 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.guns.deagle
|
||||
|
||||
import de.moritzruth.spigot_ttt.CustomItems
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer.Role.DETECTIVE
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer.Role.TRAITOR
|
||||
import de.moritzruth.spigot_ttt.items.BuyableItem
|
||||
import de.moritzruth.spigot_ttt.items.TTTItem
|
||||
import de.moritzruth.spigot_ttt.items.weapons.LoreHelper
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.Gun
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import java.util.*
|
||||
|
||||
object GoldenDeagle: Gun<GoldenDeagleState>(), BuyableItem {
|
||||
override val spawning = false
|
||||
override val displayName = "${ChatColor.GOLD}${ChatColor.BOLD}Golden Deagle"
|
||||
override val damage = 1.0 // is not used
|
||||
override val cooldown = 1.4
|
||||
override val magazineSize = 2
|
||||
override val reloadTime = 10.0
|
||||
override val itemMaterial = CustomItems.deagle
|
||||
override val itemStack = ItemStack(itemMaterial).apply {
|
||||
itemMeta = getItemMeta(this).apply {
|
||||
lore = listOf(
|
||||
"",
|
||||
"${ChatColor.GRAY}Schaden: ${LoreHelper.damage(null)}",
|
||||
"${ChatColor.GRAY}Cooldown: ${LoreHelper.cooldown(cooldown)}",
|
||||
"${ChatColor.GRAY}Magazin: ${LoreHelper.magazineSize(magazineSize)} Schuss"
|
||||
)
|
||||
}
|
||||
}
|
||||
override val recoil = 10
|
||||
override val buyableBy: EnumSet<TTTPlayer.Role> = EnumSet.of(TRAITOR, DETECTIVE)
|
||||
override val price = 3
|
||||
override val type = TTTItem.Type.SPECIAL_WEAPON
|
||||
|
||||
override fun computeActualDamage(tttPlayer: TTTPlayer, receiver: Player) = 1000.0
|
||||
override fun getState(tttPlayer: TTTPlayer) = tttPlayer.stateContainer.get(GoldenDeagleState::class) { GoldenDeagleState() }
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.guns.deagle
|
||||
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.GunState
|
||||
|
||||
class GoldenDeagleState: GunState(GoldenDeagle.magazineSize)
|
|
@ -0,0 +1,28 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.guns.pistol
|
||||
|
||||
import de.moritzruth.spigot_ttt.CustomItems
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import de.moritzruth.spigot_ttt.items.TTTItem
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.Gun
|
||||
import de.moritzruth.spigot_ttt.utils.heartsToHealth
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.inventory.ItemStack
|
||||
|
||||
object Pistol: Gun<PistolState>() {
|
||||
override val spawning = true
|
||||
override val displayName = "${ChatColor.YELLOW}${ChatColor.BOLD}Pistol"
|
||||
override val damage = heartsToHealth(2.5)
|
||||
override val cooldown = 0.8
|
||||
override val magazineSize = 10
|
||||
override val reloadTime = 2.0
|
||||
override val itemMaterial = CustomItems.pistol
|
||||
override val itemStack = ItemStack(itemMaterial).apply {
|
||||
itemMeta = getItemMeta(this)
|
||||
}
|
||||
override val recoil = 5
|
||||
override val type = TTTItem.Type.NORMAL_WEAPON
|
||||
|
||||
override fun getState(tttPlayer: TTTPlayer) = tttPlayer.stateContainer.get(PistolState::class) { PistolState() }
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.guns.pistol
|
||||
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.GunState
|
||||
|
||||
class PistolState: GunState(Pistol.magazineSize)
|
|
@ -0,0 +1,87 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.guns.shotgun
|
||||
|
||||
import de.moritzruth.spigot_ttt.CustomItems
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import de.moritzruth.spigot_ttt.items.TTTItem
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.Gun
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import de.moritzruth.spigot_ttt.utils.heartsToHealth
|
||||
import de.moritzruth.spigot_ttt.utils.secondsToTicks
|
||||
import de.moritzruth.spigot_ttt.utils.startItemDamageProgress
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.inventory.meta.Damageable
|
||||
import org.bukkit.inventory.meta.ItemMeta
|
||||
|
||||
object Shotgun: Gun<ShotgunState>() {
|
||||
private const val reloadTimePerBullet = 0.5
|
||||
|
||||
override val spawning = true
|
||||
override val displayName = "${ChatColor.YELLOW}${ChatColor.BOLD}Shotgun"
|
||||
override val damage = heartsToHealth(3.0)
|
||||
override val cooldown = 0.8
|
||||
override val magazineSize = 8
|
||||
override val reloadTime get() = reloadTimePerBullet * magazineSize
|
||||
override val recoil = 20
|
||||
override val type = TTTItem.Type.NORMAL_WEAPON
|
||||
|
||||
override val itemMaterial = CustomItems.shotgun
|
||||
override val itemStack = ItemStack(itemMaterial).apply {
|
||||
itemMeta = getItemMeta(this).apply {
|
||||
lore = lore!! + listOf("", "${ChatColor.RED}Weniger Schaden auf Distanz")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getState(tttPlayer: TTTPlayer) = tttPlayer.stateContainer.get(ShotgunState::class) { ShotgunState() }
|
||||
|
||||
override fun computeActualDamage(tttPlayer: TTTPlayer, receiver: Player): Double {
|
||||
val distance = tttPlayer.player.location.distance(receiver.location)
|
||||
|
||||
return when {
|
||||
distance <= 2 -> heartsToHealth(10.0)
|
||||
distance >= 14 -> 0.0
|
||||
distance > 8 -> heartsToHealth(1.5)
|
||||
else -> heartsToHealth(damage)
|
||||
}
|
||||
}
|
||||
|
||||
override fun reload(tttPlayer: TTTPlayer, item: ItemStack, state: ShotgunState) {
|
||||
if (state.remainingShots == magazineSize) return
|
||||
|
||||
state.cooldownOrReloadTask = startItemDamageProgress(item, reloadTime, state.remainingShots.toDouble() / magazineSize) {
|
||||
state.cooldownOrReloadTask = null
|
||||
}
|
||||
|
||||
state.reloadUpdateTask = plugin.server.scheduler.runTaskTimer(plugin, fun() {
|
||||
state.remainingShots++
|
||||
updateLevel(tttPlayer)
|
||||
|
||||
// TODO: Add sound
|
||||
|
||||
if (state.remainingShots == magazineSize) {
|
||||
state.reloadUpdateTask?.cancel()
|
||||
state.reloadUpdateTask = null
|
||||
}
|
||||
}, secondsToTicks(reloadTimePerBullet).toLong(), secondsToTicks(reloadTimePerBullet).toLong())
|
||||
}
|
||||
|
||||
override fun onBeforeShoot(tttPlayer: TTTPlayer, item: ItemStack, state: ShotgunState) {
|
||||
if (state.remainingShots == 0) return
|
||||
|
||||
if (state.reloadUpdateTask == null && state.cooldownOrReloadTask != null) {
|
||||
throw ActionInProgressError()
|
||||
}
|
||||
|
||||
state.cooldownOrReloadTask?.cancel()
|
||||
state.cooldownOrReloadTask = null
|
||||
val damageMeta = item.itemMeta!! as Damageable
|
||||
damageMeta.damage = 0
|
||||
item.itemMeta = damageMeta as ItemMeta
|
||||
|
||||
state.reloadUpdateTask?.cancel()
|
||||
state.reloadUpdateTask = null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.guns.shotgun
|
||||
|
||||
import de.moritzruth.spigot_ttt.items.weapons.guns.GunState
|
||||
import org.bukkit.scheduler.BukkitTask
|
||||
|
||||
class ShotgunState: GunState(Shotgun.magazineSize) {
|
||||
var reloadUpdateTask: BukkitTask? = null
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.knife
|
||||
|
||||
import de.moritzruth.spigot_ttt.CustomItems
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer.Role.TRAITOR
|
||||
import de.moritzruth.spigot_ttt.items.BuyableItem
|
||||
import de.moritzruth.spigot_ttt.items.TTTItem
|
||||
import de.moritzruth.spigot_ttt.items.weapons.LoreHelper
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.inventory.ItemFlag
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import java.util.*
|
||||
|
||||
object Knife: TTTItem, BuyableItem {
|
||||
override val displayName = "${ChatColor.RED}${ChatColor.BOLD}Knife"
|
||||
override val spawning = false
|
||||
override val listener = KnifeListener(this)
|
||||
override val itemStack = ItemStack(CustomItems.knife)
|
||||
override val buyableBy: EnumSet<TTTPlayer.Role> = EnumSet.of(TRAITOR)
|
||||
override val price = 1
|
||||
override val type = TTTItem.Type.SPECIAL_WEAPON
|
||||
|
||||
init {
|
||||
val meta = itemStack.itemMeta!!
|
||||
|
||||
meta.setDisplayName(displayName)
|
||||
meta.lore = listOf(
|
||||
"",
|
||||
"${ChatColor.GRAY}Schaden: ${LoreHelper.damage(null)}",
|
||||
"",
|
||||
"${ChatColor.RED}Nur einmal verwendbar",
|
||||
"${ChatColor.RED}Nur aus nächster Nähe"
|
||||
)
|
||||
|
||||
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES)
|
||||
|
||||
itemStack.itemMeta = meta
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package de.moritzruth.spigot_ttt.items.weapons.knife
|
||||
|
||||
import de.moritzruth.spigot_ttt.items.isRelevant
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
||||
import org.bukkit.inventory.meta.Damageable
|
||||
import org.bukkit.inventory.meta.ItemMeta
|
||||
|
||||
class KnifeListener(val knife: Knife): Listener {
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
fun onEntityDamageByEntity(event: EntityDamageByEntityEvent) {
|
||||
if (!event.isRelevant(Knife.displayName)) return
|
||||
|
||||
val damagerPlayer = event.damager as Player
|
||||
val damagedPlayer = event.entity as Player
|
||||
|
||||
val distance = damagerPlayer.location.distance(damagedPlayer.location)
|
||||
|
||||
if (distance > 1.5) event.isCancelled = true else {
|
||||
// Break the item
|
||||
val item = damagerPlayer.inventory.itemInMainHand
|
||||
val damageableMeta = item.itemMeta!! as Damageable
|
||||
damageableMeta.damage = 1000
|
||||
item.itemMeta = damageableMeta as ItemMeta
|
||||
|
||||
event.damage = 1000.0
|
||||
}
|
||||
}
|
||||
}
|
45
src/main/kotlin/de/moritzruth/spigot_ttt/shop/Shop.kt
Normal file
45
src/main/kotlin/de/moritzruth/spigot_ttt/shop/Shop.kt
Normal file
|
@ -0,0 +1,45 @@
|
|||
package de.moritzruth.spigot_ttt.shop
|
||||
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import de.moritzruth.spigot_ttt.items.BuyableItem
|
||||
import de.moritzruth.spigot_ttt.items.ItemManager
|
||||
import org.bukkit.ChatColor
|
||||
|
||||
object Shop {
|
||||
val SHOP_SLOTS = (9..35).toMutableList().apply {
|
||||
// last row; possibly needed in the future
|
||||
// remove(17)
|
||||
// remove(26)
|
||||
// remove(35)
|
||||
}.toList()
|
||||
private val ITEMS_PER_PAGE = SHOP_SLOTS.count()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun getBuyableItems(tttPlayer: TTTPlayer): Set<BuyableItem> = ItemManager.items.filter {
|
||||
if (it is BuyableItem) it.buyableBy.contains(tttPlayer.role) else false
|
||||
}.toSet() as Set<BuyableItem>
|
||||
|
||||
fun show(tttPlayer: TTTPlayer) {
|
||||
val itemsIterator = getBuyableItems(tttPlayer).iterator()
|
||||
|
||||
for(index in SHOP_SLOTS) {
|
||||
if (!itemsIterator.hasNext()) break
|
||||
|
||||
val tttItem = itemsIterator.next()
|
||||
val itemStack = tttItem.itemStack.clone()
|
||||
val meta = itemStack.itemMeta!!
|
||||
meta.setDisplayName(meta.displayName + "${ChatColor.RESET} ${ChatColor.WHITE}${ChatColor.BOLD}$${tttItem.price}")
|
||||
itemStack.itemMeta = meta
|
||||
|
||||
tttPlayer.player.inventory.setItem(index, itemStack)
|
||||
}
|
||||
}
|
||||
|
||||
fun hide(tttPlayer: TTTPlayer) {
|
||||
val range = 9..19
|
||||
|
||||
range + (1..8)
|
||||
|
||||
for(index in 9..35) tttPlayer.player.inventory.clear(index) // All slots except the hotbar and armor
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package de.moritzruth.spigot_ttt.shop
|
||||
|
||||
import com.connorlinfoot.actionbarapi.ActionBarAPI
|
||||
import de.moritzruth.spigot_ttt.game.players.PlayerManager
|
||||
import de.moritzruth.spigot_ttt.game.players.TTTPlayer
|
||||
import de.moritzruth.spigot_ttt.items.BuyableItem
|
||||
import de.moritzruth.spigot_ttt.items.ItemManager
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
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 {
|
||||
fun register() {
|
||||
plugin.server.pluginManager.registerEvents(this, plugin)
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
fun onInventoryClick(event: InventoryClickEvent) {
|
||||
if (event.whoClicked !is Player) return
|
||||
val tttPlayer = PlayerManager.getTTTPlayer(event.whoClicked as Player) ?: return
|
||||
|
||||
if (event.click === ClickType.CREATIVE) return
|
||||
event.isCancelled = true
|
||||
|
||||
val itemStack = event.currentItem
|
||||
if (
|
||||
(event.click === ClickType.RIGHT || event.click === ClickType.LEFT) &&
|
||||
itemStack !== null &&
|
||||
event.clickedInventory?.holder == tttPlayer.player &&
|
||||
Shop.SHOP_SLOTS.contains(event.slot)
|
||||
) {
|
||||
val tttItem = ItemManager.getItemByItemStack(itemStack)
|
||||
if (tttItem === null || tttItem !is BuyableItem || !tttItem.buyableBy.contains(tttPlayer.role)) return
|
||||
|
||||
if (tttPlayer.credits < tttItem.price) {
|
||||
ActionBarAPI.sendActionBar(tttPlayer.player, "${ChatColor.RED}Du hast nicht genug Credits")
|
||||
}
|
||||
|
||||
try {
|
||||
tttPlayer.addItem(tttItem)
|
||||
tttPlayer.credits -= tttItem.price
|
||||
} catch (e: TTTPlayer.AlreadyHasItemException) {
|
||||
ActionBarAPI.sendActionBar(tttPlayer.player, "${ChatColor.RED}Du hast dieses Item bereits")
|
||||
} catch (e: TTTPlayer.TooManyItemsOfTypeException) {
|
||||
ActionBarAPI.sendActionBar(tttPlayer.player, "${ChatColor.RED}Du hast keinen Platz dafür")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package de.moritzruth.spigot_ttt.utils
|
||||
|
||||
import de.moritzruth.spigot_ttt.plugin
|
||||
import org.bukkit.configuration.file.YamlConfiguration
|
||||
import java.nio.file.Path
|
||||
|
||||
class ConfigurationFile(name: String): YamlConfiguration() {
|
||||
private val filePath = Path.of(plugin.dataFolder.absolutePath, "$name.yml").toAbsolutePath().toString()
|
||||
|
||||
init {
|
||||
try {
|
||||
load(filePath)
|
||||
} catch (e: Exception) {}
|
||||
}
|
||||
|
||||
fun save() {
|
||||
save(filePath)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package de.moritzruth.spigot_ttt.utils
|
||||
|
||||
fun secondsToTicks(seconds: Double) = (seconds * 20).toInt()
|
||||
fun secondsToTicks(seconds: Int) = secondsToTicks(seconds.toDouble())
|
||||
fun heartsToHealth(hearts: Double) = hearts * 2
|
|
@ -0,0 +1,11 @@
|
|||
package de.moritzruth.spigot_ttt.utils
|
||||
|
||||
import org.bukkit.inventory.Inventory
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.inventory.PlayerInventory
|
||||
|
||||
fun Inventory.setAllToItem(indexes: Iterable<Int>, itemStack: ItemStack) {
|
||||
indexes.forEach { setItem(it, itemStack) }
|
||||
}
|
||||
|
||||
val PlayerInventory.hotbarContents get() = this.contents.slice(0..8) as List<ItemStack?>
|
|
@ -0,0 +1,14 @@
|
|||
package de.moritzruth.spigot_ttt.utils
|
||||
|
||||
import org.bukkit.Material
|
||||
|
||||
val maxDurabilitiesOfItems = mapOf(
|
||||
Material.WOODEN_HOE to 59,
|
||||
Material.STONE_HOE to 131,
|
||||
Material.GOLDEN_HOE to 32,
|
||||
Material.IRON_HOE to 250,
|
||||
Material.DIAMOND_HOE to 1561,
|
||||
Material.IRON_AXE to 250
|
||||
)
|
||||
|
||||
fun getMaxDurability(material: Material) = maxDurabilitiesOfItems[material] ?: throw Exception("The maximum durability of this item is not defined")
|
16
src/main/kotlin/de/moritzruth/spigot_ttt/utils/Noop.kt
Normal file
16
src/main/kotlin/de/moritzruth/spigot_ttt/utils/Noop.kt
Normal file
|
@ -0,0 +1,16 @@
|
|||
package de.moritzruth.spigot_ttt.utils
|
||||
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.command.CommandSender
|
||||
import org.bukkit.command.TabCompleter
|
||||
|
||||
fun noop() {}
|
||||
|
||||
class NoOpTabCompleter: TabCompleter {
|
||||
override fun onTabComplete(
|
||||
sender: CommandSender,
|
||||
command: Command,
|
||||
alias: String,
|
||||
args: Array<out String>
|
||||
) = mutableListOf<String>()
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package de.moritzruth.spigot_ttt.utils
|
||||
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun randomNumber(min: Int = 0, max: Int): Int = (min + (Math.random() * (max - min))).roundToInt()
|
|
@ -0,0 +1,33 @@
|
|||
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(item: ItemStack, duration: Double, startProgress: Double = 0.0, onFinish: () -> Unit): BukkitTask {
|
||||
val startedAt = Instant.now().toEpochMilli()
|
||||
|
||||
var task: BukkitTask? = null
|
||||
|
||||
task = plugin.server.scheduler.runTaskTimer(plugin, fun() {
|
||||
val secondsElapsed = (Instant.now().toEpochMilli() - startedAt) / 1000.0
|
||||
val progress = secondsElapsed / duration + startProgress
|
||||
|
||||
val maxDurability = getMaxDurability(item.type)
|
||||
|
||||
val damageMeta = item.itemMeta!! as Damageable
|
||||
damageMeta.damage = maxDurability - (maxDurability * progress).roundToInt()
|
||||
item.itemMeta = damageMeta as ItemMeta
|
||||
|
||||
if (progress >= 1) {
|
||||
task?.cancel()
|
||||
onFinish()
|
||||
}
|
||||
}, 0, 1)
|
||||
|
||||
return task
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package de.moritzruth.spigot_ttt.utils
|
||||
|
||||
import org.bukkit.entity.Player
|
||||
|
||||
fun teleportPlayerToWorldSpawn(player: Player) {
|
||||
player.teleport(player.world.spawnLocation)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package de.moritzruth.spigot_ttt.utils
|
||||
|
||||
import java.util.*
|
||||
|
||||
fun UUID.toTrimmedString() = toString().replace("-", "")
|
11
src/main/resources/config.yml
Normal file
11
src/main/resources/config.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
min-players: 4
|
||||
min-players-for:
|
||||
detective: 5
|
||||
jackal: 6
|
||||
|
||||
discord-token: xxx
|
||||
|
||||
duration:
|
||||
preparing: 20
|
||||
combat: 480
|
||||
over: 10
|
23
src/main/resources/plugin.yml
Normal file
23
src/main/resources/plugin.yml
Normal file
|
@ -0,0 +1,23 @@
|
|||
name: TTT
|
||||
version: 1.0.0
|
||||
api-version: "1.14"
|
||||
main: de.moritzruth.spigot_ttt.TTTPlugin
|
||||
depend:
|
||||
- CorpseReborn
|
||||
- ActionBarAPI
|
||||
|
||||
commands:
|
||||
start:
|
||||
usage: /start
|
||||
permission: ttt.start
|
||||
description: Starts the TTT game
|
||||
|
||||
abort:
|
||||
usage: /abort
|
||||
permission: ttt.abort
|
||||
description: Aborts the TTT game
|
||||
|
||||
additemspawn:
|
||||
usage: /additemspawn
|
||||
permission: ttt.additemspawn
|
||||
description: Add an item spawn
|
Reference in a new issue