🖍️Main commit

This commit is contained in:
2025-06-28 14:53:52 +02:00
commit 9ab31b2fe1
34 changed files with 2115 additions and 0 deletions

120
.gitignore vendored Normal file
View File

@@ -0,0 +1,120 @@
# User-specific stuff
.idea/
*.iml
*.ipr
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
.kotlin/
.gradle
build/
# Ignore Gradle GUI config
gradle-app.setting
# Cache of project
.gradletasknamecache
**/build/
# Common working directory
run/
runs/
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
# TCOWW
TCOWW is a small paper plugin created for an event on the French Discord server, [The Court of Gaming](https://discord.gg/evCYTnRrpU).
The event is a simple game of "Loup-Garou de Thiercelieux" in Minecraft with a role-play dimension.
Write in kotlin with the paper 1.21.4 api.

69
build.gradle.kts Normal file
View File

@@ -0,0 +1,69 @@
plugins {
kotlin("jvm") version "2.2.0-Beta1"
id("com.gradleup.shadow") version "8.3.0"
id("xyz.jpenilla.run-paper") version "2.3.1"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.16"
}
group = "fr.azur"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
gradlePluginPortal()
maven("https://repo.papermc.io/repository/maven-public/") {
name = "papermc-repo"
}
maven("https://oss.sonatype.org/content/groups/public/") {
name = "sonatype"
}
maven("https://repo.codemc.org/repository/maven-public/") {
name = "codemc"
}
maven("https://repo.plasmoverse.com/releases")
maven("https://repo.plasmoverse.com/snapshots")
}
dependencies {
paperweight.paperDevBundle("1.21.4-R0.1-SNAPSHOT")
compileOnly("net.skinsrestorer:skinsrestorer-api:15.6.3")
compileOnly("su.plo.voice.api:server:2.1.4")
implementation(kotlin("stdlib-jdk8"))
}
val targetJavaVersion = 21
kotlin {
jvmToolchain(targetJavaVersion)
}
tasks {
runServer {
downloadPlugins {
modrinth("plasmo-voice", "spigot-2.1.4")
modrinth("skinsrestorer", "15.6.3")
}
minecraftVersion("1.21.4")
}
build {
dependsOn("shadowJar")
}
processResources {
val props = mapOf("version" to version)
inputs.properties(props)
filteringCharset = "UTF-8"
filesMatching("paper-plugin.yml") {
expand(props)
}
}
shadowJar {
archiveBaseName.set("tcoww")
minimize()
// exclude("META-INF/*.kotlin_module")
// exclude("META-INF/*.version")
}
}

0
gradle.properties Normal file
View File

View File

@@ -0,0 +1 @@
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip

1
settings.gradle.kts Normal file
View File

@@ -0,0 +1 @@
rootProject.name = "Werewolf Role Play"

View File

@@ -0,0 +1,51 @@
package fr.azur.tcoww
import fr.azur.tcoww.events.GameEvent
import fr.azur.tcoww.events.ToolsEvents
import fr.azur.tcoww.plasmovoice.addon.PlasmoAddon
import fr.azur.tcoww.roles.*
import fr.azur.tcoww.utils.Skin
import net.skinsrestorer.api.SkinsRestorer
import net.skinsrestorer.api.SkinsRestorerProvider
import org.bukkit.plugin.java.JavaPlugin
import su.plo.voice.api.server.PlasmoVoiceServer
class Tcoww : JavaPlugin() {
private lateinit var skinsRestorer: SkinsRestorer
private lateinit var skinManager: Skin
val plasmoAddon = PlasmoAddon()
override fun onEnable() {
saveResource("config.yml", false)
saveDefaultConfig()
PlasmoVoiceServer.getAddonsLoader().load(plasmoAddon)
skinsRestorer = SkinsRestorerProvider.get()
skinManager = Skin(this, skinsRestorer)
server.pluginManager.registerEvents(ToolsEvents(this), this)
server.pluginManager.registerEvents(GameEvent(this, skinManager), this)
registerRoles()
}
override fun onDisable() {
}
fun reload(withPlasmoAddon: Boolean) {
this.reloadConfig()
skinManager.reloadSkin()
if (withPlasmoAddon) plasmoAddon.onConfigLoaded()
}
private fun registerRoles() {
Role.registerRole(Villager())
Role.registerRole(Werewolf())
Role.registerRole(Child())
Role.registerRole(FortuneTeller())
Role.registerRole(Hunter())
Role.registerRole(Witch())
}
}

