7 Commits

Author SHA1 Message Date
ff6a2cc0df Gitea cache !
All checks were successful
Auto Build / Build (push) Successful in 1m31s
2025-12-07 08:54:45 +00:00
07d285957a 😭 Cache / no cache / recache
All checks were successful
Auto Build / Build (push) Successful in 4m45s
2025-12-07 08:35:29 +00:00
d4b3a43551 😮‍💨 try to cache gradle for fastest build
All checks were successful
Auto Build / Build (push) Successful in 18m54s
2025-12-06 17:20:55 +00:00
97b73b0c55 🦋 Debugging for the 1.2.
All checks were successful
Auto Build / Build (push) Successful in 2m40s
2025-12-06 16:40:12 +01:00
d6103b078b 😭 Tcoww -> Tcoww.instance
All checks were successful
Auto Build / Build (push) Successful in 4m26s
2025-11-30 19:04:28 +01:00
25804f76e0 Merge remote-tracking branch 'origin/master'
All checks were successful
Auto Build / Build (push) Successful in 2m55s
2025-11-30 17:23:17 +01:00
fa5a54c8b9 ✔️ Prepare plugin 1.2 version. 2025-11-30 17:22:48 +01:00
45 changed files with 2135 additions and 1534 deletions

View File

@@ -3,6 +3,8 @@ on: [push]
jobs:
Build:
env:
RUNNER_TOOL_CACHE: /toolcache
runs-on: ubuntu-latest
steps:
- name: Checkout sources
@@ -12,6 +14,8 @@ jobs:
with:
distribution: 'temurin'
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Build
run: ./gradlew build
- name: Upload build

View File