View File

@@ -0,0 +1,24 @@
package fr.azur.tcoww
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 {
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())
}
}
}

View File

@@ -0,0 +1,67 @@
package fr.azur.tcoww.commands
import com.mojang.brigadier.Command
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.builder.LiteralArgumentBuilder
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 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.Bukkit
import org.bukkit.NamespacedKey
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
}
)
)
).then(
Commands.literal("stop").executes { ctx ->
Game.current?.stopGame(true)
Command.SINGLE_SUCCESS
}
)
}

View File

@@ -0,0 +1,156 @@
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

@@ -0,0 +1,18 @@
package fr.azur.tcoww.commands
import com.mojang.brigadier.Command
import com.mojang.brigadier.builder.LiteralArgumentBuilder
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
}
}

View File

@@ -0,0 +1,44 @@
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

@@ -0,0 +1,146 @@
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 fr.azur.tcoww.Tcoww
import fr.azur.tcoww.items.CustomItems
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.event.ClickEvent
import net.kyori.adventure.text.format.NamedTextColor
import net.kyori.adventure.text.format.TextDecoration
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()
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 { ctx ->
val plugin = Bukkit.getPluginManager().getPlugin("tcoww") as Tcoww
plugin.reload(true)
Command.SINGLE_SUCCESS
}
).then(
Commands.literal("customitem").then(
Commands.argument("item", StringArgumentType.greedyString())
.suggests { ctx, 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
}
)
)
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
)
}"
)
)
.color(NamedTextColor.LIGHT_PURPLE)
.decorate(TextDecoration.UNDERLINED)
)
.appendNewline()
}
return Component.text("Current bed locations :")
.color(NamedTextColor.DARK_PURPLE)
.appendNewline()
.append(bedLocationComponent)
}
}

View File

@@ -0,0 +1,32 @@
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.game.Phase
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 org.bukkit.entity.Player
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()
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
}
Command.SINGLE_SUCCESS
}
)
}

View File

@@ -0,0 +1,432 @@
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.addPotionEffect(PotionEffect(PotionEffectType.WITHER, 1200, 10))
target.addPotionEffect(PotionEffect(PotionEffectType.POISON, 1200, 10))
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
)
)
event.player.addPotionEffect(
PotionEffect(
PotionEffectType.SATURATION,
PotionEffect.INFINITE_DURATION,
1,
false,
false
)
)
}
@EventHandler(
priority = EventPriority.MONITOR,
ignoreCancelled = true
)
fun endSleep(event: PlayerBedLeaveEvent) {
event.player.removePotionEffect(PotionEffectType.SATURATION)
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.1,
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,20 @@
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

@@ -0,0 +1,82 @@
package fr.azur.tcoww.events
import fr.azur.tcoww.utils.BedGestion
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.NamespacedKey
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 {
@EventHandler
fun clickEvent(event: PlayerInteractEvent) {
val tool = event.player.inventory.itemInMainHand
if (!tool.persistentDataContainer.has(NamespacedKey("tcoww", "tools"))) return
when (tool.persistentDataContainer.get(NamespacedKey("tcoww", "tools"), 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 block = BedGestion.getHeadBed(interactloc.block)
if (!list.contains(block.location.serialize())) {
list.add(block.location.serialize())
plugin.config.set("bedLocation", list)
plugin.saveConfig()
event.player.sendMessage(
Component.text("Bed location add.").color(NamedTextColor.LIGHT_PURPLE)
)
} else {
event.player.sendMessage(
Component.text("Bed location already exists.").color(NamedTextColor.DARK_RED)
)
}
} else {
event.player.sendMessage(
Component.text("It's not a bed...").color(NamedTextColor.DARK_RED)
)
}
}
}
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))
}
}
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))
}
}
}
}
// @EventHandler
// fun reloadAfterWorld(worldInitEvent: WorldInitEvent) {
//
// }
}

View File

@@ -0,0 +1,129 @@
package fr.azur.tcoww.game
import fr.azur.tcoww.roles.Role
import fr.azur.tcoww.roles.Villager
import fr.azur.tcoww.roles.Werewolf
import net.kyori.adventure.text.Component
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.persistence.PersistentDataType
import org.bukkit.plugin.Plugin
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
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.inventory.clear()
player.health = 20.0
}
assignRoles()
timeGestion = TimeGestion(plugin, world)
timeGestShedule = Bukkit.getScheduler().runTaskTimer(plugin, Runnable {
timeGestion.tick()
}, 0L, 1L)
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())
}
}
companion object {
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 stopGame(withoutResult: Boolean = false) {
current ?: return
current = null
timeGestShedule.cancel()
world.time = 18000
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
}
}, 200)
}
}

View File

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

View File

@@ -0,0 +1,81 @@
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

@@ -0,0 +1,226 @@
package fr.azur.tcoww.items
import fr.azur.tcoww.game.Game
import fr.azur.tcoww.roles.Role
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
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.CrossbowMeta
import org.bukkit.inventory.meta.Damageable
import org.bukkit.inventory.meta.PotionMeta
import org.bukkit.persistence.PersistentDataType
import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType
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)
)
)
}
}
}),
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)
}
}
}
}),
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)
}
}
}),
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)))
}
}
}
),
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)
)
)
}
}
}),
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)))
}
}
}),
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)))
}
}
}),
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)))
}
}
}),
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)))
}
}
}),
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 players = Game.current?.playersMutable?.shuffled().orEmpty()
val (villagers, werewolves) = players.partition {
Role.getRole(it)?.lg == false
}
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)
}
}
});
private var cachedItem: ItemStack? = null
val item: ItemStack
get() {
return if (cache) {
cachedItem ?: supplier().also { cachedItem = it }
} else {
supplier()
}
}
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

@@ -0,0 +1,37 @@
package fr.azur.tcoww.plasmovoice.addon
import su.plo.voice.api.addon.AddonInitializer
import su.plo.voice.api.addon.InjectPlasmoVoice
import su.plo.voice.api.addon.annotation.Addon
import su.plo.voice.api.event.EventSubscribe
import su.plo.voice.api.server.PlasmoVoiceServer
import su.plo.voice.api.server.event.player.PlayerInfoCreateEvent
@Addon(
id = "tcoww",
name = "The court of werewolf",
version = "1.0.0",
authors = ["Azur"]
)
class PlasmoAddon : AddonInitializer {
@InjectPlasmoVoice
private lateinit var voiceServer: PlasmoVoiceServer
override fun onAddonInitialize() {
onConfigLoaded()
}
override fun onAddonShutdown() {
}
fun onConfigLoaded() {
}
@EventSubscribe
fun onPlayerInfoCreate(event: PlayerInfoCreateEvent) {
event.voicePlayerInfo.playerNick = "..."
}
}

View File

@@ -0,0 +1,22 @@
package fr.azur.tcoww.roles
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
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")
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)
)
}

View File

@@ -0,0 +1,25 @@
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.NamespacedKey
import org.bukkit.entity.Player
class FortuneTeller : Role {
override fun handle(player: Player) {
player.inventory.addItem(CustomItems.CrystalBall.item)
}
override fun onDeath(player: Player) {}
override var lg = false
override var id = NamespacedKey("tcoww", "fortune_teller")
override val displayName = Component.text("Voyante")
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)
)
}

View File

@@ -0,0 +1,34 @@
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.NamespacedKey
import org.bukkit.entity.Player
class Hunter : Role {
override fun handle(player: Player) {
player.inventory.addItem(CustomItems.BlasTechDL44.item)
}
override fun onDeath(player: Player) {
TODO("Not yet implemented")
}
override var lg = false
override var id = NamespacedKey("tcoww", "hunter")
override val displayName = Component.text("Chaseur")
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)
)
)
}

View File

@@ -0,0 +1,49 @@
package fr.azur.tcoww.roles
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
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
val id: NamespacedKey
val displayName: Component
val hasPowerCommand: Boolean
val description: Component
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()
)
player.sendMessage(
Component.text("Votre role est ").append(role.displayName).color(NamedTextColor.DARK_AQUA)
)
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)
})
role.handle(player)
}
fun getRole(player: Player): Role? {
val rolestr = player.persistentDataContainer.get(rolekey, PersistentDataType.STRING)
if (rolestr == null) return null
return registerRoles[NamespacedKey.fromString(rolestr)]
}
fun registerRole(role: Role) {
registerRoles.put(role.id, role)
}
}
}

View File

@@ -0,0 +1,26 @@
package fr.azur.tcoww.roles
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
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
override var id = NamespacedKey("tcoww", "villager")
override val displayName = Component.text("Villageois")
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)
)
}

View File