@@ -3,16 +3,18 @@ plugins {
id("com.gradleup.shadow") version "9.2.2"
id("xyz.jpenilla.run-paper") version "3.0.2"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
id("org.jlleitschuh.gradle.ktlint") version "14.0.1"
}
group = "fr.azur"
version = "1.1-SNAPSHOT"
val version: String by project
val minecraftVersion: String by project
val paperVersion: String by project
val skinRestorerVersion: String by project
val voiceChatVersion: String by project
val voiceChatApiVersion: String by project
val luckPermsVersion: String by project
repositories {
mavenCentral()
@@ -34,11 +36,11 @@ repositories {
dependencies {
paperweight.paperDevBundle("$minecraftVersion-$paperVersion")
implementation("net.skinsrestorer:skinsrestorer-api:$skinRestorerVersion")
implementation("de.maxhenkel.voicechat:voicechat-api:$voiceChatApiVersion")
compileOnly("net.skinsrestorer:skinsrestorer-api:$skinRestorerVersion")
compileOnly("de.maxhenkel.voicechat:voicechat-api:$voiceChatApiVersion")
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
}
val targetJavaVersion = 21
@@ -49,8 +51,9 @@ kotlin {
tasks {
runServer {
downloadPlugins {
modrinth("plasmo-voice", "bukkit-$voiceChatVersion")
modrinth("skinsrestorer", skinRestorerVersion)
modrinth("luckperms", "v$luckPermsVersion-bukkit")
modrinth("simple-voice-chat", "bukkit-$voiceChatVersion")
}
minecraftVersion(minecraftVersion)
}
@@ -69,8 +72,5 @@ tasks {
archiveBaseName.set("tcoww")
minimize()
// exclude("META-INF/*.kotlin_module")
// exclude("META-INF/*.version")
}
}
}

View File

@@ -1,5 +1,7 @@
version=1.2.0
paperVersion=R0.1-SNAPSHOT
minecraftVersion=1.21.8
skinRestorerVersion=15.8.2
minecraftVersion=1.21.10
skinRestorerVersion=15.9.0
voiceChatApiVersion=2.6.0
voiceChatVersion=2.6.6
voiceChatVersion=2.6.6
luckPermsVersion=5.5.17

View File

@@ -1,50 +1,75 @@
package fr.azur.tcoww
import de.maxhenkel.voicechat.api.BukkitVoicechatService
import fr.azur.tcoww.events.GameEvent
import fr.azur.tcoww.events.GameEvents
import fr.azur.tcoww.events.PhaseEvents
import fr.azur.tcoww.events.PowerEvents
import fr.azur.tcoww.events.ToolsEvents
import fr.azur.tcoww.roles.*
import fr.azur.tcoww.utils.Skin
import fr.azur.tcoww.voicechat.VoiceChatPlugin
import net.skinsrestorer.api.SkinsRestorer
import net.skinsrestorer.api.SkinsRestorerProvider
import fr.azur.tcoww.events.UIEvents
import fr.azur.tcoww.roles.Child
import fr.azur.tcoww.roles.FortuneTeller
import fr.azur.tcoww.roles.Hunter
import fr.azur.tcoww.roles.Role
import fr.azur.tcoww.roles.Villager
import fr.azur.tcoww.roles.Werewolf
import fr.azur.tcoww.roles.Witch
import fr.azur.tcoww.utils.VoiceChatPlugin
import fr.azur.tcoww.utils.skins.Manager
import fr.azur.tcoww.utils.skins.WerewolfSkin
import org.bukkit.configuration.serialization.ConfigurationSerialization
import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.scoreboard.Team
class Tcoww : JavaPlugin() {
private lateinit var skinsRestorer: SkinsRestorer
private lateinit var skinManager: Skin
init {
instance = this
}
override fun onEnable() {
saveResource("config.yml", false)
ConfigurationSerialization.registerClass(WerewolfSkin::class.java)
saveDefaultConfig()
skinsRestorer = SkinsRestorerProvider.get()
skinManager = Skin(this, skinsRestorer)
Manager.handle()
server.pluginManager.registerEvents(ToolsEvents(this), this)
server.pluginManager.registerEvents(GameEvent(this, skinManager), this)
server.pluginManager.apply {
registerEvents(UIEvents, this@Tcoww)
registerEvents(GameEvents, this@Tcoww)
registerEvents(PowerEvents, this@Tcoww)
registerEvents(ToolsEvents, this@Tcoww)
registerEvents(PhaseEvents, this@Tcoww)
}
val vcservice = server.servicesManager.load(BukkitVoicechatService::class.java)
vcservice?.registerPlugin(VoiceChatPlugin())
val service = server.servicesManager.load(BukkitVoicechatService::class.java)
service?.registerPlugin(VoiceChatPlugin)
registerRoles()
val mainScoreboard = server.scoreboardManager.mainScoreboard
if (mainScoreboard.getTeam("NoCollision") == null) {
val team = server.scoreboardManager.mainScoreboard.registerNewTeam("NoCollision")
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER)
}
}
override fun onDisable() {
}
fun reload() {
this.reloadConfig()
skinManager.reloadSkin()
Manager.reloadSkin()
}
private fun registerRoles() {
Role.registerRole(Villager())
Role.registerRole(Werewolf())
Role.registerRole(Child())
Role.registerRole(FortuneTeller())
Role.registerRole(Hunter())
Role.registerRole(Witch())
Role.registerRole(Villager)
Role.registerRole(Werewolf)
Role.registerRole(Child)
Role.registerRole(FortuneTeller)
Role.registerRole(Hunter)
Role.registerRole(Witch)
}
companion object {
lateinit var instance: Tcoww
}
}

View File

@@ -1,26 +1,26 @@
package fr.azur.tcoww
import fr.azur.tcoww.commands.Exclude
import fr.azur.tcoww.commands.Power
import fr.azur.tcoww.commands.GameCommand
import fr.azur.tcoww.commands.ReloadCommands
import fr.azur.tcoww.commands.TimeGest
import fr.azur.tcoww.commands.Tools
import fr.azur.tcoww.commands.Vote
import io.papermc.paper.plugin.bootstrap.BootstrapContext
import io.papermc.paper.plugin.bootstrap.PluginBootstrap
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents
class TcowwBootstrap : PluginBootstrap {
object TcowwBootstrap : PluginBootstrap {
override fun bootstrap(context: BootstrapContext) {
context.lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS) { commands ->
commands.registrar().register(Tools().root.build())
commands.registrar().register(TimeGest().root.build())
commands.registrar().register(Power().root.build())
commands.registrar().register(GameCommand().root.build())
commands.registrar().register(Vote().root.build())
commands.registrar().register(ReloadCommands().root.build())
commands.registrar().register(Exclude().root.build())
context.lifecycleManager.apply {
registerEventHandler(LifecycleEvents.COMMANDS) { commands ->
commands.registrar().apply {
register(Tools.root)
register(GameCommand.root)
commands.registrar().register(Vote.root)
register(ReloadCommands.root)
commands.registrar().register(Exclude.root)
}
}
}
}
}
}

View File

@@ -1,40 +1,34 @@
package fr.azur.tcoww.commands
import com.mojang.brigadier.Command
import com.mojang.brigadier.builder.LiteralArgumentBuilder
import com.mojang.brigadier.tree.LiteralCommandNode
import fr.azur.tcoww.game.Game
import fr.azur.tcoww.utils.Players.kill
import io.papermc.paper.command.brigadier.CommandSourceStack
import io.papermc.paper.command.brigadier.Commands
import io.papermc.paper.command.brigadier.argument.ArgumentTypes
import io.papermc.paper.command.brigadier.argument.resolvers.PlayerProfileListResolver
import org.bukkit.Bukkit
import org.bukkit.GameMode
class Exclude {
val root: LiteralArgumentBuilder<CommandSourceStack> = Commands.literal("exclude")
.requires { sender ->
sender.sender.isOp
}.then(
Commands.literal("start")
.then(
Commands.argument("players", ArgumentTypes.playerProfiles()).executes { ctx ->
val targetResolver = ctx.getArgument("players", PlayerProfileListResolver::class.java)
val target = targetResolver.resolve(ctx.source).first()
val uuid = target.id
if (uuid != null) {
val player = Bukkit.getPlayer(uuid)
if (player != null) {
val game = Game.current ?: return@executes Command.SINGLE_SUCCESS
player.gameMode = GameMode.SPECTATOR
player.isInvulnerable = false
game.playersMutable.remove(player)
}
object Exclude {
val root: LiteralCommandNode<CommandSourceStack> =
Commands
.literal("exclude")
.requires { sender ->
sender.sender.isOp
}.then(
Commands.argument("players", ArgumentTypes.playerProfiles()).executes { ctx ->
val targetResolver = ctx.getArgument("players", PlayerProfileListResolver::class.java)
val target = targetResolver.resolve(ctx.source).first()
val uuid = target.id
if (uuid != null) {
val player = Bukkit.getPlayer(uuid)
if (player != null) {
val game = Game.current ?: return@executes Command.SINGLE_SUCCESS
player.kill()
}
Command.SINGLE_SUCCESS
}
)
)
}
Command.SINGLE_SUCCESS
},
).build()
}

View File

@@ -1,67 +1,90 @@
package fr.azur.tcoww.commands
import com.mojang.brigadier.Command
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.builder.LiteralArgumentBuilder
import com.mojang.brigadier.tree.LiteralCommandNode
import fr.azur.tcoww.Tcoww
import fr.azur.tcoww.game.Game
import fr.azur.tcoww.roles.Role
import fr.azur.tcoww.roles.Villager
import fr.azur.tcoww.roles.Werewolf
import fr.azur.tcoww.ui.GameConfig
import io.papermc.paper.command.brigadier.CommandSourceStack
import io.papermc.paper.command.brigadier.Commands
import io.papermc.paper.command.brigadier.argument.ArgumentTypes
import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver
import io.papermc.paper.dialog.Dialog
import io.papermc.paper.registry.data.dialog.ActionButton
import io.papermc.paper.registry.data.dialog.DialogBase
import io.papermc.paper.registry.data.dialog.action.DialogAction
import io.papermc.paper.registry.data.dialog.type.DialogType
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.event.ClickEvent
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Bukkit
import org.bukkit.GameMode
import org.bukkit.NamespacedKey
import org.bukkit.World
import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin
class GameCommand {
val root: LiteralArgumentBuilder<CommandSourceStack> = Commands.literal("game")
.requires { sender ->
sender.sender.isOp
}.then(
Commands.literal("start")
.then(
Commands.argument("players", ArgumentTypes.players()).then(
Commands.argument("roles", StringArgumentType.greedyString()).suggests { ctx, builder ->
val roleswithoutauto =
Role.registerRoles.filter { role -> role.value !is Villager && role.value !is Werewolf }
val rolessplit = builder.remaining.split(" ")
val startstr = rolessplit.dropLast(1).joinToString(separator = " ")
roleswithoutauto.forEach { role ->
if (role.key.toString().startsWith(rolessplit.last())) {
builder.suggest("$startstr${if (startstr.isEmpty()) "" else " "}${role.key}")
}
}
if (builder.remaining != "" && !builder.remaining.endsWith(" ")) {
roleswithoutauto.forEach { role ->
builder.suggest("${builder.remaining} ${role.key}")
}
}
builder.buildFuture()
}.executes { ctx ->
val plugin = Bukkit.getPluginManager().getPlugin("tcoww")!!
val targetResolver = ctx.getArgument("players", PlayerSelectorArgumentResolver::class.java)
val targets = targetResolver.resolve(ctx.source)
val rolesstr = StringArgumentType.getString(ctx, "roles")
val rolessplit = rolesstr.split(" ")
val roles = rolessplit.map { str ->
Role.registerRoles[NamespacedKey.fromString(str)]
}
val correctrole = roles.filterNotNull()
Game(plugin, ctx.source.location.world, targets, correctrole)
Command.SINGLE_SUCCESS
}
)
object GameCommand {
private fun createDialog(
plugin: Plugin,
world: World,
players: Iterable<Player>,
roles: Map<NamespacedKey, Int>,
): Dialog =
Dialog.create { builder ->
builder
.empty()
.base(
DialogBase
.builder(Component.text("Start the Game", NamedTextColor.GREEN))
.canCloseWithEscape(true)
.build(),
).type(
DialogType.confirmation(
ActionButton
.builder(Component.text("Start", NamedTextColor.GREEN))
.action(
DialogAction.staticAction(
ClickEvent.callback {
Game(plugin, world, players, roles)
it.closeDialog()
},
),
).build(),
ActionButton
.builder(Component.text("Cancel", NamedTextColor.RED))
.action(DialogAction.staticAction(ClickEvent.callback { it.closeDialog() }))
.build(),
),
)
).then(
Commands.literal("stop").executes { ctx ->
Game.current?.stopGame(true)
Command.SINGLE_SUCCESS
}
)
}
}
val root: LiteralCommandNode<CommandSourceStack> =
Commands
.literal("game")
.requires { sender ->
sender.sender.isOp
}.then(
Commands
.literal("start")
.executes { ctx ->
val executor = ctx.source.executor ?: ctx.source.sender
if (executor is Player) {
val players =
Bukkit
.getOnlinePlayers()
.filter { it.gameMode == GameMode.ADVENTURE || it.gameMode == GameMode.SURVIVAL }
val menu = GameConfig(players.size)
executor.openInventory(menu.inventory)
menu.future.thenAccept {
val dialog = createDialog(Tcoww.instance, executor.world, players, it)
executor.showDialog(dialog)
}
}
Command.SINGLE_SUCCESS
},
).then(
Commands.literal("stop").executes { ctx ->
Game.current?.stopGame(true)
Command.SINGLE_SUCCESS
},
).build()
}

View File

@@ -1,156 +0,0 @@
package fr.azur.tcoww.commands
import com.mojang.brigadier.Command
import com.mojang.brigadier.builder.LiteralArgumentBuilder
import fr.azur.tcoww.game.Game
import fr.azur.tcoww.roles.FortuneTeller
import fr.azur.tcoww.roles.Role
import fr.azur.tcoww.roles.Witch
import fr.azur.tcoww.utils.Stockage
import io.papermc.paper.command.brigadier.CommandSourceStack
import io.papermc.paper.command.brigadier.Commands
import io.papermc.paper.command.brigadier.argument.ArgumentTypes
import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.event.HoverEvent
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Bukkit
import org.bukkit.NamespacedKey
import org.bukkit.entity.Player
import org.bukkit.persistence.PersistentDataType
import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType
class Power {
val root: LiteralArgumentBuilder<CommandSourceStack> = Commands.literal("power")
.requires { ctx -> ctx.sender as? Player != null && Role.getRole(ctx.sender as Player)?.hasPowerCommand == true }
.then(
Commands.literal("kill")
.requires { ctx ->
val player = ctx.executor as? Player ?: return@requires false
val role = Role.getRole(player)
(role is Witch && player.hasPowerItem(1))
}
.then(
Commands.argument("target", ArgumentTypes.player())
.suggests { _, builder ->
Game.current?.playersMutable?.forEach { builder.suggest(it.name) }
builder.buildFuture()
}
.executes { ctx ->
val target = ctx.getTargetPlayer("target") ?: return@executes 0
val player = ctx.source.getPlayer() ?: return@executes 0
if (Game.current?.playersMutable?.contains(target) == true) {
target.addPotionEffect(PotionEffect(PotionEffectType.WITHER, 600, 4))
player.removePowerItem(1)
}
Command.SINGLE_SUCCESS
}
)
)
.then(
Commands.literal("save")
.requires { ctx ->
val player = ctx.executor as? Player ?: return@requires false
val role = Role.getRole(player)
role is Witch && player.hasPowerItem(2)
}
.then(
Commands.argument("target", ArgumentTypes.player())
.executes { ctx ->
val target = ctx.getTargetPlayer("target") ?: return@executes 0
val player = ctx.source.getPlayer() ?: return@executes 0
if (Game.current?.playersMutable?.contains(target) == true &&
target.persistentDataContainer.get(
NamespacedKey("tcoww", "dead"),
PersistentDataType.BOOLEAN
) == true
) {
target.persistentDataContainer.set(
NamespacedKey("tcoww", "dead"),
PersistentDataType.BOOLEAN,
false
)
Stockage.backLocation[target.uniqueId]?.let { target.teleport(it) }
target.isInvulnerable = false
target.sendMessage(Component.text("Vous avez été résucité.").color(NamedTextColor.AQUA))
player.sendMessage(
Component.text("Vous avez résuciter ").color(NamedTextColor.AQUA)
.append(player.displayName())
)
player.removePowerItem(2)
}
Command.SINGLE_SUCCESS
}
)
)
.then(
Commands.literal("view")
.requires { ctx ->
val player = ctx.executor as? Player ?: return@requires false
val role = Role.getRole(player)
role is FortuneTeller && player.hasPowerItem(4)
}
.then(
Commands.argument("target", ArgumentTypes.player())
.suggests { _, builder ->
Game.current?.playersMutable?.forEach { builder.suggest(it.name) }
builder.buildFuture()
}
.executes { ctx ->
val target = ctx.getTargetPlayer("target") ?: return@executes 0
val player = ctx.source.getPlayer() ?: return@executes 0
val plugin = Bukkit.getPluginManager().getPlugin("tcoww") ?: return@executes 0
if (Game.current?.playersMutable?.contains(target) == true) {
val item = player.getPowerItem(4) ?: return@executes 0
val timeGestion = Game.current!!.timeGestion
val cooldown =
((timeGestion.dayDuration + timeGestion.voteDuration + timeGestion.nightDuration + timeGestion.crepuscularDuration - 30) * 2) * 20
val cloneditem = item.clone()
player.inventory.remove(item)
Bukkit.getScheduler().runTaskLater(plugin, Runnable {
player.inventory.addItem(cloneditem)
}, cooldown.toLong())
val targetrole = Role.getRole(target)
player.sendMessage(
target.displayName().append(Component.text(" est "))
.append(
(targetrole?.displayName ?: Component.text("Inconnu").hoverEvent(
HoverEvent.showText(
targetrole?.description ?: Component.empty()
)
))
)
)
}
Command.SINGLE_SUCCESS
}
)
)
private fun CommandSourceStack.getPlayer(): Player? = (this.executor ?: this.sender) as? Player
private fun Player.hasPowerItem(id: Int): Boolean = this.inventory.any {
it?.persistentDataContainer?.get(NamespacedKey("tcoww", "power"), PersistentDataType.INTEGER) == id
}
private fun Player.getPowerItem(id: Int) = this.inventory.firstOrNull {
it?.persistentDataContainer?.get(NamespacedKey("tcoww", "power"), PersistentDataType.INTEGER) == id
}
private fun Player.removePowerItem(id: Int) {
val item = getPowerItem(id) ?: return
this.inventory.remove(item)
}
private fun com.mojang.brigadier.context.CommandContext<CommandSourceStack>.getTargetPlayer(name: String): Player? {
val resolver = this.getArgument(name, PlayerSelectorArgumentResolver::class.java)
return resolver.resolve(this.source).firstOrNull()
}
}

View File

@@ -1,18 +1,21 @@
package fr.azur.tcoww.commands
import com.mojang.brigadier.Command
import com.mojang.brigadier.builder.LiteralArgumentBuilder
import com.mojang.brigadier.tree.LiteralCommandNode
import io.papermc.paper.command.brigadier.CommandSourceStack
import io.papermc.paper.command.brigadier.Commands
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.entity.Player
class ReloadCommands {
val root: LiteralArgumentBuilder<CommandSourceStack> = Commands.literal("reloadcommands").executes { ctx ->
val sender = ctx.source.sender as? Player
sender?.updateCommands()
sender?.sendMessage(Component.text("Commands rechargés !").color(NamedTextColor.GOLD))
Command.SINGLE_SUCCESS
}
}
object ReloadCommands {
val root: LiteralCommandNode<CommandSourceStack> =
Commands
.literal("reload-commands")
.executes { ctx ->
val sender = ctx.source.sender as? Player
sender?.updateCommands()
sender?.sendMessage(Component.text("Commands rechargés !", NamedTextColor.GOLD))
Command.SINGLE_SUCCESS
}.build()
}

View File

@@ -1,44 +0,0 @@
package fr.azur.tcoww.commands
import com.mojang.brigadier.Command
import com.mojang.brigadier.builder.LiteralArgumentBuilder
import fr.azur.tcoww.game.TimeGestion
import io.papermc.paper.command.brigadier.CommandSourceStack
import io.papermc.paper.command.brigadier.Commands
import org.bukkit.Bukkit
import org.bukkit.plugin.Plugin
import org.bukkit.scheduler.BukkitTask
class TimeGest {
var timeGestion: TimeGestion? = null
var sheduler: BukkitTask? = null
var plugin: Plugin? = null
private fun kill() {
sheduler?.cancel()
sheduler = null
timeGestion = null
}
val root: LiteralArgumentBuilder<CommandSourceStack> = Commands.literal("timegest")
.requires { sender ->
sender.sender.isOp
}.then(
Commands.literal("start").executes { ctx ->
if (plugin == null) plugin = Bukkit.getPluginManager().getPlugin("tcoww")!!
val executor = ctx.source.executor
if (executor == null) throw Error("No executor !")
if (timeGestion != null || sheduler != null) kill()
timeGestion = TimeGestion(plugin!!, executor.world)
sheduler = Bukkit.getScheduler().runTaskTimer(plugin!!, Runnable {
timeGestion!!.tick()
}, 0L, 1L)
Command.SINGLE_SUCCESS
}
).then(
Commands.literal("kill").executes {
kill()
Command.SINGLE_SUCCESS
}
)
}

View File

@@ -3,7 +3,7 @@ package fr.azur.tcoww.commands
import com.mojang.brigadier.Command
import com.mojang.brigadier.arguments.IntegerArgumentType
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.builder.LiteralArgumentBuilder
import com.mojang.brigadier.tree.LiteralCommandNode
import fr.azur.tcoww.Tcoww
import fr.azur.tcoww.items.CustomItems
import io.papermc.paper.command.brigadier.CommandSourceStack
@@ -16,131 +16,139 @@ import org.bukkit.Bukkit
import org.bukkit.Location
import org.bukkit.entity.Player
class Tools {
val root: LiteralArgumentBuilder<CommandSourceStack> = Commands.literal("tools")
.requires { sender ->
sender.sender.isOp
}.then(
Commands.literal("location")
.then(
Commands.literal("bed").executes { ctx ->
val player = ctx.source.executor as Player
player.inventory.addItem(CustomItems.BedLocTool.item)
Command.SINGLE_SUCCESS
}.then(
Commands.literal("get").executes { ctx ->
ctx.source.sender.sendMessage(sendBed())
Command.SINGLE_SUCCESS
}
).then(
Commands.literal("remove").then(
Commands.argument("index", IntegerArgumentType.integer(0))
.executes { ctx ->
val plugin = Bukkit.getPluginManager().getPlugin("tcoww") ?: return@executes 0
val index = IntegerArgumentType.getInteger(ctx, "index")
val list = plugin.config.getMapList("bedLocation")
plugin.config.set("bedLocation", list.minus(list[index]))
plugin.saveConfig()
object Tools {
val root: LiteralCommandNode<CommandSourceStack> =
Commands
.literal("tools")
.requires { sender ->
sender.sender.isOp
}.then(
Commands
.literal("location")
.then(
Commands
.literal("bed")
.executes { ctx ->
val player = ctx.source.executor as Player
player.inventory.addItem(CustomItems.BedLocTool.item)
Command.SINGLE_SUCCESS
}.then(
Commands.literal("get").executes { ctx ->
ctx.source.sender.sendMessage(sendBed())
Command.SINGLE_SUCCESS
}
))
).then(
Commands.literal("spawn").executes { ctx ->
val player = ctx.source.executor as Player
player.inventory.addItem(CustomItems.SpawnLocTool.item)
Command.SINGLE_SUCCESS
}
).then(
Commands.literal("lobby").executes { ctx ->
val player = ctx.source.executor as Player
player.inventory.addItem(CustomItems.LobbyLocTool.item)
Command.SINGLE_SUCCESS
}
)
).then(
Commands.literal("reload").executes {
val plugin = Bukkit.getPluginManager().getPlugin("tcoww") as Tcoww
plugin.reload()
Command.SINGLE_SUCCESS
}
).then(
Commands.literal("customitem").then(
Commands.argument("item", StringArgumentType.greedyString())
.suggests { _, builder ->
CustomItems.entries.forEach {
builder.suggest(it.name)
}
builder.buildFuture()
}
.executes { ctx ->
val itemName = StringArgumentType.getString(ctx, "item")
val item = CustomItems.valueOf(itemName).item
val exe = ctx.source.executor
if (exe is Player) {
exe.inventory.addItem(item)
}
Command.SINGLE_SUCCESS
}
)
)
},
).then(
Commands.literal("remove").then(
Commands
.argument("index", IntegerArgumentType.integer(0))
.executes { ctx ->
val plugin = Bukkit.getPluginManager().getPlugin("tcoww") ?: return@executes 0
val index = IntegerArgumentType.getInteger(ctx, "index")
val list =
plugin.config
.getList("bed-locations")
.orEmpty()
.mapNotNull { it as? Location }
plugin.config.set("bed-locations", list.drop(index))
plugin.saveConfig()
ctx.source.sender.sendMessage(sendBed())
Command.SINGLE_SUCCESS
},
),
),
).then(
Commands.literal("spawn").executes { ctx ->
val player = ctx.source.executor as Player
player.inventory.addItem(CustomItems.SpawnLocTool.item)
Command.SINGLE_SUCCESS
},
).then(
Commands.literal("lobby").executes { ctx ->
val player = ctx.source.executor as Player
player.inventory.addItem(CustomItems.LobbyLocTool.item)
Command.SINGLE_SUCCESS
},
),
).then(
Commands.literal("reload").executes {
val plugin = Bukkit.getPluginManager().getPlugin("tcoww") as Tcoww
plugin.reload()
Command.SINGLE_SUCCESS
},
).then(
Commands.literal("customitem").then(
Commands
.argument("item", StringArgumentType.greedyString())
.suggests { _, builder ->
CustomItems.entries.forEach {
builder.suggest(it.name)
}
builder.buildFuture()
}.executes { ctx ->
val itemName = StringArgumentType.getString(ctx, "item")
val item = CustomItems.valueOf(itemName).item
val exe = ctx.source.executor
if (exe is Player) {
exe.inventory.addItem(item)
}
Command.SINGLE_SUCCESS
},
),
).build()
fun sendBed(): Component {
val plugin = Bukkit.getPluginManager().getPlugin("tcoww") ?: return Component.empty()
val bedlist = plugin.config.getMapList("bedLocation")
val bedLocationComponent = bedlist.map {
if (it as? Map<String, Object> != null) {
val loc = Location.deserialize(it as Map<String, Object>)
Component.text("[${loc.x}, ${loc.y}, ${loc.z}] ")
.color(NamedTextColor.LIGHT_PURPLE)
.append(
Component.text("teleport")
.clickEvent(
ClickEvent.runCommand("/minecraft:tp ${loc.x.toInt()} ${loc.y.toInt()} ${loc.z.toInt()}")
)
.color(NamedTextColor.LIGHT_PURPLE)
.decorate(TextDecoration.UNDERLINED)
)
.appendSpace()
.append(
Component.text("remove")
.clickEvent(
ClickEvent.runCommand(
"/tcoww:tools location bed remove ${
bedlist.indexOf(
it
)
}"
)
)
.color(NamedTextColor.LIGHT_PURPLE)
.decorate(TextDecoration.UNDERLINED)
)
.appendNewline()
} else Component.text("[unkown location] ")
.color(NamedTextColor.LIGHT_PURPLE)
.append(
Component.text("remove")
.clickEvent(
ClickEvent.runCommand(
"/tcoww:tools location bed remove ${
bedlist.indexOf(
it
)
}"
)
)
val bedList =
plugin.config
.getList("bed-locations")
.orEmpty()
val bedLocationComponent =
bedList.mapIndexed { index, element ->
if (element is Location) {
Component
.text("[${element.x}, ${element.y}, ${element.z}] ")
.color(NamedTextColor.LIGHT_PURPLE)
.decorate(TextDecoration.UNDERLINED)
)
.appendNewline()
}
return Component.text("Current bed locations :")
.append(
Component
.text("teleport")
.clickEvent(
ClickEvent.runCommand("/minecraft:tp ${element.x} ${element.y} ${element.z}"),
).color(NamedTextColor.LIGHT_PURPLE)
.decorate(TextDecoration.UNDERLINED),
).appendSpace()
.append(
Component
.text("remove")
.clickEvent(
ClickEvent.runCommand(
"/tcoww:tools location bed remove $index",
),
).color(NamedTextColor.LIGHT_PURPLE)
.decorate(TextDecoration.UNDERLINED),
).appendNewline()
} else {
Component
.text("[unkown location] ")
.color(NamedTextColor.LIGHT_PURPLE)
.append(
Component
.text("remove")
.clickEvent(
ClickEvent.runCommand(
"/tcoww:tools location bed remove $index",
),
).color(NamedTextColor.LIGHT_PURPLE)
.decorate(TextDecoration.UNDERLINED),
).appendNewline()
}
}
return Component
.text("Current bed locations :")
.color(NamedTextColor.DARK_PURPLE)
.appendNewline()
.append(bedLocationComponent)
}
}
}

View File

@@ -1,32 +1,53 @@
package fr.azur.tcoww.commands
import com.mojang.brigadier.Command
import com.mojang.brigadier.builder.LiteralArgumentBuilder
import com.mojang.brigadier.tree.LiteralCommandNode
import fr.azur.tcoww.game.Game
import fr.azur.tcoww.game.Phase
import fr.azur.tcoww.utils.Stockage
import fr.azur.tcoww.game.NightLightCycle
import fr.azur.tcoww.ui.PlayerSelectMenu
import io.papermc.paper.command.brigadier.CommandSourceStack
import io.papermc.paper.command.brigadier.Commands
import io.papermc.paper.command.brigadier.argument.ArgumentTypes
import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver
import org.bukkit.entity.Player
import java.util.UUID
class Vote {
val root: LiteralArgumentBuilder<CommandSourceStack> = Commands.literal("vote").then(
Commands.argument("target", ArgumentTypes.player())
.requires { ctx ->
Game.current?.timeGestion?.phase == Phase.VOTE && Game.current!!.playersMutable.contains(ctx.sender)
}.suggests { ctx, builder ->
Game.current?.playersMutable?.forEach { player -> builder.suggest(player.name) }
builder.buildFuture()
}.executes { ctx ->
val targetResolver = ctx.getArgument("target", PlayerSelectorArgumentResolver::class.java)
val target = targetResolver.resolve(ctx.source).first()
object Vote {
var vote = mutableMapOf<UUID, UUID>()
val root: LiteralCommandNode<CommandSourceStack> =
Commands
.literal("vote")
.then(
Commands
.argument("target", ArgumentTypes.player())
.requires { ctx ->
Game.current?.timeGestion?.phase == NightLightCycle.Phase.VOTE &&
Game.current!!.remainingPlayer.contains(ctx.sender)
}.suggests { _, builder ->
Game.current?.remainingPlayer?.forEach { player -> builder.suggest(player.name) }
builder.buildFuture()
}.executes { ctx ->
val targetResolver = ctx.getArgument("target", PlayerSelectorArgumentResolver::class.java)
val target = targetResolver.resolve(ctx.source).first()
val player = (ctx.source.executor ?: ctx.source.sender as Player) as Player
if (Game.current?.remainingPlayer?.contains(target) ?: false) {
vote[player.uniqueId] = target.uniqueId
}
Command.SINGLE_SUCCESS
},
).executes { ctx ->
val current = Game.current ?: return@executes Command.SINGLE_SUCCESS
val player = (ctx.source.executor ?: ctx.source.sender as Player) as Player
if (Game.current?.playersMutable?.contains(target) ?: false) {
Stockage.vote[player.uniqueId] = target.uniqueId
val menu = PlayerSelectMenu(current.remainingPlayer)
player.openInventory(menu.inventory)
menu.future.whenComplete { selectedPlayer, _ ->
val target = selectedPlayer ?: return@whenComplete
vote[player.uniqueId] = target.uniqueId
}
Command.SINGLE_SUCCESS
}
)
}
}.build()
}

View File

@@ -1,421 +0,0 @@
package fr.azur.tcoww.events
import fr.azur.tcoww.game.Game
import fr.azur.tcoww.game.Phase
import fr.azur.tcoww.items.CustomItems
import fr.azur.tcoww.roles.Child
import fr.azur.tcoww.roles.Role
import fr.azur.tcoww.roles.Witch
import fr.azur.tcoww.utils.BedGestion
import fr.azur.tcoww.utils.Skin
import fr.azur.tcoww.utils.Stockage
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.event.ClickEvent
import net.kyori.adventure.text.format.NamedTextColor
import net.kyori.adventure.text.format.TextDecoration
import net.kyori.adventure.title.Title
import org.bukkit.*
import org.bukkit.attribute.Attribute
import org.bukkit.attribute.AttributeModifier
import org.bukkit.entity.EntityType
import org.bukkit.entity.ItemDisplay
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority
import org.bukkit.event.Listener
import org.bukkit.event.entity.EntityDamageByEntityEvent
import org.bukkit.event.entity.PlayerDeathEvent
import org.bukkit.event.inventory.InventoryMoveItemEvent
import org.bukkit.event.player.*
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.SkullMeta
import org.bukkit.persistence.PersistentDataType
import org.bukkit.plugin.Plugin
import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType
import java.time.Duration
class GameEvent(val plugin: Plugin, val skinUtils: Skin) : Listener {
@EventHandler
fun phaseChange(event: TimePhaseChangeEvent) {
Game.current?.playersMutable?.forEach { it.updateCommands() }
when (event.newPhase) {
Phase.DAY -> {
event.world.players.forEach { player ->
player.sendMessage(Component.text("Le jour se lève.").color(NamedTextColor.GOLD))
player.clearActivePotionEffects()
player.persistentDataContainer.set(
NamespacedKey("tcoww", "insomie"),
PersistentDataType.BOOLEAN,
false
)
if (player.role?.lg == true) {
player.inventory.forEach {
if (it != null && it.persistentDataContainer.get(
NamespacedKey("tcoww", "power"),
PersistentDataType.INTEGER
) == 3
) {
player.inventory.remove(it)
}
if (player.isTransformed()) {
player.tfHuman()
}
}
}
}
}
Phase.VOTE -> {
event.world.players.forEach { player ->
player.sendMessage(
Component.text("Le jour se couche, ce soir un vote est organisé.")
.color(NamedTextColor.DARK_AQUA)
.appendNewline()
.append(
Component.text("Faite /vote <pseudo> pour voter.")
.color(NamedTextColor.AQUA)
)
)
}
}
Phase.NIGHT -> {
if (Stockage.vote.isNotEmpty()) {
val voteMap = Stockage.vote.values.groupingBy { it }.eachCount()
val mostVoted = voteMap.maxByOrNull { it.value }?.key
mostVoted?.let { uuid ->
Bukkit.getPlayer(uuid)?.let { votedPlayer ->
votedPlayer.fireTicks = 1200
Bukkit.getScheduler().runTaskLater(plugin, Runnable {
votedPlayer.health = 0.0
}, 60)
Game.current?.playersMutable?.forEach {
it.sendMessage(
Component.text("${votedPlayer.name} a reçu le plus de vote.")
.color(NamedTextColor.RED)
)
}
}
}
Stockage.vote.clear()
}
Game.current?.wwcankill = true
event.world.players.forEach { player ->
player.sendMessage(
Component.text("La lune se léve et les loups sont de sortie. Vous devriez dormir.")
.color(NamedTextColor.DARK_PURPLE)
)
if (player.role?.lg ?: false) {
player.sendMessage(
Component.text("Vous pouvez vous transformer.").color(NamedTextColor.DARK_PURPLE)
)
player.inventory.addItem(CustomItems.WereWolfTransformItem.item)
}
}
Bukkit.getScheduler().runTaskLater(plugin, Runnable {
val openBed = plugin.config.getMapList("bedLocation").mapNotNull {
if (it as? Map<String, Object> != null) Location.deserialize(it as Map<String, Object>) else null
}.filter { !BedGestion.isOcupied(it.block) }.toMutableList()
Game.current?.playersMutable?.forEach { player ->
if (!player.isSleeping && !player.persistentDataContainer.getOrDefault(
NamespacedKey("tcoww", "dead"),
PersistentDataType.BOOLEAN,
false
) && !player.isTransformed()
) {
val loc = openBed.removeFirst()
player.sleep(loc, true)
val role = player.role
if (role is Child || role?.lg == true) {
player.sendMessage(
Component.text(
"Tout le monde est couché. Vous pouvez vous lever.",
NamedTextColor.AQUA
)
)
}
}
}
}, plugin.config.getLong("playerSleep"))
}
Phase.CREPUSCULAR -> {
val current = Game.current ?: return
val deadPlayers = current.playersMutable.filter {
it.persistentDataContainer.get(NamespacedKey("tcoww", "dead"), PersistentDataType.BOOLEAN) == true
}
deadPlayers.forEach { player ->
player.gameMode = GameMode.SPECTATOR
Stockage.backLocation[player.uniqueId]?.let { player.teleport(it) }
player.kill(current)
}
current.playersMutable.removeAll(deadPlayers)
if (current.gameEnded()) {
current.playersMutable.forEach {
if (it.isTransformed()) it.tfHuman()
}
current.stopGame()
}
}
}
}
@EventHandler
fun powerItemDrop(event: PlayerDropItemEvent) {
val item = event.itemDrop.itemStack
if (event.player.gameMode != GameMode.CREATIVE &&
item.itemMeta.persistentDataContainer.has(NamespacedKey("tcoww", "power"), PersistentDataType.INTEGER)
) {
event.isCancelled = true
}
}
@EventHandler
fun powerItemTransfer(event: InventoryMoveItemEvent) {
val item = event.item
if (event.source != event.destination &&
item.itemMeta.persistentDataContainer.has(NamespacedKey("tcoww", "power"), PersistentDataType.INTEGER)
) {
event.isCancelled = true
}
}
@EventHandler
fun sleepLeaveEvent(event: PlayerBedLeaveEvent) {
val role = event.player.role
if (Game.current?.timeGestion?.phase == Phase.NIGHT &&
role !is Child && role?.lg != true &&
!event.player.persistentDataContainer.getOrDefault(
NamespacedKey("tcoww", "insomie"),
PersistentDataType.BOOLEAN,
false
)
) {
event.isCancelled = true
}
}
@EventHandler
fun clickEvent(event: PlayerInteractEvent) {
val item = event.item ?: return
if (item.persistentDataContainer.get(NamespacedKey("tcoww", "power"), PersistentDataType.INTEGER) == 3) {
val player = event.player
player.inventory.remove(item)
if (player.isTransformed()) {
player.tfHuman()
} else {
player.tfWerewolf()
}
}
}
@EventHandler
fun onDeath(event: PlayerDeathEvent) {
val current = Game.current ?: return
val player = event.player
if (!current.playersMutable.contains(player)) return
event.isCancelled = true
if (current.timeGestion.phase == Phase.NIGHT) {
Stockage.backLocation[player.uniqueId] = player.location
Bukkit.getScheduler().runTaskLater(plugin, Runnable {
player.teleport(plugin.config.getLocation("lobbyLocation")!!)
player.clearActivePotionEffects()
player.fireTicks = 0
}, 1)
player.isInvulnerable = true
player.persistentDataContainer.set(NamespacedKey("tcoww", "dead"), PersistentDataType.BOOLEAN, true)
player.sendMessage(
Component.text("Vous étes mort... Mais vous pouvez être encore résucité.").color(
NamedTextColor.RED
)
)
current.playersMutable.forEach { p ->
if (p.role is Witch && p.inventory.any {
it != null && it.persistentDataContainer.get(
NamespacedKey("tcoww", "power"),
PersistentDataType.INTEGER
) == 2
}) {
p.sendMessage(
Component.text("Cette nuit, ${player.name} est mort dans d'atroces souffrances. Vous avez le pouvoir de le/la sauver.\n")
.color(NamedTextColor.DARK_PURPLE)
.append(
Component.text("Cliquez ici pour le/la ressusciter.")
.color(NamedTextColor.DARK_PURPLE)
.decorate(TextDecoration.UNDERLINED)
.clickEvent(ClickEvent.suggestCommand("/power save ${player.name}"))
)
)
}
}
} else {
player.kill(current)
}
}
@EventHandler
fun onHit(event: EntityDamageByEntityEvent) {
val current = Game.current ?: return
if (current.timeGestion.phase != Phase.NIGHT) return
val damager = event.damager
val target = event.entity
if (damager is Player && target is Player &&
current.playersMutable.contains(damager) &&
current.playersMutable.contains(target) &&
damager.role?.lg == true &&
damager.isTransformed() &&
target.role?.lg != true &&
current.wwcankill
) {
current.wwcankill = false
target.health = 0.0
target.persistentDataContainer.set(NamespacedKey("tcoww", "insomie"), PersistentDataType.BOOLEAN, true)
if (target.isSleeping) target.wakeup(false)
}
}
@EventHandler(
priority = EventPriority.MONITOR,
ignoreCancelled = true
)
fun startSleep(event: PlayerBedEnterEvent) {
event.player.addPotionEffect(
PotionEffect(
PotionEffectType.REGENERATION,
PotionEffect.INFINITE_DURATION,
1,
false,
false
)
)
}
@EventHandler(
priority = EventPriority.MONITOR,
ignoreCancelled = true
)
fun endSleep(event: PlayerBedLeaveEvent) {
event.player.removePotionEffect(PotionEffectType.REGENERATION)
}
@EventHandler
fun disconnect(event: PlayerQuitEvent) {
val current = Game.current ?: return
val player = event.player
if (!current.playersMutable.contains(player)) return
Bukkit.getScheduler().runTaskLater(plugin, Runnable {
if (!player.isConnected) {
player.kill(current)
}
}, 2_400)
}
fun Player.kill(current: Game) {
if (isTransformed()) tfHuman()
val item = ItemStack.of(Material.PLAYER_HEAD)
val skullmeta = item.itemMeta as SkullMeta
skullmeta.owningPlayer = player
item.itemMeta = skullmeta
val loc = this.location.add(0.0, 1.5, 0.0)
val entity = this.world.spawnEntity(loc, EntityType.ITEM_DISPLAY) as ItemDisplay
entity.setItemStack(item)
Bukkit.getScheduler().runTaskLater(
plugin,
Runnable {
entity.remove()
},
2400L
)
this.gameMode = GameMode.SPECTATOR
this.isInvulnerable = false
current.playersMutable.remove(player)
this.showTitle(
Title.title(
Component.text("Vous êtes mort.").color(NamedTextColor.DARK_RED),
Component.empty(),
Title.Times.times(Duration.ZERO, Duration.ofSeconds(2), Duration.ofMillis(500))
)
)
if (current.gameEnded()) current.stopGame()
}
fun Player.tfWerewolf() {
if (isTransformed()) return
skinUtils.applyWerewolfFur(this)
persistentDataContainer.set(
NamespacedKey("tcoww", "wwtranform"),
PersistentDataType.BOOLEAN,
true
)
val speedatr = getAttribute(Attribute.MOVEMENT_SPEED)!!
val scaleatr = getAttribute(Attribute.SCALE)!!
speedatr.addModifier(
AttributeModifier(
NamespacedKey("tcoww", "wwtranform"),
0.05,
AttributeModifier.Operation.ADD_NUMBER
)
)
scaleatr.addModifier(
AttributeModifier(
NamespacedKey("tcoww", "wwtranform"),
0.1,
AttributeModifier.Operation.ADD_NUMBER
)
)
}
fun Player.tfHuman() {
if (!isTransformed()) return
skinUtils.unApplySkin(this)
persistentDataContainer.set(
NamespacedKey("tcoww", "wwtranform"),
PersistentDataType.BOOLEAN,
false
)
val speedatr = getAttribute(Attribute.MOVEMENT_SPEED)!!
val scaleatr = getAttribute(Attribute.SCALE)!!
speedatr.removeModifier(
NamespacedKey("tcoww", "wwtranform")
)
scaleatr.removeModifier(
NamespacedKey("tcoww", "wwtranform")
)
}
fun Player.isTransformed(): Boolean {
return persistentDataContainer.getOrDefault(
NamespacedKey("tcoww", "wwtranform"),
PersistentDataType.BOOLEAN,
false
)
}
val Player.role
get() = Role.getRole(this)
}

View File

@@ -0,0 +1,258 @@
package fr.azur.tcoww.events
import fr.azur.tcoww.Tcoww
import fr.azur.tcoww.events.PowerEvents.backLocation
import fr.azur.tcoww.game.Game
import fr.azur.tcoww.game.NightLightCycle
import fr.azur.tcoww.roles.Child
import fr.azur.tcoww.roles.Role
import fr.azur.tcoww.roles.Role.Companion.werewolfRole
import fr.azur.tcoww.roles.Werewolf
import fr.azur.tcoww.roles.Witch
import fr.azur.tcoww.utils.DataKeys.Insomnia
import fr.azur.tcoww.utils.DataKeys.PlayerDead
import fr.azur.tcoww.utils.DataKeys.PowerItems
import fr.azur.tcoww.utils.Players
import fr.azur.tcoww.utils.Players.isTransformed
import fr.azur.tcoww.utils.Players.kill
import fr.azur.tcoww.utils.VoiceChatPlugin
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.event.ClickEvent
import net.kyori.adventure.text.format.NamedTextColor
import net.kyori.adventure.text.format.TextDecoration
import org.bukkit.Bukkit
import org.bukkit.GameMode
import org.bukkit.NamespacedKey
import org.bukkit.entity.Mannequin
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority
import org.bukkit.event.Listener
import org.bukkit.event.entity.EntityDamageByEntityEvent
import org.bukkit.event.entity.EntityRegainHealthEvent
import org.bukkit.event.entity.PlayerDeathEvent
import org.bukkit.event.player.PlayerBedEnterEvent
import org.bukkit.event.player.PlayerBedLeaveEvent
import org.bukkit.event.player.PlayerInteractEntityEvent
import org.bukkit.inventory.meta.Damageable
import org.bukkit.persistence.PersistentDataType
import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType
object GameEvents : Listener {
@EventHandler
fun onDeath(event: PlayerDeathEvent) {
val current = Game.current ?: return
val player = event.player
if (!current.remainingPlayer.contains(player)) return
event.isCancelled = true
if (current.timeGestion.phase == NightLightCycle.Phase.NIGHT) {
backLocation[player.uniqueId] = player.location
val lobby = Tcoww.instance.config.getLocation("lobby-location")
Bukkit.getScheduler().runTaskLater(
Tcoww.instance,
Runnable {
if (lobby != null) {
player.teleport(lobby)
} else {
player.gameMode = GameMode.SPECTATOR
}
player.clearActivePotionEffects()
player.fireTicks = 0
},
1,
)
player.isInvulnerable = true
player.persistentDataContainer.set(PlayerDead, PersistentDataType.BOOLEAN, true)
player.sendMessage(
Component.text(
"Vous étes mort... Mais vous pouvez être encore résucité.",
NamedTextColor.RED,
),
)
current.remainingPlayer.forEach { p ->
if (p.werewolfRole is Witch &&
p.inventory.any {
it != null &&
it.persistentDataContainer.get(
PowerItems,
PersistentDataType.INTEGER,
) == 2 &&
(it.itemMeta as Damageable).damage != 1
}
) {
p.sendMessage(
Component
.text(
"Cette nuit, ${player.name} est mort dans d'atroces souffrances. Vous avez le pouvoir de le/la sauver.\n",
NamedTextColor.DARK_PURPLE,
).append(
Component
.text("Cliquez ici pour le/la ressusciter.", NamedTextColor.DARK_PURPLE)
.decorate(TextDecoration.UNDERLINED)
.clickEvent(
ClickEvent.callback {
val item =
p.inventory.find {
it != null &&
it.persistentDataContainer.get(
PowerItems,
PersistentDataType.INTEGER,
) == 2 &&
(it.itemMeta as Damageable).damage != 1
} ?: return@callback
if (player.persistentDataContainer.get(
PlayerDead,
PersistentDataType.BOOLEAN,
) == false
) {
return@callback
}
player.inventory.remove(item)
player.persistentDataContainer.set(
NamespacedKey("tcoww", "dead"),
PersistentDataType.BOOLEAN,
false,
)
backLocation[player.uniqueId]?.let { player.teleport(it) }
player.isInvulnerable = false
player.sendMessage(
Component.text("Vous avez été résucité.", NamedTextColor.AQUA),
)
player.sendMessage(
Component
.text("Vous avez résuciter ")
.color(NamedTextColor.AQUA)
.append(player.displayName()),
)
},
),
),
)
}
}
} else {
player.kill()
}
}
@EventHandler
fun onHit(event: EntityDamageByEntityEvent) {
val current = Game.current ?: return
if (current.timeGestion.phase != NightLightCycle.Phase.NIGHT || Werewolf.remainingKill < 1) return
val damager = event.damager
val target = event.entity
if (damager is Player &&
target is Player &&
current.remainingPlayer.contains(damager) &&
current.remainingPlayer.contains(target) &&
damager.werewolfRole.team == Role.Team.LG &&
damager.isTransformed() &&
target.werewolfRole.team != Role.Team.LG
) {
target.health = 0.0
target.persistentDataContainer.set(Insomnia, PersistentDataType.BOOLEAN, true)
if (target.isSleeping) target.wakeup(false)
Werewolf.remainingKill--
}
}
@EventHandler
fun sleepLeaveEvent(event: PlayerBedLeaveEvent) {
val role = event.player.werewolfRole
if (Game.current?.timeGestion?.phase == NightLightCycle.Phase.NIGHT &&
role !is Child &&
role.team != Role.Team.LG &&
!event.player.persistentDataContainer.getOrDefault(
Insomnia,
PersistentDataType.BOOLEAN,
false,
)
) {
event.isCancelled = true
}
}
@EventHandler(
priority = EventPriority.MONITOR,
ignoreCancelled = true,
)
fun startSleep(event: PlayerBedEnterEvent) {
if (event.player.werewolfRole.team == Role.Team.LG) {
VoiceChatPlugin.addWerewolf(event.player)
} else {
VoiceChatPlugin.createSoloGroup(event.player)
}
event.player.addPotionEffect(
PotionEffect(
PotionEffectType.REGENERATION,
PotionEffect.INFINITE_DURATION,
1,
false,
false,
),
)
event.player.addPotionEffect(
PotionEffect(
PotionEffectType.BLINDNESS,
PotionEffect.INFINITE_DURATION,
1,
false,
false,
),
)
}
@EventHandler(
priority = EventPriority.MONITOR,
)
fun endSleep(event: PlayerBedLeaveEvent) {
event.player.removePotionEffect(PotionEffectType.REGENERATION)
event.player.removePotionEffect(PotionEffectType.BLINDNESS)
}
@EventHandler
fun clickOnCorpse(event: PlayerInteractEntityEvent) {
val corpse = event.rightClicked
if (corpse is Mannequin &&
corpse.persistentDataContainer.getOrDefault(Players.corpseKey, PersistentDataType.BOOLEAN, false)
) {
val deadPlayer = Bukkit.getPlayer(corpse.profile.uuid()!!)!!
val playerRole =
Role.registerRoles.getValue(
NamespacedKey.fromString(
corpse.persistentDataContainer.get(
Players.corpseRoleKey,
PersistentDataType.STRING,
)!!,
)!!,
)
event.player.sendMessage {
Component
.text("Ceci est le cadavre de ", NamedTextColor.DARK_RED)
.append(deadPlayer.displayName())
.append(Component.text(".", NamedTextColor.DARK_RED))
.appendNewline()
.append(Component.text("Il était ", NamedTextColor.DARK_RED))
.append(playerRole.displayName)
.append(Component.text(".", NamedTextColor.DARK_RED))
}
}
}
@EventHandler
fun disablePotionRegen(event: EntityRegainHealthEvent) {
val player = event.entity as? Player ?: return
if (player.hasPotionEffect(PotionEffectType.WITHER)) {
event.isCancelled = true
}
}
}

View File

@@ -0,0 +1,198 @@
package fr.azur.tcoww.events
import fr.azur.tcoww.Tcoww
import fr.azur.tcoww.commands.Vote
import fr.azur.tcoww.game.Game
import fr.azur.tcoww.game.NightLightCycle
import fr.azur.tcoww.roles.Child
import fr.azur.tcoww.roles.Role
import fr.azur.tcoww.roles.Role.Companion.werewolfRole
import fr.azur.tcoww.roles.Werewolf
import fr.azur.tcoww.utils.BedGestion.isOccupied
import fr.azur.tcoww.utils.DataKeys.Insomnia
import fr.azur.tcoww.utils.DataKeys.PlayerDead
import fr.azur.tcoww.utils.DataKeys.PowerItems
import fr.azur.tcoww.utils.Players.corpseKey
import fr.azur.tcoww.utils.Players.isTransformed
import fr.azur.tcoww.utils.Players.kill
import fr.azur.tcoww.utils.Players.tfHuman
import fr.azur.tcoww.utils.VoiceChatPlugin
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Bukkit
import org.bukkit.GameMode
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.persistence.PersistentDataType
object PhaseEvents : Listener {
@EventHandler
fun phaseChange(event: NightLightCycle.NightLightCyclePhaseChangeEvent) {
val current = Game.current ?: return
current.remainingPlayer.forEach { it.updateCommands() }
when (event.newPhase) {
NightLightCycle.Phase.DAY -> {
event.world.players.forEach { player ->
VoiceChatPlugin.degroupPlayer(player)
player.sendMessage(Component.text("Le jour se lève.", NamedTextColor.GOLD))
player.clearActivePotionEffects()
player.persistentDataContainer.set(
Insomnia,
PersistentDataType.BOOLEAN,
false,
)
if (player.werewolfRole.team == Role.Team.LG) {
player.inventory.forEach {
if (it != null &&
it.persistentDataContainer.get(
PowerItems,
PersistentDataType.INTEGER,
) == 3
) {
player.setCooldown(it, ((event.duration.day + event.duration.vote) * 20))
}
if (player.isTransformed()) {
player.tfHuman()
}
}
}
}
}
NightLightCycle.Phase.VOTE -> {
event.world.entities
.filter {
it.persistentDataContainer.getOrDefault(
corpseKey,
PersistentDataType.BOOLEAN,
false,
)
}.forEach { it.remove() }
event.world.players.forEach { player ->
if (event.dayCount >= 1) {
player.sendMessage(
Component
.text("Le jour se couche, ce soir un vote est organisé.")
.color(NamedTextColor.DARK_AQUA)
.appendNewline()
.append(
Component
.text("Faite /vote pour voter.")
.color(NamedTextColor.AQUA),
),
)
} else {
player.sendMessage(
Component
.text("Le jour se couche, ce soir aucun vote n'est organisé. Faute de preuve.", NamedTextColor.DARK_AQUA),
)
}
}
}
NightLightCycle.Phase.NIGHT -> {
if (Vote.vote.isNotEmpty() && current.timeGestion.dayCount >= 1) {
val voteMap =
Vote.vote.values
.groupingBy { it }
.eachCount()
val mostVoted = voteMap.maxByOrNull { it.value }?.key
mostVoted?.let { uuid ->
Bukkit.getPlayer(uuid)?.let { votedPlayer ->
votedPlayer.fireTicks = 1200
Bukkit.getScheduler().runTaskLater(
Tcoww.instance,
Runnable {
votedPlayer.health = 0.0
},
60,
)
current.remainingPlayer.forEach {
it.sendMessage(
Component
.text("${votedPlayer.name} a reçu le plus de vote.")
.color(NamedTextColor.RED),
)
}
}
}
Vote.vote.clear()
}
Werewolf.remainingKill = 1
event.world.players.forEach { player ->
player.sendMessage(
Component
.text("La lune se léve et les loups sont de sortie. Vous devriez dormir.")
.color(NamedTextColor.DARK_PURPLE),
)
if (player.werewolfRole.team == Role.Team.LG) {
player.sendMessage(
Component.text("Vous pouvez vous transformer.", NamedTextColor.DARK_PURPLE),
)
}
}
Bukkit.getScheduler().runTaskLater(
Tcoww.instance,
Runnable {
val openBed =
current.beds
.filter { !it.block.isOccupied() }
.toMutableList()
current.remainingPlayer.forEach { player ->
if (!player.isSleeping &&
!player.persistentDataContainer.getOrDefault(
PlayerDead,
PersistentDataType.BOOLEAN,
false,
) &&
!player.isTransformed()
) {
val loc = openBed.removeFirst()
player.sleep(loc, true)
val role = player.werewolfRole
if (role is Child || role.team == Role.Team.LG) {
player.sendMessage(
Component.text(
"Tout le monde est couché. Vous pouvez vous lever.",
NamedTextColor.AQUA,
),
)
}
}
}
},
Tcoww.instance.config.getLong("player-sleep") * 20,
)
}
NightLightCycle.Phase.CREPUSCULAR -> {
val current = Game.current ?: return
val deadPlayers =
current.remainingPlayer.filter {
it.persistentDataContainer.get(PlayerDead, PersistentDataType.BOOLEAN) == true
}
deadPlayers.forEach { player ->
player.gameMode = GameMode.SPECTATOR
PowerEvents.backLocation[player.uniqueId]?.let { player.teleport(it) }
player.kill()
}
current.remainingPlayer.removeAll(deadPlayers)
// if (current.gameEnded()) {
// current.remainingPlayer.forEach {
// if (it.isTransformed()) it.tfHuman()
// }
// current.stopGame()
// }
}
}
}
}

View File

@@ -0,0 +1,154 @@
package fr.azur.tcoww.events
import fr.azur.tcoww.game.Game
import fr.azur.tcoww.roles.Role
import fr.azur.tcoww.ui.PlayerSelectMenu
import fr.azur.tcoww.utils.DataKeys
import fr.azur.tcoww.utils.Players.isTransformed
import fr.azur.tcoww.utils.Players.tfHuman
import fr.azur.tcoww.utils.Players.tfWerewolf
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.event.HoverEvent
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.GameMode
import org.bukkit.Location
import org.bukkit.NamespacedKey
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.inventory.InventoryMoveItemEvent
import org.bukkit.event.player.PlayerDropItemEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.persistence.PersistentDataType
import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType
import java.util.UUID
object PowerEvents : Listener {
var backLocation = mutableMapOf<UUID, Location>()
@EventHandler
fun powerItemDrop(event: PlayerDropItemEvent) {
val item = event.itemDrop.itemStack
if (event.player.gameMode != GameMode.CREATIVE &&
item.itemMeta.persistentDataContainer.has(DataKeys.PowerItems, PersistentDataType.INTEGER)
) {
event.isCancelled = true
}
}
@EventHandler
fun powerItemTransfer(event: InventoryMoveItemEvent) {
val item = event.item
if (event.source != event.destination &&
item.itemMeta.persistentDataContainer.has(DataKeys.PowerItems, PersistentDataType.INTEGER)
) {
event.isCancelled = true
}
}
@EventHandler
fun clickEvent(event: PlayerInteractEvent) {
val item = event.item ?: return
val player = event.player
if (!item.persistentDataContainer.has(NamespacedKey("tcoww", "power"))) return
event.isCancelled = true
if (player.hasCooldown(item)) return
when (item.persistentDataContainer.get(NamespacedKey("tcoww", "power"), PersistentDataType.INTEGER)) {
1 -> {
val current = Game.current ?: return
val menu = PlayerSelectMenu(current.remainingPlayer)
player.openInventory(menu.inventory)
menu.future.whenComplete { selectedPlayer, _ ->
val target = selectedPlayer ?: return@whenComplete
player.inventory.remove(item)
target.addPotionEffect(PotionEffect(PotionEffectType.WITHER, 600, 4))
}
}
2 -> {
val current = Game.current ?: return
val menu =
PlayerSelectMenu(
current.remainingPlayer.filter {
it.persistentDataContainer.get(
NamespacedKey("tcoww", "dead"),
PersistentDataType.BOOLEAN,
) == true
},
)
player.openInventory(menu.inventory)
menu.future.whenComplete { selectedPlayer, _ ->
val target = selectedPlayer ?: return@whenComplete
player.inventory.remove(item)
target.persistentDataContainer.set(
NamespacedKey("tcoww", "dead"),
PersistentDataType.BOOLEAN,
false,
)
backLocation[target.uniqueId]?.let { target.teleport(it) }
target.isInvulnerable = false
target.sendMessage(Component.text("Vous avez été résucité.", NamedTextColor.AQUA))
player.sendMessage(
Component
.text("Vous avez résuciter ")
.color(NamedTextColor.AQUA)
.append(player.displayName()),
)
}
}
3 -> {
if (player.isTransformed()) {
player.tfHuman()
} else {
player.tfWerewolf()
}
player.setCooldown(item, 200)
}
4 -> {
val current = Game.current ?: return
val timeGestion = current.timeGestion
val menu = PlayerSelectMenu(current.remainingPlayer)
player.openInventory(menu.inventory)
menu.future.whenComplete { selectedPlayer, _ ->
val target = selectedPlayer ?: return@whenComplete
val cooldown =
(
(
timeGestion.duration.day + timeGestion.duration.vote + timeGestion.duration.night +
timeGestion.duration.crepuscular -
30
) *
2
) *
20
player.setCooldown(item, cooldown)
val targetRole = Role.getRole(target)
player.sendMessage(
target
.displayName()
.append(Component.text(" est "))
.append(
targetRole?.displayName ?: Component.text("Inconnu").hoverEvent(
HoverEvent.showText(
targetRole?.lineDescription ?: Component.empty(),
),
),
),
)
}
}
}
}
}

View File

@@ -1,20 +0,0 @@
package fr.azur.tcoww.events
import fr.azur.tcoww.game.Phase
import org.bukkit.World
import org.bukkit.event.Event
import org.bukkit.event.HandlerList
class TimePhaseChangeEvent(
val world: World,
val newPhase: Phase
) : Event() {
override fun getHandlers(): HandlerList {
return handlerList
}
companion object {
@JvmStatic
val handlerList: HandlerList = HandlerList()
}
}

View File

@@ -1,47 +1,56 @@
package fr.azur.tcoww.events
import fr.azur.tcoww.utils.BedGestion
import fr.azur.tcoww.Tcoww
import fr.azur.tcoww.utils.BedGestion.getHeadBed
import fr.azur.tcoww.utils.BedGestion.isBed
import fr.azur.tcoww.utils.DataKeys
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.NamespacedKey
import org.bukkit.Location
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.persistence.PersistentDataType
import org.bukkit.plugin.java.JavaPlugin
class ToolsEvents(val plugin: JavaPlugin) : Listener {
object ToolsEvents : Listener {
@EventHandler
fun clickEvent(event: PlayerInteractEvent) {
val tool = event.player.inventory.itemInMainHand
if (!tool.persistentDataContainer.has(NamespacedKey("tcoww", "tools"))) return
if (!tool.persistentDataContainer.has(DataKeys.ToolsItems) || event.player.hasCooldown(tool)) return
when (tool.persistentDataContainer.get(NamespacedKey("tcoww", "tools"), PersistentDataType.INTEGER)) {
event.player.setCooldown(tool, 20)
when (tool.persistentDataContainer.get(DataKeys.ToolsItems, PersistentDataType.INTEGER)) {
1 -> {
event.isCancelled = true
val interactloc = event.interactionPoint
if (interactloc != null) {
if (BedGestion.isBed(interactloc.block)) {
val list = plugin.config.getMapList("bedLocation")
val interactLoc = event.interactionPoint
if (interactLoc != null) {
if (interactLoc.block.isBed()) {
val list =
Tcoww.instance.config
.getList("bed-locations")
.orEmpty()
.mapNotNull { it as? Location }
.toMutableList()
val block = BedGestion.getHeadBed(interactloc.block)
val block = interactLoc.block.getHeadBed()
if (!list.contains(block.location.serialize())) {
list.add(block.location.serialize())
plugin.config.set("bedLocation", list)
plugin.saveConfig()
if (!list.contains(block.location)) {
list.add(block.location)
Tcoww.instance.config.set("bed-locations", list)
Tcoww.instance.saveConfig()
event.player.sendMessage(
Component.text("Bed location add.").color(NamedTextColor.LIGHT_PURPLE)
Component.text("Bed location add.", NamedTextColor.LIGHT_PURPLE),
)
} else {
event.player.sendMessage(
Component.text("Bed location already exists.").color(NamedTextColor.DARK_RED)
Component.text("Bed location already exists.", NamedTextColor.DARK_RED),
)
}
} else {
event.player.sendMessage(
Component.text("It's not a bed...").color(NamedTextColor.DARK_RED)
Component.text("It's not a bed...", NamedTextColor.DARK_RED),
)
}
}
@@ -49,34 +58,29 @@ class ToolsEvents(val plugin: JavaPlugin) : Listener {
2 -> {
event.isCancelled = true
val interactloc = event.interactionPoint
if (interactloc != null) {
interactloc.x = interactloc.blockX.toDouble()
interactloc.y = interactloc.blockY.toDouble() + 1
interactloc.z = interactloc.blockZ.toDouble()
plugin.config.set("spawnLocation", interactloc)
plugin.saveConfig()
event.player.sendMessage(Component.text("Spawn location set.").color(NamedTextColor.LIGHT_PURPLE))
val interactLoc = event.interactionPoint
if (interactLoc != null) {
interactLoc.x = interactLoc.blockX.toDouble()
interactLoc.y = interactLoc.blockY.toDouble() + 1
interactLoc.z = interactLoc.blockZ.toDouble()
Tcoww.instance.config.set("spawn-location", interactLoc)
Tcoww.instance.saveConfig()
event.player.sendMessage(Component.text("Spawn location set.", NamedTextColor.LIGHT_PURPLE))
}
}
3 -> {
event.isCancelled = true
val interactloc = event.interactionPoint
if (interactloc != null) {
interactloc.x = interactloc.blockX.toDouble()
interactloc.y = interactloc.blockY.toDouble() + 1
interactloc.z = interactloc.blockZ.toDouble()
plugin.config.set("lobbyLocation", interactloc)
plugin.saveConfig()
event.player.sendMessage(Component.text("Lobby location set.").color(NamedTextColor.LIGHT_PURPLE))
val interactLoc = event.interactionPoint
if (interactLoc != null) {
interactLoc.x = interactLoc.blockX.toDouble()
interactLoc.y = interactLoc.blockY.toDouble() + 1
interactLoc.z = interactLoc.blockZ.toDouble()
Tcoww.instance.config.set("lobby-location", interactLoc)
Tcoww.instance.saveConfig()
event.player.sendMessage(Component.text("Lobby location set.", NamedTextColor.LIGHT_PURPLE))
}
}
}
}
// @EventHandler
// fun reloadAfterWorld(worldInitEvent: WorldInitEvent) {
//
// }
}
}

View File

@@ -0,0 +1,27 @@
package fr.azur.tcoww.events
import fr.azur.tcoww.ui.CustomUI
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
object UIEvents : Listener {
@EventHandler
fun onInventoryClick(event: InventoryClickEvent) {
val inventory = event.inventory
val holder = inventory.getHolder(false)
if (holder is CustomUI) {
holder.onClick(event)
}
}
@EventHandler
fun onInventoryClick(event: InventoryCloseEvent) {
val inventory = event.inventory
val holder = inventory.getHolder(false)
if (holder is CustomUI) {
holder.onClose(event)
}
}
}

View File

@@ -1,35 +1,44 @@
package fr.azur.tcoww.game
import fr.azur.tcoww.Tcoww
import fr.azur.tcoww.roles.Role
import fr.azur.tcoww.roles.Villager
import fr.azur.tcoww.roles.Werewolf
import fr.azur.tcoww.roles.Role.Companion.werewolfRole
import fr.azur.tcoww.utils.DataKeys.Insomnia
import fr.azur.tcoww.utils.DataKeys.PlayerDead
import fr.azur.tcoww.utils.Players.tfHuman
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Bukkit
import org.bukkit.GameMode
import org.bukkit.Location
import org.bukkit.NamespacedKey
import org.bukkit.World
import org.bukkit.entity.Player
import org.bukkit.persistence.PersistentDataType
import org.bukkit.plugin.Plugin
import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType
import org.bukkit.scheduler.BukkitTask
import kotlin.math.ceil
class Game(var plugin: Plugin, val world: World, val players: Iterable<Player>, var roles: Iterable<Role>) {
val werewolfPercentage: Float = plugin.config.getInt("wwcount").toFloat()
val timeGestion: TimeGestion
val playersMutable = players.toMutableList()
val timeGestShedule: BukkitTask
var wwcankill = false
class Game(
var plugin: Plugin,
val world: World,
val players: Iterable<Player>,
var roles: Map<NamespacedKey, Int>,
) {
val timeGestion: NightLightCycle
val remainingPlayer = players.toMutableList()
val beds =
Tcoww.instance.config
.getList("bed-locations")
.orEmpty()
.mapNotNull { it as? Location }
init {
players.forEach { player ->
player.persistentDataContainer.set(NamespacedKey("tcoww", "dead"), PersistentDataType.BOOLEAN, false)
player.persistentDataContainer.set(NamespacedKey("tcoww", "wwtranform"), PersistentDataType.BOOLEAN, false)
player.teleport(plugin.config.getLocation("spawnLocation")!!)
player.tfHuman()
val spawnLoc = plugin.config.getLocation("spawn-location")
if (spawnLoc != null) player.teleport(spawnLoc)
player.inventory.clear()
player.gameMode = GameMode.ADVENTURE
player.health = 20.0
player.addPotionEffect(
PotionEffect(
@@ -37,47 +46,27 @@ class Game(var plugin: Plugin, val world: World, val players: Iterable<Player>,
PotionEffect.INFINITE_DURATION,
1,
false,
false
)
false,
),
)
}
assignRoles()
timeGestion = TimeGestion(plugin, world)
timeGestShedule = Bukkit.getScheduler().runTaskTimer(plugin, Runnable {
timeGestion.tick()
}, 0L, 1L)
timeGestion = NightLightCycle(plugin, world)
current = this
}
fun getWerewolfInt(): Int {
val playerCount = players.count()
return ceil(playerCount * (werewolfPercentage / 100f)).toInt()
}
private fun assignRoles() {
val playerList = players.shuffled().toMutableList()
val werewolfCount = getWerewolfInt()
val specialRolesCount = roles.count()
if (werewolfCount + specialRolesCount > playerList.size) {
throw IllegalArgumentException("Not enough players for the given number of werewolves and special roles.")
}
repeat(werewolfCount) { i ->
Role.setRole(playerList[i], Werewolf())
}
roles.forEachIndexed { idx, role ->
Role.setRole(playerList[werewolfCount + idx], role)
}
for (i in werewolfCount + specialRolesCount until playerList.size) {
Role.setRole(playerList[i], Villager())
val playersShuffled = players.shuffled()
var playerIndex = 0
roles.forEach {
val role = Role.registerRoles.getValue(it.key)
for (i in 0..<it.value) {
playersShuffled[playerIndex].werewolfRole = role
playerIndex++
}
}
}
@@ -85,56 +74,51 @@ class Game(var plugin: Plugin, val world: World, val players: Iterable<Player>,
var current: Game? = null
}
fun gameEnded(): Boolean {
var hasWerewolf = false
var hasVillager = false
for (player in playersMutable) {
val isWerewolf = Role.getRole(player)?.lg ?: false
if (isWerewolf) hasWerewolf = true else hasVillager = true
if (hasWerewolf && hasVillager) return false
}
return true
fun checkGameEnd(): Boolean {
val team = remainingPlayer.map { player -> player.werewolfRole.team }.toSet()
return team.count() <= 1
}
fun stopGame(withoutResult: Boolean = false) {
current ?: return
current = null
timeGestShedule.cancel()
timeGestion.stop()
world.time = 18000
val endComponent =
Component
.text("La partie est terminée !", NamedTextColor.DARK_GREEN)
val winComponent =
Component
.text("Victoire des ", NamedTextColor.DARK_GREEN)
.append(remainingPlayer[0].werewolfRole.team.displayName)
.append(Component.text(".", NamedTextColor.DARK_GREEN))
players.forEach { player ->
player.gameMode = GameMode.SPECTATOR
player.sendMessage(
Component.text("La partie est terminée !", NamedTextColor.DARK_GREEN)
.appendNewline()
.append {
if (withoutResult)
Component.empty()
else {
val villageHasWolf = playersMutable.any {
Role.getRole(it)?.lg ?: false
}
if (villageHasWolf) {
Component.text(
"Les loups-garous ont finalement dévoré le village.",
NamedTextColor.DARK_RED
)
} else {
Component.text(
"Les villageois ont finalement sauvé le village.",
NamedTextColor.DARK_BLUE
)
}
}
}
)
}
Bukkit.getScheduler().runTaskLater(plugin, Runnable {
players.forEach { player ->
player.teleport(plugin.config.getLocation("lobbyLocation")!!)
player.gameMode = GameMode.ADVENTURE
player.sendMessage(endComponent)
if (remainingPlayer[0].werewolfRole.team == Role.Team.Solo) {
TODO()
} else {
if (!withoutResult) player.sendMessage(winComponent)
}
}, 200)
}
val lobbyLoc = plugin.config.getLocation("lobby-location")
Bukkit.getScheduler().runTaskLater(
plugin,
Runnable {
players.forEach { player ->
if (lobbyLoc != null) player.teleport(lobbyLoc)
player.gameMode = GameMode.ADVENTURE
player.persistentDataContainer.remove(PlayerDead)
player.persistentDataContainer.remove(Insomnia)
player.inventory.clear()
player.resetCooldown()
}
},
200,
)
}
}

View File

@@ -0,0 +1,133 @@
package fr.azur.tcoww.game
import org.bukkit.Bukkit
import org.bukkit.GameRule
import org.bukkit.World
import org.bukkit.event.Event
import org.bukkit.event.HandlerList
import org.bukkit.plugin.Plugin
import org.bukkit.scheduler.BukkitTask
class NightLightCycle(
plugin: Plugin,
val world: World,
) {
enum class Phase {
DAY,
VOTE,
NIGHT,
CREPUSCULAR,
}
class NightLightCyclePhaseChangeEvent(
val world: World,
val newPhase: Phase,
val duration: Duration,
val dayCount: Int,
) : Event() {
override fun getHandlers(): HandlerList = handlerList
companion object {
@JvmStatic
val handlerList: HandlerList = HandlerList()
}
}
data class Duration(
val day: Int,
val vote: Int,
val night: Int,
val crepuscular: Int,
) {
companion object {
fun fromPlugin(plugin: Plugin): Duration {
val dayDuration = plugin.config.getInt("duration.day")
val voteDuration = plugin.config.getInt("duration.vote")
val nightDuration = plugin.config.getInt("duration.night")
val crepuscularDuration = plugin.config.getInt("duration.crepuscular")
return Duration(dayDuration, voteDuration, nightDuration, crepuscularDuration)
}
}
}
var phase: Phase = Phase.DAY
val phases = Phase.entries
val process: BukkitTask
val duration = Duration.fromPlugin(plugin)
var targetInstant: Long = 0
var tick: Float = 0F
var tickOrigin: Int = 0
var realDifference: Long = 0
var tickDifference: Int = 0
var realOrigin: Long = 0
var dayCount: Int = 0
init {
world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false)
changePhase(Phase.DAY)
process =
Bukkit.getScheduler().runTaskTimer(
plugin,
Runnable {
this.tick()
},
0L,
1L,
)
}
fun changePhase(newPhase: Phase) {
realOrigin = System.currentTimeMillis()
when (newPhase) {
Phase.DAY -> {
tickDifference = 12_000
tickOrigin = 0
realDifference = duration.day * 1000L
}
Phase.VOTE -> {
tickDifference = 1_000
tickOrigin = 12_000
realDifference = duration.vote * 1000L
}
Phase.NIGHT -> {
tickDifference = 10_000
tickOrigin = 13_000
realDifference = duration.night * 1000L
}
Phase.CREPUSCULAR -> {
tickDifference = 1_000
tickOrigin = 23_000
realDifference = duration.crepuscular * 1000L
}
}
targetInstant = System.currentTimeMillis() + realDifference
tick = tickDifference / realDifference.toFloat()
phase = newPhase
Bukkit.getPluginManager().callEvent(NightLightCyclePhaseChangeEvent(world, newPhase, duration, dayCount))
}
fun tick() {
val realtime = System.currentTimeMillis()
if (realtime >= targetInstant) {
val phase = phases[(phases.indexOf(phase) + 1) % phases.count()]
if (phase == Phase.DAY) {
dayCount += 1
}
changePhase(phase)
}
val result = tickOrigin + (realtime - realOrigin) * tick
world.time = result.toLong().coerceIn(0, 23999)
}
fun stop() {
process.cancel()
}
}

View File

@@ -1,8 +0,0 @@
package fr.azur.tcoww.game
enum class Phase {
DAY,
VOTE,
NIGHT,
CREPUSCULAR
}

View File

@@ -1,81 +0,0 @@
package fr.azur.tcoww.game
import fr.azur.tcoww.events.TimePhaseChangeEvent
import org.bukkit.Bukkit
import org.bukkit.World
import org.bukkit.GameRule
import org.bukkit.plugin.Plugin
import java.util.Calendar
class TimeGestion(plugin: Plugin, val world: World) {
var calendar: Calendar = Calendar.getInstance()
var targetCalendar: Calendar = calendar.clone() as Calendar
var difference: Long
var differencetick: Int
val dayDuration: Int = plugin.config.getInt("duration.day")
val voteDuration: Int = plugin.config.getInt("duration.vote")
val nightDuration: Int = plugin.config.getInt("duration.night")
val crepuscularDuration: Int = plugin.config.getInt("duration.crepuscular")
var phase: Phase = Phase.DAY
var tickcorrecter = 0
init {
world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false)
targetCalendar.add(Calendar.SECOND, dayDuration)
difference = targetCalendar.timeInMillis - calendar.timeInMillis
differencetick = 12500
Bukkit.getPluginManager().callEvent(TimePhaseChangeEvent(world,phase))
}
fun changePhase() {
calendar = Calendar.getInstance()
targetCalendar = calendar.clone() as Calendar
when (phase) {
// vote
Phase.DAY -> {
phase = Phase.VOTE
tickcorrecter = 12_500
differencetick = 1_500
targetCalendar.add(Calendar.SECOND, voteDuration)
}
// night
Phase.VOTE -> {
phase = Phase.NIGHT
tickcorrecter = 14_000
differencetick = 8_500
targetCalendar.add(Calendar.SECOND, nightDuration)
}
// crepuscular
Phase.NIGHT -> {
phase = Phase.CREPUSCULAR
tickcorrecter = 22_500
differencetick = 1_500
targetCalendar.add(Calendar.SECOND, crepuscularDuration)
}
// day
Phase.CREPUSCULAR -> {
phase = Phase.DAY
tickcorrecter = 0
differencetick = 12_500
targetCalendar.add(Calendar.SECOND, dayDuration)
}
}
Bukkit.getPluginManager().callEvent(TimePhaseChangeEvent(world,phase))
difference = targetCalendar.timeInMillis - calendar.timeInMillis
}
fun tick() {
if (System.currentTimeMillis() >= targetCalendar.timeInMillis) {
changePhase()
}
if (difference == 0L) return
val result = tickcorrecter + ((differencetick.toDouble() * (System.currentTimeMillis() - calendar.timeInMillis)) / difference).toInt()
world.time = result.coerceIn(0, 23999).toLong()
}
}

View File

@@ -2,12 +2,12 @@ package fr.azur.tcoww.items
import fr.azur.tcoww.game.Game
import fr.azur.tcoww.roles.Role
import fr.azur.tcoww.utils.DataKeys
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import net.kyori.adventure.text.format.TextDecoration
import org.bukkit.Color
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.enchantments.Enchantment
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemRarity
@@ -19,181 +19,216 @@ import org.bukkit.persistence.PersistentDataType
import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType
enum class CustomItems(val cache: Boolean, val supplier: () -> ItemStack) {
enum class CustomItems(
val cache: Boolean,
val supplier: () -> ItemStack,
) {
WereWolfTransformItem(true, {
ItemStack.of(Material.BONE).apply {
itemMeta = itemMeta.apply {
customName(Component.text("Os de transformation").color(NamedTextColor.DARK_RED))
persistentDataContainer.set(
NamespacedKey("tcoww", "power"),
PersistentDataType.INTEGER,
3
)
lore(
listOf(
Component.text("Clique pour basculer en forme loup-garou.")
.color(NamedTextColor.GOLD)
itemMeta =
itemMeta.apply {
customName(Component.text("Os de transformation", NamedTextColor.DARK_RED))
persistentDataContainer.set(
DataKeys.PowerItems,
PersistentDataType.INTEGER,
3,
)
)
}
lore(
listOf(
Component
.text("Clique pour basculer en forme loup-garou.")
.color(NamedTextColor.GOLD),
),
)
}
}
}),
GunBullet(true, {
ItemStack(Material.TIPPED_ARROW).apply {
itemMeta = (itemMeta as PotionMeta).apply {
color = Color.ORANGE
displayName(Component.text("Balle de fusil.", NamedTextColor.YELLOW))
basePotionType = basePotionType.apply {
addCustomEffect(PotionEffect(PotionEffectType.INSTANT_DAMAGE, 20, 99), true)
itemMeta =
(itemMeta as PotionMeta).apply {
color = Color.ORANGE
displayName(Component.text("Balle de fusil.", NamedTextColor.YELLOW))
basePotionType =
basePotionType.apply {
addCustomEffect(PotionEffect(PotionEffectType.INSTANT_DAMAGE, 20, 99), true)
}
}
}
}
}),
BlasTechDL44(true, {
ItemStack.of(Material.CROSSBOW).apply {
itemMeta = (itemMeta as CrossbowMeta).apply {
addChargedProjectile(GunBullet.item)
displayName(Component.text("BlasTech DL-44", NamedTextColor.GOLD))
addEnchant(Enchantment.INFINITY, 1, true)
}
itemMeta = (itemMeta as Damageable).apply {
setMaxDamage(1)
}
itemMeta =
(itemMeta as CrossbowMeta).apply {
addChargedProjectile(GunBullet.item)
displayName(Component.text("BlasTech DL-44", NamedTextColor.GOLD))
addEnchant(Enchantment.INFINITY, 1, true)
}
itemMeta =
(itemMeta as Damageable).apply {
setMaxDamage(1)
}
}
}),
CrystalBall(true, {
ItemStack.of(Material.GLASS).apply {
itemMeta = itemMeta.apply {
customName(
Component.text("Boule de cristal").color(NamedTextColor.AQUA)
)
persistentDataContainer.set(
NamespacedKey("tcoww", "power"),
PersistentDataType.INTEGER,
4
)
lore(listOf(Component.text("/power view <pseudo> pour l'utiliser.").color(NamedTextColor.GOLD)))
}
itemMeta =
itemMeta.apply {
customName(
Component.text("Boule de cristal", NamedTextColor.AQUA),
)
persistentDataContainer.set(
DataKeys.PowerItems,
PersistentDataType.INTEGER,
4,
)
setMaxStackSize(1)
lore(listOf(Component.text("Click droit pour utiliser.", NamedTextColor.GOLD)))
}
}
}
),
}),
DeathPotion(true, {
ItemStack.of(Material.POTION).apply {
itemMeta = (itemMeta as PotionMeta).apply {
customName(
Component.text("Potion de poison").color(NamedTextColor.DARK_PURPLE)
)
persistentDataContainer.set(
NamespacedKey("tcoww", "power"),
PersistentDataType.INTEGER,
1
)
color = Color.BLACK
lore(
listOf(
Component.text("/power kill <pseudo> pour l'utiliser.").color(NamedTextColor.GOLD)
itemMeta =
(itemMeta as PotionMeta).apply {
setMaxStackSize(1)
customName(
Component.text("Potion de poison", NamedTextColor.DARK_PURPLE),
)
)
}
persistentDataContainer.set(
DataKeys.PowerItems,
PersistentDataType.INTEGER,
1,
)
color = Color.BLACK
lore(
listOf(
Component.text("Click droit pour utiliser.", NamedTextColor.GOLD),
),
)
}
itemMeta =
(itemMeta as Damageable).apply {
setMaxDamage(1)
}
}
}),
HealPotion(true, {
ItemStack.of(Material.POTION).apply {
itemMeta = (itemMeta as PotionMeta).apply {
customName(
Component.text("Potion de vie").color(NamedTextColor.DARK_PURPLE)
)
persistentDataContainer.set(
NamespacedKey("tcoww", "power"),
PersistentDataType.INTEGER,
2
)
color = Color.RED
lore(listOf(Component.text("/power save <pseudo> pour l'utiliser.").color(NamedTextColor.GOLD)))
}
itemMeta =
(itemMeta as PotionMeta).apply {
setMaxStackSize(1)
customName(
Component.text("Potion de vie", NamedTextColor.DARK_PURPLE),
)
persistentDataContainer.set(
DataKeys.PowerItems,
PersistentDataType.INTEGER,
2,
)
color = Color.RED
lore(listOf(Component.text("Click droit pour utiliser.", NamedTextColor.GOLD)))
}
itemMeta =
(itemMeta as Damageable).apply {
setMaxDamage(1)
}
}
}),
SpawnLocTool(true, {
ItemStack.of(Material.SUGAR_CANE).apply {
itemMeta = itemMeta.apply {
setRarity(ItemRarity.EPIC)
persistentDataContainer.set(
NamespacedKey("tcoww", "tools"),
PersistentDataType.INTEGER,
2
)
lore(listOf(Component.text("Spawn location tool").color(NamedTextColor.GOLD)))
}
itemMeta =
itemMeta.apply {
setRarity(ItemRarity.EPIC)
persistentDataContainer.set(
DataKeys.ToolsItems,
PersistentDataType.INTEGER,
2,
)
lore(listOf(Component.text("Spawn location tool", NamedTextColor.GOLD)))
}
}
}),
BedLocTool(true, {
ItemStack.of(Material.WOODEN_HOE).apply {
itemMeta = itemMeta.apply {
setRarity(ItemRarity.EPIC)
persistentDataContainer.set(
NamespacedKey("tcoww", "tools"),
PersistentDataType.INTEGER,
1
)
lore(listOf(Component.text("bed location tool").color(NamedTextColor.GOLD)))
}
itemMeta =
itemMeta.apply {
setRarity(ItemRarity.EPIC)
persistentDataContainer.set(
DataKeys.ToolsItems,
PersistentDataType.INTEGER,
1,
)
lore(listOf(Component.text("bed location tool", NamedTextColor.GOLD)))
}
}
}),
LobbyLocTool(true, {
ItemStack.of(Material.POPPED_CHORUS_FRUIT).apply {
itemMeta = itemMeta.apply {
setRarity(ItemRarity.EPIC)
persistentDataContainer.set(
NamespacedKey("tcoww", "tools"),
PersistentDataType.INTEGER,
3
)
lore(listOf(Component.text("Lobby location tool").color(NamedTextColor.GOLD)))
}
itemMeta =
itemMeta.apply {
setRarity(ItemRarity.EPIC)
persistentDataContainer.set(
DataKeys.ToolsItems,
PersistentDataType.INTEGER,
3,
)
lore(listOf(Component.text("Lobby location tool", NamedTextColor.GOLD)))
}
}
}),
ListHint(false, {
val finalLore = buildList {
add(
Component.text("C'est une liste de nom.", NamedTextColor.RED)
)
add(
Component.text("Il y a écrit que un de ces 3 citoyens", NamedTextColor.DARK_RED)
)
add(
Component.text("est un loup-garou.", NamedTextColor.DARK_RED)
)
val finalLore =
buildList {
add(
Component.text("C'est une liste de nom.", NamedTextColor.RED),
)
add(
Component.text("Il y a écrit que un de ces 3 citoyens", NamedTextColor.DARK_RED),
)
add(
Component.text("est un loup-garou.", NamedTextColor.DARK_RED),
)
val players = Game.current?.playersMutable?.shuffled().orEmpty()
val players =
Game.current
?.remainingPlayer
?.shuffled()
.orEmpty()
val (villagers, werewolves) = players.partition {
Role.getRole(it)?.lg == false
val (villagers, werewolves) =
players.partition {
Role.getRole(it)?.team != Role.Team.LG
}
val lg = werewolves.firstOrNull()
val names = mutableListOf<Player>()
if (lg != null) names.add(lg)
names.addAll(villagers.take(2))
val colors = NamedColors.namedColors.shuffled().toMutableList()
names.shuffled().forEach {
val color = colors.removeFirst()
add(Component.text("- ").append(it.displayName()).color(color))
}
if (isEmpty()) {
add(Component.text("nothing", NamedTextColor.DARK_RED, TextDecoration.OBFUSCATED))
}
}
val lg = werewolves.firstOrNull()
val names = mutableListOf<Player>()
if (lg != null) names.add(lg)
names.addAll(villagers.take(2))
val colors = NamedColors.namedColors.shuffled().toMutableList()
names.shuffled().forEach {
val color = colors.removeFirst()
add(Component.text("- ").append(it.displayName()).color(color))
}
if (isEmpty()) {
add(Component.text("nothing", NamedTextColor.DARK_RED, TextDecoration.OBFUSCATED))
}
}
ItemStack(Material.PAPER).apply {
itemMeta = itemMeta.apply {
displayName(Component.text("Liste de nom", NamedTextColor.GOLD))
lore(finalLore)
}
itemMeta =
itemMeta.apply {
displayName(Component.text("Liste de nom", NamedTextColor.GOLD))
lore(finalLore)
}
}
});
}),
;
private var cachedItem: ItemStack? = null
@@ -206,21 +241,27 @@ enum class CustomItems(val cache: Boolean, val supplier: () -> ItemStack) {
}
}
data object NamedColors {
val namedColors = listOf(
NamedTextColor.DARK_RED,
NamedTextColor.RED,
NamedTextColor.GOLD,
NamedTextColor.YELLOW,
NamedTextColor.DARK_GREEN,
NamedTextColor.GREEN,
NamedTextColor.AQUA,
NamedTextColor.DARK_AQUA,
NamedTextColor.DARK_BLUE,
NamedTextColor.BLUE,
NamedTextColor.LIGHT_PURPLE,
NamedTextColor.DARK_PURPLE
)
interface DamageablePotion :
Damageable,
PotionMeta {
override fun clone(): DamageablePotion
}
}
data object NamedColors {
val namedColors =
listOf(
NamedTextColor.DARK_RED,
NamedTextColor.RED,
NamedTextColor.GOLD,
NamedTextColor.YELLOW,
NamedTextColor.DARK_GREEN,
NamedTextColor.GREEN,
NamedTextColor.AQUA,
NamedTextColor.DARK_AQUA,
NamedTextColor.DARK_BLUE,
NamedTextColor.BLUE,
NamedTextColor.LIGHT_PURPLE,
NamedTextColor.DARK_PURPLE,
)
}
}

View File

@@ -2,21 +2,22 @@ package fr.azur.tcoww.roles
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.entity.Player
class Child : Role {
override fun handle(player: Player) {}
override fun onDeath(player: Player) {}
override var lg = false
override var id = NamespacedKey("tcoww", "child")
override val displayName = Component.text("Petite Fille")
object Child : Role {
override val id = NamespacedKey("tcoww", "child")
override val team = Role.Team.Villager
override val displayName = Component.text("Petite Fille", NamedTextColor.LIGHT_PURPLE)
override val icon = Material.MAGENTA_WOOL
override val order: Int = 2
override val hasPowerCommand = false
override val description =
Component.text("Extrêmement discrette et au coeur du risque.", NamedTextColor.BLUE).appendNewline()
.append(
Component.text("Vous n'êtes pas obliger de dormir la nuit, vous pouvez donc observer les loups le soir.", NamedTextColor.BLUE)
)
}
listOf(
Component.text("Extrêmement discrette et au coeur du risque.", NamedTextColor.BLUE),
Component.text(
"Vous n'êtes pas obliger de dormir la nuit, vous pouvez donc observer les loups le soir.",
NamedTextColor.BLUE,
),
)
}

View File

@@ -3,23 +3,27 @@ package fr.azur.tcoww.roles
import fr.azur.tcoww.items.CustomItems
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.entity.Player
class FortuneTeller : Role {
object FortuneTeller : Role {
override fun handle(player: Player) {
player.inventory.addItem(CustomItems.CrystalBall.item)
player.inventory.setItem(9, CustomItems.CrystalBall.item)
}
override fun onDeath(player: Player) {}
override var lg = false
override val team = Role.Team.Villager
override var id = NamespacedKey("tcoww", "fortune_teller")
override val displayName = Component.text("Voyante")
override val icon = Material.GLASS
override val order: Int = 3
override val displayName = Component.text("Voyante", NamedTextColor.AQUA)
override val hasPowerCommand = true
override val description =
Component.text("Vous voyez des choses mais quoi ?.", NamedTextColor.BLUE).appendNewline()
.append(
Component.text("Tout les 2 jours vous pouvez regarder le role d'un joueur.", NamedTextColor.BLUE)
)
}
listOf(
Component.text("Vous voyez des choses mais quoi ?.", NamedTextColor.BLUE),
Component
.text("Tout les 2 jours vous pouvez regarder le role d'un joueur grâce à votre ", NamedTextColor.BLUE)
.append(CustomItems.CrystalBall.item.displayName())
.append(Component.text(".", NamedTextColor.BLUE)),
)
}

View File

@@ -3,32 +3,32 @@ package fr.azur.tcoww.roles
import fr.azur.tcoww.items.CustomItems
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.entity.Player
class Hunter : Role {
object Hunter : Role {
override fun handle(player: Player) {
player.inventory.addItem(CustomItems.BlasTechDL44.item)
player.inventory.setItem(9, CustomItems.BlasTechDL44.item)
}
override fun onDeath(player: Player) {
TODO("Not yet implemented")
}
override var lg = false
override val team = Role.Team.Villager
override var id = NamespacedKey("tcoww", "hunter")
override val displayName = Component.text("Chaseur")
override val displayName = Component.text("Chaseur", NamedTextColor.GOLD)
override val icon = Material.ARROW
override val order: Int = 5
override val hasPowerCommand = false
override val description =
Component.text(
"Vous êtes la personne, la plus redouter des animaux (et en particulier des loups).",
NamedTextColor.BLUE
).appendNewline()
.append(
Component.text("Vous possedez un ", NamedTextColor.BLUE)
.append(CustomItems.BlasTechDL44.item.displayName())
.append(
Component.text(" qui permet de one shot n'importe quoi... (Usage Unique.)", NamedTextColor.BLUE)
)
)
}
listOf(
Component.text(
"Vous êtes la personne, la plus redouter des animaux (et en particulier des loups).",
NamedTextColor.BLUE,
),
Component
.text("Vous possedez un ", NamedTextColor.BLUE)
.append(CustomItems.BlasTechDL44.item.displayName())
.append(
Component.text(" qui permet de one shot n'importe quoi... (Usage Unique)", NamedTextColor.BLUE),
),
)
}

View File

@@ -1,49 +1,91 @@
package fr.azur.tcoww.roles
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.JoinConfiguration
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.entity.Player
import org.bukkit.persistence.PersistentDataType
interface Role {
fun handle(player: Player)
fun onDeath(player: Player)
val lg: Boolean
enum class Team {
LG {
override val displayName = Component.text("loups-garous").color(NamedTextColor.DARK_RED)
},
Villager {
override val displayName: Component = Component.text("villageois", NamedTextColor.GREEN)
},
Solo {
override val displayName: Component = Component.text("solo", NamedTextColor.YELLOW)
}, ;
abstract val displayName: Component
}
fun handle(player: Player) {}
fun onDeath(player: Player) {}
fun onVote(player: Player) {}
val id: NamespacedKey
val icon: Material
val order: Int
val team: Team
val displayName: Component
val hasPowerCommand: Boolean
val description: Component
val description: List<Component>
val lineDescription: Component
get() {
val joinConfig =
JoinConfiguration
.builder()
.separator(Component.newline())
.build()
return Component.join(joinConfig, description)
}
companion object {
var registerRoles = hashMapOf<NamespacedKey, Role>()
private val rolekey = NamespacedKey("tcoww", "role")
fun setRole(player: Player, role: Role) {
player.persistentDataContainer.set(
rolekey,
PersistentDataType.STRING,
role.id.toString()
)
private val roleKey = NamespacedKey("tcoww", "role")
fun setRole(
player: Player,
role: Role,
) {
player.sendMessage(
Component.text("Votre role est ").append(role.displayName).color(NamedTextColor.DARK_AQUA)
Component.text("Votre role est ", NamedTextColor.DARK_AQUA).append(role.displayName),
)
player.sendMessage(role.description.appendNewline().append {
if (role.lg)
Component.text("Vous gagnez avec les loups-garous.", NamedTextColor.AQUA)
else
Component.text("Vous gagnez avec les villageois.", NamedTextColor.AQUA)
})
player.sendMessage(role.lineDescription)
player.sendMessage(
if (role.team == Team.Solo) {
Component.text("Vous gagnez Seul.", NamedTextColor.AQUA)
} else {
Component
.text("Vous gagnez avec les ", NamedTextColor.AQUA)
.append(role.team.displayName)
.append(Component.text(".", NamedTextColor.AQUA))
},
)
role.handle(player)
player.persistentDataContainer.set(roleKey, PersistentDataType.STRING, role.id.toString())
}
fun getRole(player: Player): Role? {
val rolestr = player.persistentDataContainer.get(rolekey, PersistentDataType.STRING)
if (rolestr == null) return null
return registerRoles[NamespacedKey.fromString(rolestr)]
val roleKey = player.persistentDataContainer.get(roleKey, PersistentDataType.STRING) ?: return null
return registerRoles[NamespacedKey.fromString(roleKey)]
}
var Player.werewolfRole: Role
get() = getRole(this)!!
set(role) = setRole(this, role)
fun registerRole(role: Role) {
registerRoles.put(role.id, role)
registerRoles[role.id] = role
}
}
}
}

View File

@@ -2,25 +2,19 @@ package fr.azur.tcoww.roles
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.entity.Player
class Villager : Role {
override fun handle(player: Player) {
}
override fun onDeath(player: Player) {
}
override var lg: Boolean = false
object Villager : Role {
override val team = Role.Team.Villager
override var id = NamespacedKey("tcoww", "villager")
override val displayName = Component.text("Villageois")
override val displayName = Component.text("Villageois", NamedTextColor.GRAY)
override val icon = Material.DIRT
override val order: Int = 0
override val hasPowerCommand = false
override val description =
Component.text("En tant que villageois, vous n'avez aucun pouvoir.", NamedTextColor.BLUE).appendNewline()
.append(
Component.text("Profiter en pour risquer pour trouver des indices ! ^^", NamedTextColor.BLUE)
)
}
listOf(
Component.text("En tant que villageois, vous n'avez aucun pouvoir.", NamedTextColor.BLUE),
Component.text("Profiter en pour risquer pour trouver des indices ! ^^", NamedTextColor.BLUE),
)
}

View File

@@ -1,25 +1,40 @@
package fr.azur.tcoww.roles
import fr.azur.tcoww.Tcoww
import fr.azur.tcoww.items.CustomItems
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.entity.Player
class Werewolf : Role {
override fun handle(player: Player) {}
override fun onDeath(player: Player) {}
override var lg = true
object Werewolf : Role {
var remainingKill = 0
override val team = Role.Team.LG
override var id = NamespacedKey("tcoww", "werewolf")
override val displayName = Component.text("Loup-Garou")
override val displayName = Component.text("Loup-Garou", NamedTextColor.DARK_GRAY)
override val icon = Material.BONE
override val order: Int = 1
override val hasPowerCommand = false
override val description =
Component.text("Pour la faire courte, vous avez faim, très faim.", NamedTextColor.BLUE).appendNewline()
.append(
Component.text(
"Votre but est de tuer les villageois. Sans vous faire atraper et voter. Toute les nuits, vous pouvez vous tranformer en loup pour manger un villageois.",
NamedTextColor.BLUE
)
)
}
listOf(
Component.text("Pour la faire courte, vous avez faim, très faim.", NamedTextColor.BLUE),
Component.text(
"Votre but est de tuer les villageois. Sans vous faire atraper et voter.",
NamedTextColor.BLUE,
),
Component.text(
"Toute les nuits, vous pouvez vous tranformer en loup pour manger un villageois.",
NamedTextColor.BLUE,
),
)
override fun handle(player: Player) {
val tfItem = CustomItems.WereWolfTransformItem.item
player.inventory.setItem(9, tfItem)
player.setCooldown(
tfItem,
((Tcoww.instance.config.getInt("duration.day") + Tcoww.instance.config.getInt("duration.day")) * 20),
)
}
}

View File

@@ -3,27 +3,28 @@ package fr.azur.tcoww.roles
import fr.azur.tcoww.items.CustomItems
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.entity.Player
class Witch : Role {
object Witch : Role {
override fun handle(player: Player) {
player.inventory.addItem(CustomItems.DeathPotion.item)
player.inventory.addItem(CustomItems.HealPotion.item)
player.inventory.setItem(9, CustomItems.DeathPotion.item)
player.inventory.setItem(10, CustomItems.HealPotion.item)
}
override fun onDeath(player: Player) {}
override var lg = false
override val team = Role.Team.Villager
override var id = NamespacedKey("tcoww", "witch")
override val displayName = Component.text("Sorcière")
override val icon = Material.GLASS_BOTTLE
override val order: Int = 4
override val hasPowerCommand = true
override val description =
Component.text("Une potion = Une solution.", NamedTextColor.BLUE).appendNewline()
.append(
Component.text(
"Vous possédez deux potions : une pour tuer et une pour ressusciter quelqu'un.",
NamedTextColor.BLUE
)
).appendNewline()
}
listOf(
Component.text("Une potion = Une solution.", NamedTextColor.BLUE),
Component.text(
"Vous possédez deux potions : une pour tuer et une pour ressusciter quelqu'un.",
NamedTextColor.BLUE,
),
)
}

View File

@@ -0,0 +1,11 @@
package fr.azur.tcoww.ui
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.inventory.InventoryHolder
interface CustomUI : InventoryHolder {
fun onClick(event: InventoryClickEvent) {}
fun onClose(event: InventoryCloseEvent) {}
}

View File

@@ -0,0 +1,121 @@
package fr.azur.tcoww.ui
import fr.azur.tcoww.Tcoww
import fr.azur.tcoww.roles.Role
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.NamespacedKey
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.Damageable
import java.util.concurrent.CompletableFuture
import kotlin.math.ceil
class GameConfig(
playerCount: Int,
) : CustomUI {
// [index, [role key, amount]]
val rolesConfig = hashMapOf<Int, Pair<NamespacedKey, Int>>()
val size = (Role.registerRoles.count() / 9 + 1) * 9
val future = CompletableFuture<Map<NamespacedKey, Int>>()
private val inventory =
Tcoww.instance.server.createInventory(this, size, Component.text("Game Config.", NamedTextColor.GREEN)).apply {
Role.registerRoles.values.sortedBy { it.order }.onEachIndexed { index, role ->
setItem(index, createItem(index, role))
}
}
init {
val werewolfCount = ceil((playerCount * (Tcoww.instance.config.getInt("werewolf-default-percentage") / 100f))).toInt()
editItem(0, playerCount - werewolfCount)
editItem(1, werewolfCount)
}
private fun createItem(
index: Int,
role: Role,
): ItemStack {
rolesConfig[index] = Pair(role.id, 0)
return ItemStack.of(role.icon).apply {
itemMeta =
(itemMeta as Damageable).apply {
lore(
role.description,
)
itemName(
role.displayName,
)
setMaxStackSize(1)
setMaxDamage(1)
damage = 1
}
}
}
private fun editItem(
index: Int,
newAmount: Int,
) {
if (newAmount !in 0..99) throw IllegalArgumentException("Amount must be in 0 and 99 !")
rolesConfig[index] = Pair(rolesConfig[index]!!.first, newAmount)
val item = inventory.getItem(index) ?: return
item.itemMeta =
(item.itemMeta as Damageable).apply {
if (newAmount == 0) {
setMaxStackSize(1)
setMaxDamage(1)
damage = 1
} else {
damage = 0
setMaxStackSize(99)
item.amount = newAmount
}
}
}
override fun getInventory(): Inventory = inventory
override fun onClick(event: InventoryClickEvent) {
event.isCancelled = true
val index = event.slot
if (index !in 0..<Role.registerRoles.size || event.clickedInventory != inventory) return
if (event.isLeftClick) {
for (i in 0..<Role.registerRoles.size) {
if (i == index) continue
val count = rolesConfig[i]!!.second
if (count != 0) {
val targetCount = rolesConfig[index]!!.second
editItem(i, count - 1)
editItem(index, targetCount + 1)
break
}
}
} else if (event.isRightClick) {
for (i in 0..<Role.registerRoles.size) {
if (i == index) continue
val count = rolesConfig[i]!!.second
if (count != 0) {
val targetCount = rolesConfig[index]!!.second
if (targetCount == 0) break
editItem(i, count + 1)
editItem(index, targetCount - 1)
break
}
}
}
}
override fun onClose(event: InventoryCloseEvent) {
future.complete(
buildMap {
rolesConfig.forEach {
if (it.value.second != 0) put(it.value.first, it.value.second)
}
},
)
}
}

View File

@@ -0,0 +1,55 @@
package fr.azur.tcoww.ui
import fr.azur.tcoww.Tcoww
import fr.azur.tcoww.utils.Players.isTransformed
import fr.azur.tcoww.utils.skins.Manager
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.SkullMeta
import java.util.concurrent.CompletableFuture
class PlayerSelectMenu(
val players: Iterable<Player>,
) : CustomUI {
val size = (players.count() / 9 + 1) * 9
val future = CompletableFuture<Player?>()
private val inventory =
Tcoww.instance.server.createInventory(this, size, Component.text("Select a player.", NamedTextColor.GREEN)).apply {
players.forEachIndexed { index, player ->
val item =
ItemStack.of(Material.PLAYER_HEAD).apply {
itemMeta =
(itemMeta as SkullMeta).apply {
playerProfile =
if (player.isTransformed()) {
Manager.oldPlayerProfile[player.uniqueId]
} else {
player.playerProfile
}
}
}
setItem(index, item)
}
}
override fun getInventory(): Inventory = inventory
override fun onClick(event: InventoryClickEvent) {
val index = event.slot
event.isCancelled = true
if (index !in 0..<players.count() || event.clickedInventory != inventory) return
future.complete(players.elementAt(index))
event.inventory.close()
}
override fun onClose(event: InventoryCloseEvent) {
future.complete(null)
}
}

View File

@@ -4,21 +4,19 @@ import org.bukkit.block.Block
import org.bukkit.block.data.type.Bed
object BedGestion {
fun isBed(block: Block): Boolean {
return block.blockData is Bed
}
fun Block.isBed() = blockData is Bed
fun getHeadBed(block: Block): Block {
val bedData = block.blockData as Bed
fun Block.getHeadBed(): Block {
val bedData = blockData as Bed
return if (bedData.part == Bed.Part.FOOT) {
block.getRelative(bedData.facing)
getRelative(bedData.facing)
} else {
block
this
}
}
fun isOcupied(block: Block): Boolean {
val bedData = block.blockData as Bed
fun Block.isOccupied(): Boolean {
val bedData = blockData as Bed
return bedData.isOccupied
}
}
}

View File

@@ -0,0 +1,11 @@
package fr.azur.tcoww.utils
import fr.azur.tcoww.Tcoww
import org.bukkit.NamespacedKey
data object DataKeys {
val PlayerDead = NamespacedKey(Tcoww.instance, "dead")
val PowerItems = NamespacedKey(Tcoww.instance, "power")
val Insomnia = NamespacedKey(Tcoww.instance, "insomnia")
val ToolsItems = NamespacedKey(Tcoww.instance, "tools")
}

View File

@@ -0,0 +1,118 @@
package fr.azur.tcoww.utils
import fr.azur.tcoww.game.Game
import fr.azur.tcoww.roles.Role.Companion.werewolfRole
import fr.azur.tcoww.utils.skins.Manager
import io.papermc.paper.datacomponent.item.ResolvableProfile
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import net.kyori.adventure.title.Title
import org.bukkit.Bukkit
import org.bukkit.GameMode
import org.bukkit.NamespacedKey
import org.bukkit.attribute.Attribute
import org.bukkit.attribute.AttributeModifier
import org.bukkit.entity.Mannequin
import org.bukkit.entity.Player
import org.bukkit.entity.Pose
import org.bukkit.persistence.PersistentDataType
import java.time.Duration
object Players {
private val transformKey = NamespacedKey("tcoww", "werewolf_transform")
val corpseKey = NamespacedKey("tcoww", "corpse")
val corpseRoleKey = NamespacedKey("tcoww", "corpse_role")
private val noCollisionTeam =
Bukkit
.getServer()
.scoreboardManager.mainScoreboard
.getTeam("NoCollision")
fun Player.tfWerewolf() {
if (isTransformed()) return
Manager.applyWerewolfFur(this)
persistentDataContainer.set(
transformKey,
PersistentDataType.BOOLEAN,
true,
)
val speedAtr = getAttribute(Attribute.MOVEMENT_SPEED)!!
val scaleAtr = getAttribute(Attribute.SCALE)!!
speedAtr.addModifier(
AttributeModifier(
transformKey,
0.02,
AttributeModifier.Operation.ADD_NUMBER,
),
)
scaleAtr.addModifier(
AttributeModifier(
transformKey,
0.1,
AttributeModifier.Operation.ADD_NUMBER,
),
)
}
fun Player.tfHuman() {
if (!isTransformed()) return
Manager.unapplySkin(this)
persistentDataContainer.set(
transformKey,
PersistentDataType.BOOLEAN,
false,
)
val speedAtr = getAttribute(Attribute.MOVEMENT_SPEED)!!
val scaleAtr = getAttribute(Attribute.SCALE)!!
speedAtr.removeModifier(transformKey)
scaleAtr.removeModifier(transformKey)
}
fun Player.isTransformed(): Boolean =
persistentDataContainer.getOrDefault(
transformKey,
PersistentDataType.BOOLEAN,
false,
)
fun Player.kill() {
if (isTransformed()) tfHuman()
val current = Game.current ?: return
val corpse = createCorpse(this)
corpse.spawnAt(this.location)
this.gameMode = GameMode.SPECTATOR
this.isInvulnerable = false
current.remainingPlayer.remove(this)
this.showTitle(
Title.title(
Component.text("Vous êtes mort.", NamedTextColor.DARK_RED),
Component.empty(),
Title.Times.times(Duration.ZERO, Duration.ofSeconds(2), Duration.ofMillis(500)),
),
)
if (current.checkGameEnd()) current.stopGame()
}
fun createCorpse(player: Player): Mannequin =
player.world.createEntity(player.location, Mannequin::class.java).apply {
profile = ResolvableProfile.resolvableProfile(player.playerProfile)
noCollisionTeam?.addEntity(this)
isInvulnerable = true
pose = Pose.SLEEPING
persistentDataContainer.set(corpseKey, PersistentDataType.BOOLEAN, true)
val role = player.werewolfRole
persistentDataContainer.set(corpseRoleKey, PersistentDataType.STRING, role.id.toString())
}
}

View File

@@ -1,65 +0,0 @@
package fr.azur.tcoww.utils
import net.skinsrestorer.api.SkinsRestorer
import net.skinsrestorer.api.property.SkinProperty
import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin
import java.util.*
import kotlin.jvm.optionals.getOrNull
import kotlin.random.Random
class Skin(private val plugin: Plugin, private val skinsRestorer: SkinsRestorer) {
var maxSkinId: Int = 0
private set
init {
reloadSkin()
}
fun reloadSkin() {
val skinsValue = plugin.config.getStringList("wwskinsvalue")
val skinsSignature = plugin.config.getStringList("wwskinssignature")
Stockage.wwPlayerFur.clear()
maxSkinId = 0
skinsValue.zip(skinsSignature).forEachIndexed { index, (value, signature) ->
skinsRestorer.skinStorage.setCustomSkinData("tcoww_$index", SkinProperty.of(value, signature))
maxSkinId = index
}
}
fun applyWerewolfFur(player: Player) {
val skin = skinsRestorer.playerStorage.getSkinIdOfPlayer(player.uniqueId)
Stockage.oldPlayerSkin[player.uniqueId] = skin.getOrNull()
val result = skinsRestorer.skinStorage.findOrCreateSkinData("tcoww_${getWerewolfFur(player.uniqueId)}")
if (!result.isEmpty) {
skinsRestorer.playerStorage.setSkinIdOfPlayer(player.uniqueId, result.get().identifier)
skinsRestorer.getSkinApplier(Player::class.java).applySkin(player)
}
}
fun unApplySkin(player: Player) {
val skin = Stockage.oldPlayerSkin[player.uniqueId]
if (skin == null) {
skinsRestorer.playerStorage.removeSkinIdOfPlayer(player.uniqueId)
} else {
skinsRestorer.playerStorage.setSkinIdOfPlayer(player.uniqueId,skin)
}
skinsRestorer.getSkinApplier(Player::class.java).applySkin(player)
}
private fun getWerewolfFur(playerUUID: UUID): Int {
return Stockage.wwPlayerFur[playerUUID]
?: randomFur(playerUUID)
}
private fun randomFur(playerUUID: UUID): Int {
val id = if (maxSkinId == 0) 0 else Random(playerUUID.mostSignificantBits xor playerUUID.leastSignificantBits).nextInt(maxSkinId)
Stockage.wwPlayerFur[playerUUID] = id
return id
}
}

View File

@@ -1,13 +0,0 @@
package fr.azur.tcoww.utils
import net.skinsrestorer.api.property.SkinIdentifier
import org.bukkit.Location
import java.util.*
object Stockage {
var backLocation = mutableMapOf<UUID, Location>()
var vote = mutableMapOf<UUID, UUID>()
var oldPlayerSkin = mutableMapOf<UUID, SkinIdentifier?>()
var wwPlayerFur = mutableMapOf<UUID, Int>()
}

View File

@@ -0,0 +1,73 @@
package fr.azur.tcoww.utils
import de.maxhenkel.voicechat.api.Group
import de.maxhenkel.voicechat.api.VoicechatPlugin
import de.maxhenkel.voicechat.api.VoicechatServerApi
import de.maxhenkel.voicechat.api.events.EventRegistration
import de.maxhenkel.voicechat.api.events.LeaveGroupEvent
import de.maxhenkel.voicechat.api.events.VoicechatServerStartedEvent
import fr.azur.tcoww.game.Game
import fr.azur.tcoww.game.NightLightCycle
import org.bukkit.entity.Player
import java.util.UUID
import kotlin.random.Random
object VoiceChatPlugin : VoicechatPlugin {
lateinit var api: VoicechatServerApi
lateinit var werewolfGroup: Group
override fun getPluginId(): String = "tcoww"
override fun registerEvents(registration: EventRegistration) {
registration.registerEvent(VoicechatServerStartedEvent::class.java, this::onServerStarted)
registration.registerEvent(LeaveGroupEvent::class.java, this::onGroupRemove)
}
private fun onServerStarted(event: VoicechatServerStartedEvent) {
api = event.voicechat
werewolfGroup =
api
.groupBuilder()
.setPersistent(true)
.setName("werewolf")
.setPassword(Random.nextInt().toString())
.setType(Group.Type.NORMAL)
.setHidden(true)
.build()
}
private fun onGroupRemove(event: LeaveGroupEvent) {
val current = Game.current ?: return
if ((
current.timeGestion.phase == NightLightCycle.Phase.NIGHT ||
current.timeGestion.phase == NightLightCycle.Phase.CREPUSCULAR
)
) {
event.cancel()
}
}
fun addWerewolf(player: Player) {
val connection = api.getConnectionOf(player.uniqueId) ?: return
connection.group = werewolfGroup
}
fun createSoloGroup(player: Player) {
val connection = api.getConnectionOf(player.uniqueId) ?: return
val soloGroup =
api
.groupBuilder()
.setName(UUID.randomUUID().toString())
.setHidden(true)
.setPassword(Random.nextInt().toString())
.setType(Group.Type.ISOLATED)
.setPersistent(false)
.build()
connection.group = soloGroup
}
fun degroupPlayer(player: Player) {
val connection = api.getConnectionOf(player.uniqueId) ?: return
connection.group = null
}
}

View File

@@ -0,0 +1,80 @@
package fr.azur.tcoww.utils.skins
import com.destroystokyo.paper.profile.PlayerProfile
import fr.azur.tcoww.Tcoww
import net.skinsrestorer.api.SkinsRestorer
import net.skinsrestorer.api.SkinsRestorerProvider
import net.skinsrestorer.api.property.SkinIdentifier
import net.skinsrestorer.api.property.SkinProperty
import org.bukkit.entity.Player
import java.util.UUID
import kotlin.jvm.optionals.getOrNull
import kotlin.random.Random
object Manager {
var maxSkinId: Int = 0
private lateinit var skinsRestorer: SkinsRestorer
var oldPlayerSkin = mutableMapOf<UUID, SkinIdentifier?>()
var oldPlayerProfile = mutableMapOf<UUID, PlayerProfile>()
var wwPlayerFur = mutableMapOf<UUID, Int>()
fun handle() {
skinsRestorer = SkinsRestorerProvider.get()
reloadSkin()
}
fun reloadSkin() {
val skinsValue =
Tcoww.instance.config
.getList("werewolf-skins")
.orEmpty()
.mapNotNull { it as? WerewolfSkin }
wwPlayerFur.clear()
maxSkinId = skinsValue.size
skinsValue.forEachIndexed { index, skin ->
skinsRestorer.skinStorage.setCustomSkinData("tcoww_$index", SkinProperty.of(skin.value, skin.signature))
}
}
fun applyWerewolfFur(player: Player) {
val skin = skinsRestorer.playerStorage.getSkinIdOfPlayer(player.uniqueId)
oldPlayerSkin[player.uniqueId] = skin.getOrNull()
oldPlayerProfile[player.uniqueId] = player.playerProfile
val result = skinsRestorer.skinStorage.findOrCreateSkinData("tcoww_${getWerewolfFur(player.uniqueId)}")
if (!result.isEmpty) {
skinsRestorer.playerStorage.setSkinIdOfPlayer(player.uniqueId, result.get().identifier)
skinsRestorer.getSkinApplier(Player::class.java).applySkin(player)
}
}
fun unapplySkin(player: Player) {
val skin = oldPlayerSkin[player.uniqueId]
if (skin == null) {
skinsRestorer.playerStorage.removeSkinIdOfPlayer(player.uniqueId)
} else {
skinsRestorer.playerStorage.setSkinIdOfPlayer(player.uniqueId, skin)
}
skinsRestorer.getSkinApplier(Player::class.java).applySkin(player)
}
private fun getWerewolfFur(playerUUID: UUID): Int =
wwPlayerFur[playerUUID]
?: randomFur(playerUUID)
private fun randomFur(playerUUID: UUID): Int {
val id =
if (maxSkinId == 0) {
0
} else {
Random(playerUUID.mostSignificantBits xor playerUUID.leastSignificantBits).nextInt(
maxSkinId,
)
}
wwPlayerFur[playerUUID] = id
return id
}
}

View File

@@ -0,0 +1,22 @@
package fr.azur.tcoww.utils.skins
import org.bukkit.configuration.serialization.ConfigurationSerializable
data class WerewolfSkin(
val value: String,
val signature: String,
) : ConfigurationSerializable {
constructor(args: MutableMap<String, Object>) : this(
args["value"] as String,
args["signature"] as String,
)
override fun serialize(): Map<String, String> {
val data: MutableMap<String, String> = HashMap()
data["value"] = value
data["signature"] = signature
return data
}
}

View File

@@ -1,14 +0,0 @@
package fr.azur.tcoww.voicechat
import de.maxhenkel.voicechat.api.VoicechatPlugin
import de.maxhenkel.voicechat.api.events.EventRegistration
class VoiceChatPlugin: VoicechatPlugin {
override fun getPluginId(): String {
return "tcow"
}
override fun registerEvents(registration: EventRegistration) {
}
}

View File

@@ -1,38 +1,35 @@
spawnLocation:
==: org.bukkit.Location
world: world
x: 0.0
y: 0.0
z: 0.0
pitch: 0.0
yaw: 0.0
lobbyLocation:
==: org.bukkit.Location
world: world
x: 0.0
y: 0.0
z: 0.0
pitch: 0.0
yaw: 0.0
# Bukkit Location
spawn-location: null
# Bukkit Location
lobby-location: null
# List of Bukkit Location
bed-locations: null
duration: # In seconds
day: 600
vote: 120
night: 300
crepuscular: 20
wwcount: 10
werewolf-default-percentage: 20
# The skin value
wwskinsvalue: [
"ewogICJ0aW1lc3RhbXAiIDogMTYxNzQ2NzI1ODQxMCwKICAicHJvZmlsZUlkIiA6ICJjMGYzYjI3YTUwMDE0YzVhYjIxZDc5ZGRlMTAxZGZlMiIsCiAgInByb2ZpbGVOYW1lIiA6ICJDVUNGTDEzIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2Y5NmM4OWUwZWYwZmVlZjZkODc4ZDUzYzQ4OWIyMzliNDdlNmViODQyZDJmM2MzZTlhZDdkMjliZmYxYTM0OWUiCiAgICB9CiAgfQp9",
"ewogICJ0aW1lc3RhbXAiIDogMTYxNTYxMTkwOTY4MCwKICAicHJvZmlsZUlkIiA6ICIwMGM2Yjk0YTY5YmU0MzY3OTkwOTQxNjFjMjAxOWI3ZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJLQUVWRVJZIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2I0MGRjNTcxZGUwZGQ4ZmQ5NzBjNzNmOTE2NGJjMTQ0Njk1MWFlNDhkNWFiZTQyMzI5MGVlYjFhODc4NDU1ZmUiCiAgICB9CiAgfQp9",
"ewogICJ0aW1lc3RhbXAiIDogMTc0Nzk4MDc5ODQyMywKICAicHJvZmlsZUlkIiA6ICI2NWI5NTU1YWQyMGU0NWM5YjFkNmU3MjQwNjU0NTBkNCIsCiAgInByb2ZpbGVOYW1lIiA6ICJKYXZhUHJvZmlsZU5hbWVYIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzJlZTlmNjcxYzFmNjY4Y2IwYWUwNGE5YzhiZWM0OTVlYTVmNzNmNTNmYmY3ZjM5MmNkYjdlMTc1ZmVjZDFjYmUiCiAgICB9CiAgfQp9",
"ewogICJ0aW1lc3RhbXAiIDogMTY0Mjc4ODk3NDg1NSwKICAicHJvZmlsZUlkIiA6ICIzYzE0YmVkNDFiOGE0MDIzOGM3MDgzMTA1NzEwMTZmYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJOb2Jpa28iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWZlNzIzMDdjZTEyNmI1Y2I0ZDQ0ZGFlZTcwZDI1ZmQwNDMwOGJjN2M0MzI4OWM3ZDk3MDY4NDY5NWI2MGJhIgogICAgfQogIH0KfQ=="
]
# The skin signature (see mineskin.org)
wwskinssignature: [
"uWIu/2R3zImj87H7x//O/04eJ1SwsHvLSxaj1YQuI1vBndUVHhN42Ja7AOJoAieVO8yN1qn/rJj1oK5z5koQ2fZwjNCc0mKTrakCMWGqjaAImDw8RWriUQ3ClPCw6NtCmB1qVUp60kUEpGha/btUSsAEUnVZ0ghM4SXTcS2izbDe/dRfbi5EDr2Uue3sz6+med3hjecZzrKJUX19wP1WwPifEOH2X/smzzGOdqlQtylMZQh3nNeho/GBwbDVXsXsR6LcDgoInDnw63cDV7n4dTs6giGmcx0knhDWH24Z/vrgNSqzbNY9hIgVBNWUqs/1o7/xrbK1HilYBPAiVO0F6+vJOpzdFrpEZVr0M++u+X2Zkt1sfshKZqt0O07KbH8h4xj3J+6iDF5RHHFGmZy0awFGZGIi8wqlffwQfZ+l7nPU1hpMD5DkArxBWJWgnquyCNpQ44OlmTpPRa/gopRpuMwJ1r6vDPhgIavJX6cKQ2lKTYKG2/HB+ZEl/IUtuXxKO7bFe4KOvjJpMwBjOmzqbn2sk3GqbSg8NfNMFA4FjRBvHXOaMsDCqz0MJxLFKvIfYRVWG8XAU6BU82RoS3qGFSOhI6N/DvD7dx1V40q52iWv2j4/S41P5zrtMqQm7wofocptz6sS7GLE1u9rPCBLRIck0wzi3OF7MV2p4wlw6lw=",
"kVGwx98YPc6tIZ306TiGI5eoK4v7oEjIvCaOPS03jq4LjBWV12NBTWW67u+HrI2/2m3r8pvAyx1Ddpw4YWyaDbaG0FvLuurWbETqtwvyvVx7R3BZHgw2EnVOYk4+WPmA8xUgzoIbZF/Ch4bSqP7UlZy7/fz4AQwFK/qLMzApOAeV4Kzn4p6+MK2uOkfSjGgwxqAO/+TZj6u01mk1NKNh4ors9RGDG3DBHZKSxTG/PzY5ErKABRXkB/RBMA5IvU5Rzn5cH+Sfkus/7LtYp5u+5i+HcpN/klyBBw4D5di3WzFOAIX78ZiyKzKzWpSN0z6Cb2DZdo1ufeZH9mw8nv+A8VfpGXwphwde+oGBP4l4OffE7YvbnWyVHmlG0L36iI2/FkWQl91GIT15YBHrjeQ9pmvbzCQUMyAHi2jZePTTcT1rDnSzBk7fc9IwfcBRNyqbveTNu9zikfx0W6GnXAbV4qOTIIVEHlpxQ9KW017sqmgiF5l/3ZVwqbEQzglNehYHA55vRfKY0HKbGB54k1oTnM3X7mNXIu6fk8dVgCvT8lVdDm3LWcE6aWF8Yp6QrXNvTNW1yxSRAhigsHrvbajp2Odg18lMTng0oh/4JiYOQAgb6tFdRtQ4nrSVHRBOEvY09IRFzVTTd7n2JaYZ6QcCMOiKOX25fv22rbjSeAZ/Whk=",
"wGc2rNbc7+nziEDMKFoBIltHZwx5PQfsfMGyRTfTUdtVi3O4iu0idcpTsTVJ80njIFZvmLpzlwjPu6ULrXJwgQQyGCNdp60jQMl9vGjzgJzs6uqhSCke3EvjOCuAGkv2ScRY5hDGQOnIruxwmgYgDeXHnujt+zTBvW+imxLVaZhXoKVPyqlvm6njWp0XECyAi8DFKbzIdDVp+NsRzIGjKeVoMgWd4xlNazUZGgTsXNCD/+aZOJ1LxA51I8Wgsm888MUXnaGhNI3U4jJNKHAPZMS4++JEuwWzlptJdZIa1jOfwMD6HvIPKEmC5+eDCRFvPIEh84VqErtNTPEUAz7wZAJvO+j+x5A/9VnQmKJhEBxBjlw1Ky+mQcLJnRX/x9jc4EvEd9gWGIUj46y3IF8jWn71eXvinSJ4Op4LQEiv5BfuyMTfY2xwFTlGWjdYF6Xnac+Gz6+E1gjCBSuqKWd56QFDcrqCcvbG50ERhenCUEhUgI33rXNxS/hwz2iClVM26GFJlUtZkZ7/021FjEQNLEYivI1ZkHe9ip8sxx2QVKd5k7Ui/hXkP4xSal3okL4Ar/cThMh0aoWzziYsoAahtoxFWEags7lNOffMoLGmPbgGVRUGHogNu2CES0SNs4AhHVUVERo8OTJHx46XJxXqvLzeEn6zXFD63XXQR8AyXTQ=",
"hJ/FhIMQY9G/BqOvGJ0nhuJk/7Y/q/unCpeC7LxGvcYjXzsY/kcInwUzgfjRqNI0oI5ggOAdxIiaGvBRqWCshjxcx6iRyJZBMKwFchM1baHntRl22yxvBA16gpMxbWTihQ0Jtz9m/Dj8clYgyEkMOKFEOya5YiyyqjK+YOl5xWiM1pGAomFixqr7VBLk/NE7e+qYXuFjDl3IZ1OP+tgdgyjkqUZE3OBGQfXP6wvTFjgk/maCPUmzffnbw/yH4JZIa3N/vhK+jzowcV+ymZK/9+10ge8hIZ5LZPdU90xLWd2zPYFjju/vr9TFWUG4YkV8l1vSsG7P8MYjMKFh/YQoTyyjJUZw9YGfnRENZrdegttUxefJywpBgp0xVt5vyAMM9XjTEtwK/A5gef0fD52lKWpwD6xamPBnhb8yRPUnncapGBrQvFhB8uQnkS8BKqtiJyz8XJHg8quzUXjYd+Ywn5p+lzP5zsDXQDYOfcm4FZGYp3oFwJZz1s4YOjP8Abjd+4v0sQIcP+Kj8lBzrADQSvTIJaQZO/emnpkZYoW/ggSJLa+BrRUXK5gNHdZzUI/EDOJggrGn3ezSLd96DyDlosG+lGM/EpuiB504YNzQfrm1scVbk9R4YHNhy+sau04DJcRY0p52qeaVDswMkfjUA6JBvBFT9SqQeB7q9qRwLrk="
]
werewolf-skins:
- ==: fr.azur.tcoww.utils.skins.WerewolfSkin
value: "ewogICJ0aW1lc3RhbXAiIDogMTYxNzQ2NzI1ODQxMCwKICAicHJvZmlsZUlkIiA6ICJjMGYzYjI3YTUwMDE0YzVhYjIxZDc5ZGRlMTAxZGZlMiIsCiAgInByb2ZpbGVOYW1lIiA6ICJDVUNGTDEzIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2Y5NmM4OWUwZWYwZmVlZjZkODc4ZDUzYzQ4OWIyMzliNDdlNmViODQyZDJmM2MzZTlhZDdkMjliZmYxYTM0OWUiCiAgICB9CiAgfQp9"
signature: "uWIu/2R3zImj87H7x//O/04eJ1SwsHvLSxaj1YQuI1vBndUVHhN42Ja7AOJoAieVO8yN1qn/rJj1oK5z5koQ2fZwjNCc0mKTrakCMWGqjaAImDw8RWriUQ3ClPCw6NtCmB1qVUp60kUEpGha/btUSsAEUnVZ0ghM4SXTcS2izbDe/dRfbi5EDr2Uue3sz6+med3hjecZzrKJUX19wP1WwPifEOH2X/smzzGOdqlQtylMZQh3nNeho/GBwbDVXsXsR6LcDgoInDnw63cDV7n4dTs6giGmcx0knhDWH24Z/vrgNSqzbNY9hIgVBNWUqs/1o7/xrbK1HilYBPAiVO0F6+vJOpzdFrpEZVr0M++u+X2Zkt1sfshKZqt0O07KbH8h4xj3J+6iDF5RHHFGmZy0awFGZGIi8wqlffwQfZ+l7nPU1hpMD5DkArxBWJWgnquyCNpQ44OlmTpPRa/gopRpuMwJ1r6vDPhgIavJX6cKQ2lKTYKG2/HB+ZEl/IUtuXxKO7bFe4KOvjJpMwBjOmzqbn2sk3GqbSg8NfNMFA4FjRBvHXOaMsDCqz0MJxLFKvIfYRVWG8XAU6BU82RoS3qGFSOhI6N/DvD7dx1V40q52iWv2j4/S41P5zrtMqQm7wofocptz6sS7GLE1u9rPCBLRIck0wzi3OF7MV2p4wlw6lw="
- ==: fr.azur.tcoww.utils.skins.WerewolfSkin
value: "ewogICJ0aW1lc3RhbXAiIDogMTYxNTYxMTkwOTY4MCwKICAicHJvZmlsZUlkIiA6ICIwMGM2Yjk0YTY5YmU0MzY3OTkwOTQxNjFjMjAxOWI3ZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJLQUVWRVJZIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2I0MGRjNTcxZGUwZGQ4ZmQ5NzBjNzNmOTE2NGJjMTQ0Njk1MWFlNDhkNWFiZTQyMzI5MGVlYjFhODc4NDU1ZmUiCiAgICB9CiAgfQp9"
signature: "kVGwx98YPc6tIZ306TiGI5eoK4v7oEjIvCaOPS03jq4LjBWV12NBTWW67u+HrI2/2m3r8pvAyx1Ddpw4YWyaDbaG0FvLuurWbETqtwvyvVx7R3BZHgw2EnVOYk4+WPmA8xUgzoIbZF/Ch4bSqP7UlZy7/fz4AQwFK/qLMzApOAeV4Kzn4p6+MK2uOkfSjGgwxqAO/+TZj6u01mk1NKNh4ors9RGDG3DBHZKSxTG/PzY5ErKABRXkB/RBMA5IvU5Rzn5cH+Sfkus/7LtYp5u+5i+HcpN/klyBBw4D5di3WzFOAIX78ZiyKzKzWpSN0z6Cb2DZdo1ufeZH9mw8nv+A8VfpGXwphwde+oGBP4l4OffE7YvbnWyVHmlG0L36iI2/FkWQl91GIT15YBHrjeQ9pmvbzCQUMyAHi2jZePTTcT1rDnSzBk7fc9IwfcBRNyqbveTNu9zikfx0W6GnXAbV4qOTIIVEHlpxQ9KW017sqmgiF5l/3ZVwqbEQzglNehYHA55vRfKY0HKbGB54k1oTnM3X7mNXIu6fk8dVgCvT8lVdDm3LWcE6aWF8Yp6QrXNvTNW1yxSRAhigsHrvbajp2Odg18lMTng0oh/4JiYOQAgb6tFdRtQ4nrSVHRBOEvY09IRFzVTTd7n2JaYZ6QcCMOiKOX25fv22rbjSeAZ/Whk="
- ==: fr.azur.tcoww.utils.skins.WerewolfSkin
value: "ewogICJ0aW1lc3RhbXAiIDogMTc0Nzk4MDc5ODQyMywKICAicHJvZmlsZUlkIiA6ICI2NWI5NTU1YWQyMGU0NWM5YjFkNmU3MjQwNjU0NTBkNCIsCiAgInByb2ZpbGVOYW1lIiA6ICJKYXZhUHJvZmlsZU5hbWVYIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzJlZTlmNjcxYzFmNjY4Y2IwYWUwNGE5YzhiZWM0OTVlYTVmNzNmNTNmYmY3ZjM5MmNkYjdlMTc1ZmVjZDFjYmUiCiAgICB9CiAgfQp9"
signature: "wGc2rNbc7+nziEDMKFoBIltHZwx5PQfsfMGyRTfTUdtVi3O4iu0idcpTsTVJ80njIFZvmLpzlwjPu6ULrXJwgQQyGCNdp60jQMl9vGjzgJzs6uqhSCke3EvjOCuAGkv2ScRY5hDGQOnIruxwmgYgDeXHnujt+zTBvW+imxLVaZhXoKVPyqlvm6njWp0XECyAi8DFKbzIdDVp+NsRzIGjKeVoMgWd4xlNazUZGgTsXNCD/+aZOJ1LxA51I8Wgsm888MUXnaGhNI3U4jJNKHAPZMS4++JEuwWzlptJdZIa1jOfwMD6HvIPKEmC5+eDCRFvPIEh84VqErtNTPEUAz7wZAJvO+j+x5A/9VnQmKJhEBxBjlw1Ky+mQcLJnRX/x9jc4EvEd9gWGIUj46y3IF8jWn71eXvinSJ4Op4LQEiv5BfuyMTfY2xwFTlGWjdYF6Xnac+Gz6+E1gjCBSuqKWd56QFDcrqCcvbG50ERhenCUEhUgI33rXNxS/hwz2iClVM26GFJlUtZkZ7/021FjEQNLEYivI1ZkHe9ip8sxx2QVKd5k7Ui/hXkP4xSal3okL4Ar/cThMh0aoWzziYsoAahtoxFWEags7lNOffMoLGmPbgGVRUGHogNu2CES0SNs4AhHVUVERo8OTJHx46XJxXqvLzeEn6zXFD63XXQR8AyXTQ="
- ==: fr.azur.tcoww.utils.skins.WerewolfSkin
value: "ewogICJ0aW1lc3RhbXAiIDogMTY0Mjc4ODk3NDg1NSwKICAicHJvZmlsZUlkIiA6ICIzYzE0YmVkNDFiOGE0MDIzOGM3MDgzMTA1NzEwMTZmYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJOb2Jpa28iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWZlNzIzMDdjZTEyNmI1Y2I0ZDQ0ZGFlZTcwZDI1ZmQwNDMwOGJjN2M0MzI4OWM3ZDk3MDY4NDY5NWI2MGJhIgogICAgfQogIH0KfQ=="
signature: "hJ/FhIMQY9G/BqOvGJ0nhuJk/7Y/q/unCpeC7LxGvcYjXzsY/kcInwUzgfjRqNI0oI5ggOAdxIiaGvBRqWCshjxcx6iRyJZBMKwFchM1baHntRl22yxvBA16gpMxbWTihQ0Jtz9m/Dj8clYgyEkMOKFEOya5YiyyqjK+YOl5xWiM1pGAomFixqr7VBLk/NE7e+qYXuFjDl3IZ1OP+tgdgyjkqUZE3OBGQfXP6wvTFjgk/maCPUmzffnbw/yH4JZIa3N/vhK+jzowcV+ymZK/9+10ge8hIZ5LZPdU90xLWd2zPYFjju/vr9TFWUG4YkV8l1vSsG7P8MYjMKFh/YQoTyyjJUZw9YGfnRENZrdegttUxefJywpBgp0xVt5vyAMM9XjTEtwK/A5gef0fD52lKWpwD6xamPBnhb8yRPUnncapGBrQvFhB8uQnkS8BKqtiJyz8XJHg8quzUXjYd+Ywn5p+lzP5zsDXQDYOfcm4FZGYp3oFwJZz1s4YOjP8Abjd+4v0sQIcP+Kj8lBzrADQSvTIJaQZO/emnpkZYoW/ggSJLa+BrRUXK5gNHdZzUI/EDOJggrGn3ezSLd96DyDlosG+lGM/EpuiB504YNzQfrm1scVbk9R4YHNhy+sau04DJcRY0p52qeaVDswMkfjUA6JBvBFT9SqQeB7q9qRwLrk="
# The time in second before teleport no sleeping player
playerSleep: 20
player-sleep: 30

View File

@@ -1,5 +1,5 @@
name: tcoww
version: '1.0-SNAPSHOT'
version: '1.2.0'
main: fr.azur.tcoww.Tcoww
api-version: '1.21'
load: POSTWORLD
@@ -9,8 +9,9 @@ dependencies:
server:
SkinsRestorer:
load: BEFORE
required: true
required: false
join-classpath: true
voicechat:
load: BEFORE
required: true
required: false
join-classpath: true