@@ -0,0 +1,25 @@
package fr.azur.tcoww.roles
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
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
override var id = NamespacedKey("tcoww", "werewolf")
override val displayName = Component.text("Loup-Garou")
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
)
)
}

View File

@@ -0,0 +1,29 @@
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.NamespacedKey
import org.bukkit.entity.Player
class Witch : Role {
override fun handle(player: Player) {
player.inventory.addItem(CustomItems.DeathPotion.item)
player.inventory.addItem(CustomItems.HealPotion.item)
}
override fun onDeath(player: Player) {}
override var lg = false
override var id = NamespacedKey("tcoww", "witch")
override val displayName = Component.text("Sorcière")
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()
}

View File

@@ -0,0 +1,24 @@
package fr.azur.tcoww.utils
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 getHeadBed(block: Block): Block {
val bedData = block.blockData as Bed
return if (bedData.part == Bed.Part.FOOT) {
block.getRelative(bedData.facing)
} else {
block
}
}
fun isOcupied(block: Block): Boolean {
val bedData = block.blockData as Bed
return bedData.isOccupied
}
}

View File

@@ -0,0 +1,65 @@
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

@@ -0,0 +1,13 @@
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,38 @@
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
duration: # In seconds
day: 600
vote: 120
night: 300
crepuscular: 20
wwcount: 10
# The skin value
wwskinsvalue: [
"ewogICJ0aW1lc3RhbXAiIDogMTYxNzQ2NzI1ODQxMCwKICAicHJvZmlsZUlkIiA6ICJjMGYzYjI3YTUwMDE0YzVhYjIxZDc5ZGRlMTAxZGZlMiIsCiAgInByb2ZpbGVOYW1lIiA6ICJDVUNGTDEzIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2Y5NmM4OWUwZWYwZmVlZjZkODc4ZDUzYzQ4OWIyMzliNDdlNmViODQyZDJmM2MzZTlhZDdkMjliZmYxYTM0OWUiCiAgICB9CiAgfQp9",
"ewogICJ0aW1lc3RhbXAiIDogMTYxNTYxMTkwOTY4MCwKICAicHJvZmlsZUlkIiA6ICIwMGM2Yjk0YTY5YmU0MzY3OTkwOTQxNjFjMjAxOWI3ZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJLQUVWRVJZIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2I0MGRjNTcxZGUwZGQ4ZmQ5NzBjNzNmOTE2NGJjMTQ0Njk1MWFlNDhkNWFiZTQyMzI5MGVlYjFhODc4NDU1ZmUiCiAgICB9CiAgfQp9",
"ewogICJ0aW1lc3RhbXAiIDogMTc0Nzk4MDc5ODQyMywKICAicHJvZmlsZUlkIiA6ICI2NWI5NTU1YWQyMGU0NWM5YjFkNmU3MjQwNjU0NTBkNCIsCiAgInByb2ZpbGVOYW1lIiA6ICJKYXZhUHJvZmlsZU5hbWVYIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzJlZTlmNjcxYzFmNjY4Y2IwYWUwNGE5YzhiZWM0OTVlYTVmNzNmNTNmYmY3ZjM5MmNkYjdlMTc1ZmVjZDFjYmUiCiAgICB9CiAgfQp9",
"ewogICJ0aW1lc3RhbXAiIDogMTY0Mjc4ODk3NDg1NSwKICAicHJvZmlsZUlkIiA6ICIzYzE0YmVkNDFiOGE0MDIzOGM3MDgzMTA1NzEwMTZmYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJOb2Jpa28iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWZlNzIzMDdjZTEyNmI1Y2I0ZDQ0ZGFlZTcwZDI1ZmQwNDMwOGJjN2M0MzI4OWM3ZDk3MDY4NDY5NWI2MGJhIgogICAgfQogIH0KfQ=="
]
# The skin signature (see mineskin.org)
wwskinssignature: [
"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"
]
# The time in second before teleport no sleeping player
playerSleep: 20

View File

@@ -0,0 +1,16 @@
name: tcoww
version: '1.0-SNAPSHOT'
main: fr.azur.tcoww.Tcoww
api-version: '1.21'
load: POSTWORLD
authors: [ Azur84 ]
bootstrapper: fr.azur.tcoww.TcowwBootstrap
dependencies:
server:
SkinsRestorer:
load: BEFORE
required: true
join-classpath: true
PlasmoVoice:
load: BEFORE
required: true