From 31a8ea6192da8b29227e56792e112c3e0a97bad3 Mon Sep 17 00:00:00 2001 From: Mew2K Date: Fri, 22 May 2026 13:31:28 -0700 Subject: [PATCH 1/5] Add warden spawn mutation Co-authored-by: Codex --- .../warzone/uranus/UranusCommandGraph.kt | 4 +- .../network/warzone/uranus/UranusPlugin.kt | 15 +- .../warzone/uranus/mutations/Mutation.kt | 25 +++ .../uranus/mutations/MutationManager.kt | 25 +++ .../mutations/commands/MutationCommand.kt | 64 ++++++++ .../mutations/warden/TeamParticipantState.kt | 46 ++++++ .../mutations/warden/WardenSpawnMutation.kt | 149 ++++++++++++++++++ src/main/resources/paper-plugin.yml | 6 +- 8 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 src/main/kotlin/network/warzone/uranus/mutations/Mutation.kt create mode 100644 src/main/kotlin/network/warzone/uranus/mutations/MutationManager.kt create mode 100644 src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt create mode 100644 src/main/kotlin/network/warzone/uranus/mutations/warden/TeamParticipantState.kt create mode 100644 src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt diff --git a/src/main/kotlin/network/warzone/uranus/UranusCommandGraph.kt b/src/main/kotlin/network/warzone/uranus/UranusCommandGraph.kt index 1d39d8a..7f19939 100644 --- a/src/main/kotlin/network/warzone/uranus/UranusCommandGraph.kt +++ b/src/main/kotlin/network/warzone/uranus/UranusCommandGraph.kt @@ -28,7 +28,7 @@ class UranusCommandGraph(plugin: UranusPlugin) : CommandGraph(plug override fun registerCommands() { // Default commands - // TBD + register(plugin.mutationManager.command) } public override fun register(command: Any) { @@ -40,4 +40,4 @@ class UranusCommandGraph(plugin: UranusPlugin) : CommandGraph(plug } -} \ No newline at end of file +} diff --git a/src/main/kotlin/network/warzone/uranus/UranusPlugin.kt b/src/main/kotlin/network/warzone/uranus/UranusPlugin.kt index f7ab051..6f881da 100644 --- a/src/main/kotlin/network/warzone/uranus/UranusPlugin.kt +++ b/src/main/kotlin/network/warzone/uranus/UranusPlugin.kt @@ -1,10 +1,12 @@ package network.warzone.uranus import de.maxhenkel.voicechat.api.BukkitVoicechatService +import network.warzone.uranus.mutations.MutationManager import network.warzone.uranus.voice.UranusVoicePlugin import org.bukkit.Bukkit import org.bukkit.event.Listener import org.bukkit.plugin.java.JavaPlugin +import tc.oc.pgm.api.PGM class UranusPlugin : JavaPlugin() { @@ -14,20 +16,31 @@ class UranusPlugin : JavaPlugin() { } lateinit var commandGraph: UranusCommandGraph + lateinit var mutationManager: MutationManager override fun onEnable() { + instance = this + this.loadMutations() this.loadCommandManager() this.loadVoiceChatIntegration() } override fun onDisable() { - // Plugin shutdown logic + if (::mutationManager.isInitialized) { + runCatching { + PGM.get().matchManager.matches.forEachRemaining(mutationManager::disableAll) + } + } } fun loadCommandManager() { this.commandGraph = UranusCommandGraph(this) } + fun loadMutations() { + this.mutationManager = MutationManager(this) + } + fun loadVoiceChatIntegration() { if (server.pluginManager.isPluginEnabled("voicechat")) { val service = server.servicesManager.load(BukkitVoicechatService::class.java) diff --git a/src/main/kotlin/network/warzone/uranus/mutations/Mutation.kt b/src/main/kotlin/network/warzone/uranus/mutations/Mutation.kt new file mode 100644 index 0000000..c41b269 --- /dev/null +++ b/src/main/kotlin/network/warzone/uranus/mutations/Mutation.kt @@ -0,0 +1,25 @@ +package network.warzone.uranus.mutations + +import org.bukkit.command.CommandSender +import tc.oc.pgm.api.match.Match + +interface Mutation { + + val id: String + val name: String + + fun isAvailable(sender: CommandSender): Boolean = true + + fun isEnabled(match: Match): Boolean + + fun enable(match: Match): MutationResult + + fun disable(match: Match): MutationResult + +} + +data class MutationResult( + val success: Boolean, + val message: String +) + diff --git a/src/main/kotlin/network/warzone/uranus/mutations/MutationManager.kt b/src/main/kotlin/network/warzone/uranus/mutations/MutationManager.kt new file mode 100644 index 0000000..91c4333 --- /dev/null +++ b/src/main/kotlin/network/warzone/uranus/mutations/MutationManager.kt @@ -0,0 +1,25 @@ +package network.warzone.uranus.mutations + +import network.warzone.uranus.UranusPlugin +import network.warzone.uranus.mutations.commands.MutationCommand +import network.warzone.uranus.mutations.warden.WardenSpawnMutation +import tc.oc.pgm.api.match.Match + +class MutationManager(plugin: UranusPlugin) { + + private val mutations = listOf( + WardenSpawnMutation() + ).associateBy { it.id } + + val command = MutationCommand(this) + + fun get(id: String): Mutation? = mutations[id.lowercase()] + + fun disableAll(match: Match) { + mutations.values + .filter { it.isEnabled(match) } + .forEach { it.disable(match) } + } + +} + diff --git a/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt b/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt new file mode 100644 index 0000000..bf48d97 --- /dev/null +++ b/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt @@ -0,0 +1,64 @@ +package network.warzone.uranus.mutations.commands + +import net.kyori.adventure.text.Component.text +import net.kyori.adventure.text.format.NamedTextColor +import network.warzone.uranus.mutations.MutationManager +import org.bukkit.command.CommandSender +import tc.oc.pgm.api.PGM +import tc.oc.pgm.api.match.Match +import tc.oc.pgm.lib.org.incendo.cloud.annotations.Argument +import tc.oc.pgm.lib.org.incendo.cloud.annotations.Command +import tc.oc.pgm.lib.org.incendo.cloud.annotations.CommandDescription +import tc.oc.pgm.lib.org.incendo.cloud.annotations.Permission + +class MutationCommand(private val mutations: MutationManager) { + + @Command("mutation warden") + @CommandDescription("Toggle the Warden spawn mutation") + @Permission("uranus.mutation.warden") + fun toggleWarden(sender: CommandSender) { + handleWarden(sender, null) + } + + @Command("mutation warden ") + @CommandDescription("Enable or disable the Warden spawn mutation") + @Permission("uranus.mutation.warden") + fun setWarden(sender: CommandSender, @Argument("state") state: String) { + val enabled = when (state.lowercase()) { + "on", "enable", "enabled", "true", "yes" -> true + "off", "disable", "disabled", "false", "no" -> false + else -> { + sender.sendMessage(text("Use on or off.", NamedTextColor.RED)) + return + } + } + handleWarden(sender, enabled) + } + + private fun handleWarden(sender: CommandSender, enabled: Boolean?) { + val mutation = mutations.get("warden") ?: return + val match = getMatch(sender) ?: run { + sender.sendMessage(text("No active PGM match was found.", NamedTextColor.RED)) + return + } + + if (!mutation.isAvailable(sender)) { + sender.sendMessage(text("${mutation.name} is not available on this server version.", NamedTextColor.RED)) + return + } + + val shouldEnable = enabled ?: !mutation.isEnabled(match) + val result = if (shouldEnable) { + mutation.enable(match) + } else { + mutation.disable(match) + } + + sender.sendMessage(text(result.message, if (result.success) NamedTextColor.GREEN else NamedTextColor.RED)) + } + + private fun getMatch(sender: CommandSender): Match? { + return PGM.get().matchManager.getMatch(sender) + } + +} diff --git a/src/main/kotlin/network/warzone/uranus/mutations/warden/TeamParticipantState.kt b/src/main/kotlin/network/warzone/uranus/mutations/warden/TeamParticipantState.kt new file mode 100644 index 0000000..f26acf1 --- /dev/null +++ b/src/main/kotlin/network/warzone/uranus/mutations/warden/TeamParticipantState.kt @@ -0,0 +1,46 @@ +package network.warzone.uranus.mutations.warden + +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.text.Component +import org.bukkit.Location +import tc.oc.pgm.api.match.Match +import tc.oc.pgm.api.party.Competitor +import tc.oc.pgm.api.player.MatchPlayer +import tc.oc.pgm.api.player.ParticipantState +import tc.oc.pgm.util.named.NameStyle +import java.util.Optional +import java.util.UUID + +class TeamParticipantState( + private val competitor: Competitor, + private val location: Location +) : ParticipantState { + + private val id = UUID.nameUUIDFromBytes("uranus:${competitor.match.id}:${competitor.id}:warden".toByteArray()) + + override fun getMatch(): Match = competitor.match + + override fun getParty(): Competitor = competitor + + override fun getId(): UUID = id + + override fun getLocation(): Location = location.clone() + + override fun isDead(): Boolean = false + + override fun isVanished(): Boolean = false + + override fun getNick(): String? = null + + override fun getPlayer(): Optional = Optional.empty() + + override fun canInteract(): Boolean = competitor.isParticipating + + override fun getName(style: NameStyle): Component = competitor.getName(style) + + override fun getNameLegacy(): String = "${competitor.nameLegacy} Warden" + + override fun audience(): Audience = Audience.empty() + +} + diff --git a/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt b/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt new file mode 100644 index 0000000..c307b4b --- /dev/null +++ b/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt @@ -0,0 +1,149 @@ +package network.warzone.uranus.mutations.warden + +import network.warzone.uranus.mutations.Mutation +import network.warzone.uranus.mutations.MutationResult +import org.bukkit.Location +import org.bukkit.command.CommandSender +import org.bukkit.entity.EntityType +import org.bukkit.entity.LivingEntity +import tc.oc.pgm.api.match.Match +import tc.oc.pgm.api.party.Competitor +import tc.oc.pgm.api.player.MatchPlayer +import tc.oc.pgm.spawns.Spawn +import tc.oc.pgm.spawns.SpawnMatchModule +import tc.oc.pgm.teams.Team +import tc.oc.pgm.tracker.TrackerMatchModule +import tc.oc.pgm.tracker.info.MobInfo +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Proxy +import java.util.Optional +import java.util.UUID + +class WardenSpawnMutation : Mutation { + + override val id: String = "warden" + override val name: String = "Warden Spawn" + + private val wardensByMatch = mutableMapOf>() + + override fun isAvailable(sender: CommandSender): Boolean = wardenType() != null + + override fun isEnabled(match: Match): Boolean { + val wardenIds = wardensByMatch[match.id] ?: return false + wardenIds.removeIf { match.world.getEntity(it) == null } + return wardenIds.isNotEmpty() + } + + override fun enable(match: Match): MutationResult { + val wardenType = wardenType() + ?: return MutationResult(false, "$name is only available on servers that support Wardens.") + + if (isEnabled(match)) { + return MutationResult(false, "$name is already enabled.") + } + + val spawns = match.getModule(SpawnMatchModule::class.java) + ?: return MutationResult(false, "This map does not expose PGM team spawns.") + val tracker = match.getModule(TrackerMatchModule::class.java) + ?: return MutationResult(false, "PGM's tracker module is not loaded for this match.") + + val spawned = mutableSetOf() + val teams = match.competitors.filterIsInstance() + for (team in teams) { + val location = findSpawnLocation(match, spawns, team) ?: continue + val warden = match.world.spawnEntity(location, wardenType) + if (warden is LivingEntity) { + tracker.entityTracker.trackEntity(warden, MobInfo(warden, TeamParticipantState(team, location))) + } + spawned.add(warden.uniqueId) + } + + if (spawned.isEmpty()) { + return MutationResult(false, "No team spawn locations were found for $name.") + } + + wardensByMatch[match.id] = spawned + return MutationResult(true, "Enabled $name and spawned ${spawned.size} wardens.") + } + + override fun disable(match: Match): MutationResult { + val removed = killWardens(match) + wardensByMatch.remove(match.id) + return MutationResult(true, "Disabled $name and killed $removed wardens.") + } + + private fun findSpawnLocation(match: Match, spawns: SpawnMatchModule, team: Competitor): Location? { + val player = teamSpawnQuery(match, team) + val spawn = spawns.getSpawns(player).choose(match) ?: return null + return spawn.getSpawn(player) + } + + private fun List.choose(match: Match): Spawn? { + if (isEmpty()) return null + return this[match.random.nextInt(size)] + } + + private fun killWardens(match: Match): Int { + val wardenType = wardenType() ?: return 0 + var killed = 0 + for (entity in match.world.entities) { + if (entity.type != wardenType) continue + + if (entity is LivingEntity) { + entity.health = 0.0 + } else { + entity.remove() + } + killed++ + } + return killed + } + + private fun teamSpawnQuery(match: Match, team: Competitor): MatchPlayer { + val handler = InvocationHandler { proxy, method, args -> + when (method.name) { + "getMatch" -> match + "getParty", "getCompetitor" -> team + "getId" -> UUID.nameUUIDFromBytes("uranus:${match.id}:${team.id}:spawn-query".toByteArray()) + "getBukkit", "getPlayer", "getNick", "getSpectatorTarget" -> null + "getState", "getParticipantState" -> TeamParticipantState(team, match.world.spawnLocation) + "getLocation" -> match.world.spawnLocation + "getWorld" -> match.world + "getEntityType" -> org.bukkit.entity.Player::class.java + "isParticipating", "isAlive", "isVisible", "canInteract", "canSee" -> true + "isObserving", "isDead", "isFrozen", "isLegacy" -> false + "getProtocolVersion" -> Int.MAX_VALUE + "getNameLegacy", "getPrefixedName" -> "${team.nameLegacy} spawn" + "getName" -> team.name + "getFilterableParent" -> team + "getFilterableChildren", "getFilterableDescendants", "getSpectators" -> emptyList() + "getInventory" -> null + "getSettings" -> null + "getActivity" -> null + "getLastActive" -> java.time.Instant.now() + "audience" -> net.kyori.adventure.audience.Audience.empty() + "pointers" -> net.kyori.adventure.pointer.Pointers.empty() + "equals" -> proxy === args?.firstOrNull() + "hashCode" -> team.hashCode() + "toString" -> "WardenSpawnQuery(${team.nameLegacy})" + else -> { + if (method.returnType == Boolean::class.javaPrimitiveType) false + else if (method.returnType == Int::class.javaPrimitiveType) 0 + else if (method.returnType == Optional::class.java) Optional.empty() + else null + } + } + } + + return Proxy.newProxyInstance( + MatchPlayer::class.java.classLoader, + arrayOf(MatchPlayer::class.java), + handler + ) as MatchPlayer + } + + private fun wardenType(): EntityType? { + return runCatching { EntityType.valueOf("WARDEN") }.getOrNull() + } + +} diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 28eaf75..7f8901f 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -7,11 +7,11 @@ website: https://warzone.network/ dependencies: server: PGM: - load: AFTER + load: BEFORE required: true join-classpath: true voicechat: - load: AFTER + load: BEFORE required: false join-classpath: true #depend: [ voicechat ] @@ -23,4 +23,4 @@ dependencies: # vcunmute: # description: Unmute a player in the voice chat. # usage: /unmute -# permission: uranus.voicechat.unmute \ No newline at end of file +# permission: uranus.voicechat.unmute From b2b791fe3804f1471ee1fa17e42228f560b73e26 Mon Sep 17 00:00:00 2001 From: Mew2K Date: Fri, 22 May 2026 14:41:40 -0700 Subject: [PATCH 2/5] Polish warden mutation feedback Co-authored-by: Codex --- .../warzone/uranus/UranusCommandGraph.kt | 16 ++++++ .../warzone/uranus/mutations/Mutation.kt | 5 +- .../uranus/mutations/MutationManager.kt | 14 +++-- .../mutations/commands/MutationCommand.kt | 54 +++++++++++++++++-- .../mutations/warden/WardenSpawnMutation.kt | 32 +++++++++-- 5 files changed, 107 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/network/warzone/uranus/UranusCommandGraph.kt b/src/main/kotlin/network/warzone/uranus/UranusCommandGraph.kt index 7f19939..7066f58 100644 --- a/src/main/kotlin/network/warzone/uranus/UranusCommandGraph.kt +++ b/src/main/kotlin/network/warzone/uranus/UranusCommandGraph.kt @@ -4,9 +4,11 @@ import org.bukkit.command.CommandSender import org.bukkit.entity.Player import tc.oc.pgm.command.parsers.PlayerParser import tc.oc.pgm.command.util.CommandGraph +import tc.oc.pgm.lib.org.incendo.cloud.exception.InvalidSyntaxException import tc.oc.pgm.lib.org.incendo.cloud.minecraft.extras.MinecraftHelp import tc.oc.pgm.lib.org.incendo.cloud.parser.ArgumentParser import tc.oc.pgm.util.Audience +import tc.oc.pgm.util.text.TextException import java.lang.reflect.Type @@ -22,6 +24,13 @@ class UranusCommandGraph(plugin: UranusPlugin) : CommandGraph(plug override fun setupInjectors() { } + override fun setupExceptionHandlers() { + super.setupExceptionHandlers() + registerExceptionHandler(InvalidSyntaxException::class.java) { + TextException.usage(formatUsage(it.correctSyntax())) + } + } + override fun setupParsers() { registerParser(Player::class.java, PlayerParser()) } @@ -39,5 +48,12 @@ class UranusCommandGraph(plugin: UranusPlugin) : CommandGraph(plug super.registerParser(type, parser) } + private fun formatUsage(syntax: String): String { + val formatted = syntax + .replace("mutation warden ", "mutation warden [on/off]") + .replace("mutation warden state", "mutation warden [on/off]") + + return if (formatted.startsWith("/")) formatted else "/$formatted" + } } diff --git a/src/main/kotlin/network/warzone/uranus/mutations/Mutation.kt b/src/main/kotlin/network/warzone/uranus/mutations/Mutation.kt index c41b269..f8e53e0 100644 --- a/src/main/kotlin/network/warzone/uranus/mutations/Mutation.kt +++ b/src/main/kotlin/network/warzone/uranus/mutations/Mutation.kt @@ -20,6 +20,7 @@ interface Mutation { data class MutationResult( val success: Boolean, - val message: String + val message: String, + val mutation: Mutation? = null, + val enabled: Boolean = false ) - diff --git a/src/main/kotlin/network/warzone/uranus/mutations/MutationManager.kt b/src/main/kotlin/network/warzone/uranus/mutations/MutationManager.kt index 91c4333..336a6c8 100644 --- a/src/main/kotlin/network/warzone/uranus/mutations/MutationManager.kt +++ b/src/main/kotlin/network/warzone/uranus/mutations/MutationManager.kt @@ -3,16 +3,23 @@ package network.warzone.uranus.mutations import network.warzone.uranus.UranusPlugin import network.warzone.uranus.mutations.commands.MutationCommand import network.warzone.uranus.mutations.warden.WardenSpawnMutation +import org.bukkit.event.Listener import tc.oc.pgm.api.match.Match class MutationManager(plugin: UranusPlugin) { - private val mutations = listOf( - WardenSpawnMutation() - ).associateBy { it.id } + private val wardenSpawnMutation = WardenSpawnMutation() + + private val mutations = listOf(wardenSpawnMutation).associateBy { it.id } val command = MutationCommand(this) + init { + mutations.values + .filterIsInstance() + .forEach(plugin::registerEvents) + } + fun get(id: String): Mutation? = mutations[id.lowercase()] fun disableAll(match: Match) { @@ -22,4 +29,3 @@ class MutationManager(plugin: UranusPlugin) { } } - diff --git a/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt b/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt index bf48d97..04d88e2 100644 --- a/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt +++ b/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt @@ -1,15 +1,23 @@ package network.warzone.uranus.mutations.commands +import net.kyori.adventure.key.Key +import net.kyori.adventure.sound.Sound import net.kyori.adventure.text.Component.text import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer +import net.kyori.adventure.title.Title.title import network.warzone.uranus.mutations.MutationManager +import org.bukkit.Bukkit import org.bukkit.command.CommandSender import tc.oc.pgm.api.PGM import tc.oc.pgm.api.match.Match +import tc.oc.pgm.api.match.MatchPhase import tc.oc.pgm.lib.org.incendo.cloud.annotations.Argument import tc.oc.pgm.lib.org.incendo.cloud.annotations.Command import tc.oc.pgm.lib.org.incendo.cloud.annotations.CommandDescription import tc.oc.pgm.lib.org.incendo.cloud.annotations.Permission +import tc.oc.pgm.util.Audience +import java.time.Duration class MutationCommand(private val mutations: MutationManager) { @@ -28,7 +36,7 @@ class MutationCommand(private val mutations: MutationManager) { "on", "enable", "enabled", "true", "yes" -> true "off", "disable", "disabled", "false", "no" -> false else -> { - sender.sendMessage(text("Use on or off.", NamedTextColor.RED)) + Audience.get(sender).sendWarning(text("Use on or off.", NamedTextColor.RED)) return } } @@ -38,27 +46,65 @@ class MutationCommand(private val mutations: MutationManager) { private fun handleWarden(sender: CommandSender, enabled: Boolean?) { val mutation = mutations.get("warden") ?: return val match = getMatch(sender) ?: run { - sender.sendMessage(text("No active PGM match was found.", NamedTextColor.RED)) + Audience.get(sender).sendWarning(text("No active PGM match was found.", NamedTextColor.RED)) return } if (!mutation.isAvailable(sender)) { - sender.sendMessage(text("${mutation.name} is not available on this server version.", NamedTextColor.RED)) + Audience.get(sender).sendWarning(text("${mutation.name} is not available on this server version.", NamedTextColor.RED)) return } val shouldEnable = enabled ?: !mutation.isEnabled(match) + if (shouldEnable && match.phase != MatchPhase.RUNNING) { + Audience.get(sender).sendWarning(text("Mutations cannot be enabled before the match starts!", NamedTextColor.RED)) + return + } + val result = if (shouldEnable) { mutation.enable(match) } else { mutation.disable(match) } - sender.sendMessage(text(result.message, if (result.success) NamedTextColor.GREEN else NamedTextColor.RED)) + if (result.success && result.enabled && result.mutation != null) { + announceEnabled(match, result.mutation.name) + } else { + Audience.get(sender).sendWarning(text(result.message, if (result.success) NamedTextColor.GREEN else NamedTextColor.RED)) + } } private fun getMatch(sender: CommandSender): Match? { return PGM.get().matchManager.getMatch(sender) } + private fun announceEnabled(match: Match, mutationName: String) { + val legacy = LegacyComponentSerializer.legacySection() + val title = title( + legacy.deserialize("\u00A73\u00A7l\u00A7k[]\u00A7r \u00A73\u00A7lMutation \u00A7k[]"), + legacy.deserialize("\u00A7aWarden Mayham"), + net.kyori.adventure.title.Title.Times.times( + Duration.ofMillis(500), + Duration.ofSeconds(5), + Duration.ofMillis(750) + ) + ) + val sound = Sound.sound( + Key.key("minecraft:entity.ender_dragon.growl"), + Sound.Source.MASTER, + 1f, + 1f + ) + val message = text("[Mutations] ", NamedTextColor.DARK_AQUA) + .append(text("The ", NamedTextColor.GRAY)) + .append(text(mutationName, NamedTextColor.AQUA)) + .append(text(" mutation has been enabled!", NamedTextColor.GRAY)) + + Bukkit.getOnlinePlayers().forEach { + it.showTitle(title) + it.playSound(sound) + it.sendMessage(message) + } + } + } diff --git a/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt b/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt index c307b4b..e54cfce 100644 --- a/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt +++ b/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt @@ -2,16 +2,21 @@ package network.warzone.uranus.mutations.warden import network.warzone.uranus.mutations.Mutation import network.warzone.uranus.mutations.MutationResult +import net.kyori.adventure.text.Component.text import org.bukkit.Location import org.bukkit.command.CommandSender import org.bukkit.entity.EntityType import org.bukkit.entity.LivingEntity +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.entity.EntityDeathEvent import tc.oc.pgm.api.match.Match import tc.oc.pgm.api.party.Competitor import tc.oc.pgm.api.player.MatchPlayer import tc.oc.pgm.spawns.Spawn import tc.oc.pgm.spawns.SpawnMatchModule import tc.oc.pgm.teams.Team +import tc.oc.pgm.teams.TeamMatchModule import tc.oc.pgm.tracker.TrackerMatchModule import tc.oc.pgm.tracker.info.MobInfo import java.lang.reflect.InvocationHandler @@ -19,10 +24,10 @@ import java.lang.reflect.Proxy import java.util.Optional import java.util.UUID -class WardenSpawnMutation : Mutation { +class WardenSpawnMutation : Mutation, Listener { override val id: String = "warden" - override val name: String = "Warden Spawn" + override val name: String = "Warden Mayham" private val wardensByMatch = mutableMapOf>() @@ -42,18 +47,26 @@ class WardenSpawnMutation : Mutation { return MutationResult(false, "$name is already enabled.") } + val teams = match.competitors.filterIsInstance() + if (match.getModule(TeamMatchModule::class.java) == null || teams.isEmpty()) { + return MutationResult(false, "This mutation requires a team-based map!") + } + val spawns = match.getModule(SpawnMatchModule::class.java) ?: return MutationResult(false, "This map does not expose PGM team spawns.") val tracker = match.getModule(TrackerMatchModule::class.java) ?: return MutationResult(false, "PGM's tracker module is not loaded for this match.") val spawned = mutableSetOf() - val teams = match.competitors.filterIsInstance() for (team in teams) { val location = findSpawnLocation(match, spawns, team) ?: continue val warden = match.world.spawnEntity(location, wardenType) if (warden is LivingEntity) { + warden.removeWhenFarAway = false + warden.isPersistent = true tracker.entityTracker.trackEntity(warden, MobInfo(warden, TeamParticipantState(team, location))) + warden.customName(team.getName().append(text("'s Warden"))) + warden.isCustomNameVisible = false } spawned.add(warden.uniqueId) } @@ -63,7 +76,7 @@ class WardenSpawnMutation : Mutation { } wardensByMatch[match.id] = spawned - return MutationResult(true, "Enabled $name and spawned ${spawned.size} wardens.") + return MutationResult(true, "Enabled $name and spawned ${spawned.size} wardens.", this, true) } override fun disable(match: Match): MutationResult { @@ -99,6 +112,17 @@ class WardenSpawnMutation : Mutation { return killed } + @EventHandler + fun onWardenDeath(event: EntityDeathEvent) { + val wardenType = wardenType() ?: return + if (event.entity.type != wardenType) return + + if (wardensByMatch.values.any { event.entity.uniqueId in it }) { + event.drops.clear() + event.droppedExp = 0 + } + } + private fun teamSpawnQuery(match: Match, team: Competitor): MatchPlayer { val handler = InvocationHandler { proxy, method, args -> when (method.name) { From 41a33fa1669f479c3361eaa8e1bec743d32d0140 Mon Sep 17 00:00:00 2001 From: Mew2K Date: Fri, 22 May 2026 14:48:29 -0700 Subject: [PATCH 3/5] Respawn mutation wardens after death Co-authored-by: Codex --- .../mutations/warden/WardenSpawnMutation.kt | 70 ++++++++++++++++--- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt b/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt index e54cfce..552c272 100644 --- a/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt +++ b/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt @@ -3,13 +3,17 @@ package network.warzone.uranus.mutations.warden import network.warzone.uranus.mutations.Mutation import network.warzone.uranus.mutations.MutationResult import net.kyori.adventure.text.Component.text +import network.warzone.uranus.UranusPlugin +import org.bukkit.Bukkit import org.bukkit.Location import org.bukkit.command.CommandSender +import org.bukkit.entity.Entity import org.bukkit.entity.EntityType import org.bukkit.entity.LivingEntity import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.entity.EntityDeathEvent +import tc.oc.pgm.api.PGM import tc.oc.pgm.api.match.Match import tc.oc.pgm.api.party.Competitor import tc.oc.pgm.api.player.MatchPlayer @@ -30,6 +34,7 @@ class WardenSpawnMutation : Mutation, Listener { override val name: String = "Warden Mayham" private val wardensByMatch = mutableMapOf>() + private val teamsByWarden = mutableMapOf() override fun isAvailable(sender: CommandSender): Boolean = wardenType() != null @@ -54,20 +59,12 @@ class WardenSpawnMutation : Mutation, Listener { val spawns = match.getModule(SpawnMatchModule::class.java) ?: return MutationResult(false, "This map does not expose PGM team spawns.") - val tracker = match.getModule(TrackerMatchModule::class.java) + match.getModule(TrackerMatchModule::class.java) ?: return MutationResult(false, "PGM's tracker module is not loaded for this match.") val spawned = mutableSetOf() for (team in teams) { - val location = findSpawnLocation(match, spawns, team) ?: continue - val warden = match.world.spawnEntity(location, wardenType) - if (warden is LivingEntity) { - warden.removeWhenFarAway = false - warden.isPersistent = true - tracker.entityTracker.trackEntity(warden, MobInfo(warden, TeamParticipantState(team, location))) - warden.customName(team.getName().append(text("'s Warden"))) - warden.isCustomNameVisible = false - } + val warden = spawnWarden(match, spawns, team, wardenType) ?: continue spawned.add(warden.uniqueId) } @@ -82,9 +79,33 @@ class WardenSpawnMutation : Mutation, Listener { override fun disable(match: Match): MutationResult { val removed = killWardens(match) wardensByMatch.remove(match.id) + teamsByWarden.entries.removeIf { it.value.startsWith("${match.id}:") } return MutationResult(true, "Disabled $name and killed $removed wardens.") } + private fun spawnWarden( + match: Match, + spawns: SpawnMatchModule, + team: Team, + wardenType: EntityType + ): Entity? { + val tracker = match.getModule(TrackerMatchModule::class.java) ?: return null + val location = findSpawnLocation(match, spawns, team) ?: return null + val warden = match.world.spawnEntity(location, wardenType) + + if (warden is LivingEntity) { + warden.removeWhenFarAway = false + warden.isPersistent = true + tracker.entityTracker.trackEntity(warden, MobInfo(warden, TeamParticipantState(team, location))) + warden.customName(team.getName().append(text("'s Warden"))) + warden.isCustomNameVisible = false + } + + wardensByMatch.getOrPut(match.id) { mutableSetOf() }.add(warden.uniqueId) + teamsByWarden[warden.uniqueId] = "${match.id}:${team.id}" + return warden + } + private fun findSpawnLocation(match: Match, spawns: SpawnMatchModule, team: Competitor): Location? { val player = teamSpawnQuery(match, team) val spawn = spawns.getSpawns(player).choose(match) ?: return null @@ -117,12 +138,35 @@ class WardenSpawnMutation : Mutation, Listener { val wardenType = wardenType() ?: return if (event.entity.type != wardenType) return - if (wardensByMatch.values.any { event.entity.uniqueId in it }) { + val teamKey = teamsByWarden.remove(event.entity.uniqueId) ?: return + val matchId = teamKey.substringBefore(":") + wardensByMatch[matchId]?.remove(event.entity.uniqueId) + + if (wardensByMatch.containsKey(matchId)) { event.drops.clear() event.droppedExp = 0 + scheduleRespawn(matchId, teamKey.substringAfter(":"), wardenType) } } + private fun scheduleRespawn(matchId: String, teamId: String, wardenType: EntityType) { + Bukkit.getScheduler().runTaskLater(UranusPlugin.get(), Runnable { + val match = PGM.get().matchManager.matches.asSequence() + .firstOrNull { it.id == matchId } + ?: return@Runnable + + if (!wardensByMatch.containsKey(match.id)) return@Runnable + + val spawns = match.getModule(SpawnMatchModule::class.java) ?: return@Runnable + val team = match.competitors + .filterIsInstance() + .firstOrNull { it.id == teamId } + ?: return@Runnable + + spawnWarden(match, spawns, team, wardenType) + }, RESPAWN_DELAY_TICKS) + } + private fun teamSpawnQuery(match: Match, team: Competitor): MatchPlayer { val handler = InvocationHandler { proxy, method, args -> when (method.name) { @@ -170,4 +214,8 @@ class WardenSpawnMutation : Mutation, Listener { return runCatching { EntityType.valueOf("WARDEN") }.getOrNull() } + companion object { + private const val RESPAWN_DELAY_TICKS = 60L * 20L + } + } From bd6062b9a72841ea4acac4039cd7906fcf3e3f23 Mon Sep 17 00:00:00 2001 From: Mew2K Date: Fri, 22 May 2026 15:11:58 -0700 Subject: [PATCH 4/5] Add potion effect mutations Co-authored-by: Codex --- .../warzone/uranus/UranusCommandGraph.kt | 4 + .../uranus/mutations/MutationManager.kt | 10 ++- .../mutations/commands/MutationCommand.kt | 57 +++++++++--- .../uranus/mutations/effects/RageMutation.kt | 13 +++ .../mutations/effects/SpeedsterMutation.kt | 17 ++++ .../mutations/effects/StatusEffectMutation.kt | 89 +++++++++++++++++++ 6 files changed, 174 insertions(+), 16 deletions(-) create mode 100644 src/main/kotlin/network/warzone/uranus/mutations/effects/RageMutation.kt create mode 100644 src/main/kotlin/network/warzone/uranus/mutations/effects/SpeedsterMutation.kt create mode 100644 src/main/kotlin/network/warzone/uranus/mutations/effects/StatusEffectMutation.kt diff --git a/src/main/kotlin/network/warzone/uranus/UranusCommandGraph.kt b/src/main/kotlin/network/warzone/uranus/UranusCommandGraph.kt index 7066f58..daf5ba0 100644 --- a/src/main/kotlin/network/warzone/uranus/UranusCommandGraph.kt +++ b/src/main/kotlin/network/warzone/uranus/UranusCommandGraph.kt @@ -52,6 +52,10 @@ class UranusCommandGraph(plugin: UranusPlugin) : CommandGraph(plug val formatted = syntax .replace("mutation warden ", "mutation warden [on/off]") .replace("mutation warden state", "mutation warden [on/off]") + .replace("mutation rage ", "mutation rage [on/off]") + .replace("mutation rage state", "mutation rage [on/off]") + .replace("mutation speedster ", "mutation speedster [on/off]") + .replace("mutation speedster state", "mutation speedster [on/off]") return if (formatted.startsWith("/")) formatted else "/$formatted" } diff --git a/src/main/kotlin/network/warzone/uranus/mutations/MutationManager.kt b/src/main/kotlin/network/warzone/uranus/mutations/MutationManager.kt index 336a6c8..5974a18 100644 --- a/src/main/kotlin/network/warzone/uranus/mutations/MutationManager.kt +++ b/src/main/kotlin/network/warzone/uranus/mutations/MutationManager.kt @@ -2,15 +2,19 @@ package network.warzone.uranus.mutations import network.warzone.uranus.UranusPlugin import network.warzone.uranus.mutations.commands.MutationCommand +import network.warzone.uranus.mutations.effects.RageMutation +import network.warzone.uranus.mutations.effects.SpeedsterMutation import network.warzone.uranus.mutations.warden.WardenSpawnMutation import org.bukkit.event.Listener import tc.oc.pgm.api.match.Match class MutationManager(plugin: UranusPlugin) { - private val wardenSpawnMutation = WardenSpawnMutation() - - private val mutations = listOf(wardenSpawnMutation).associateBy { it.id } + private val mutations = listOf( + WardenSpawnMutation(), + RageMutation(), + SpeedsterMutation() + ).associateBy { it.id } val command = MutationCommand(this) diff --git a/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt b/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt index 04d88e2..5d3cd15 100644 --- a/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt +++ b/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt @@ -25,26 +25,46 @@ class MutationCommand(private val mutations: MutationManager) { @CommandDescription("Toggle the Warden spawn mutation") @Permission("uranus.mutation.warden") fun toggleWarden(sender: CommandSender) { - handleWarden(sender, null) + handleMutation(sender, "warden", null) } @Command("mutation warden ") @CommandDescription("Enable or disable the Warden spawn mutation") @Permission("uranus.mutation.warden") fun setWarden(sender: CommandSender, @Argument("state") state: String) { - val enabled = when (state.lowercase()) { - "on", "enable", "enabled", "true", "yes" -> true - "off", "disable", "disabled", "false", "no" -> false - else -> { - Audience.get(sender).sendWarning(text("Use on or off.", NamedTextColor.RED)) - return - } - } - handleWarden(sender, enabled) + handleMutation(sender, "warden", parseState(sender, state) ?: return) + } + + @Command("mutation rage") + @CommandDescription("Toggle the Rage mutation") + @Permission("uranus.mutation.rage") + fun toggleRage(sender: CommandSender) { + handleMutation(sender, "rage", null) + } + + @Command("mutation rage ") + @CommandDescription("Enable or disable the Rage mutation") + @Permission("uranus.mutation.rage") + fun setRage(sender: CommandSender, @Argument("state") state: String) { + handleMutation(sender, "rage", parseState(sender, state) ?: return) + } + + @Command("mutation speedster") + @CommandDescription("Toggle the Speedster mutation") + @Permission("uranus.mutation.speedster") + fun toggleSpeedster(sender: CommandSender) { + handleMutation(sender, "speedster", null) + } + + @Command("mutation speedster ") + @CommandDescription("Enable or disable the Speedster mutation") + @Permission("uranus.mutation.speedster") + fun setSpeedster(sender: CommandSender, @Argument("state") state: String) { + handleMutation(sender, "speedster", parseState(sender, state) ?: return) } - private fun handleWarden(sender: CommandSender, enabled: Boolean?) { - val mutation = mutations.get("warden") ?: return + private fun handleMutation(sender: CommandSender, mutationId: String, enabled: Boolean?) { + val mutation = mutations.get(mutationId) ?: return val match = getMatch(sender) ?: run { Audience.get(sender).sendWarning(text("No active PGM match was found.", NamedTextColor.RED)) return @@ -78,11 +98,22 @@ class MutationCommand(private val mutations: MutationManager) { return PGM.get().matchManager.getMatch(sender) } + private fun parseState(sender: CommandSender, state: String): Boolean? { + return when (state.lowercase()) { + "on", "enable", "enabled", "true", "yes" -> true + "off", "disable", "disabled", "false", "no" -> false + else -> { + Audience.get(sender).sendWarning(text("Use on or off.", NamedTextColor.RED)) + null + } + } + } + private fun announceEnabled(match: Match, mutationName: String) { val legacy = LegacyComponentSerializer.legacySection() val title = title( legacy.deserialize("\u00A73\u00A7l\u00A7k[]\u00A7r \u00A73\u00A7lMutation \u00A7k[]"), - legacy.deserialize("\u00A7aWarden Mayham"), + text(mutationName, NamedTextColor.GREEN), net.kyori.adventure.title.Title.Times.times( Duration.ofMillis(500), Duration.ofSeconds(5), diff --git a/src/main/kotlin/network/warzone/uranus/mutations/effects/RageMutation.kt b/src/main/kotlin/network/warzone/uranus/mutations/effects/RageMutation.kt new file mode 100644 index 0000000..f8f7a76 --- /dev/null +++ b/src/main/kotlin/network/warzone/uranus/mutations/effects/RageMutation.kt @@ -0,0 +1,13 @@ +package network.warzone.uranus.mutations.effects + +class RageMutation : StatusEffectMutation( + id = "rage", + name = "Rage", + effects = listOf( + MutationEffect( + names = listOf("INCREASE_DAMAGE", "STRENGTH"), + amplifier = 254 + ) + ) +) + diff --git a/src/main/kotlin/network/warzone/uranus/mutations/effects/SpeedsterMutation.kt b/src/main/kotlin/network/warzone/uranus/mutations/effects/SpeedsterMutation.kt new file mode 100644 index 0000000..f05c7c5 --- /dev/null +++ b/src/main/kotlin/network/warzone/uranus/mutations/effects/SpeedsterMutation.kt @@ -0,0 +1,17 @@ +package network.warzone.uranus.mutations.effects + +class SpeedsterMutation : StatusEffectMutation( + id = "speedster", + name = "Speedster", + effects = listOf( + MutationEffect( + names = listOf("SPEED"), + amplifier = 3 + ), + MutationEffect( + names = listOf("JUMP", "JUMP_BOOST"), + amplifier = 2 + ) + ) +) + diff --git a/src/main/kotlin/network/warzone/uranus/mutations/effects/StatusEffectMutation.kt b/src/main/kotlin/network/warzone/uranus/mutations/effects/StatusEffectMutation.kt new file mode 100644 index 0000000..43f9429 --- /dev/null +++ b/src/main/kotlin/network/warzone/uranus/mutations/effects/StatusEffectMutation.kt @@ -0,0 +1,89 @@ +package network.warzone.uranus.mutations.effects + +import network.warzone.uranus.mutations.Mutation +import network.warzone.uranus.mutations.MutationResult +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.potion.PotionEffect +import org.bukkit.potion.PotionEffectType +import tc.oc.pgm.api.match.Match +import tc.oc.pgm.spawns.events.ParticipantSpawnEvent + +abstract class StatusEffectMutation( + override val id: String, + override val name: String, + private val effects: List +) : Mutation, Listener { + + private val enabledMatches = mutableSetOf() + + override fun isAvailable(sender: CommandSender): Boolean { + return effects.all { it.type() != null } + } + + override fun isEnabled(match: Match): Boolean { + return match.id in enabledMatches + } + + override fun enable(match: Match): MutationResult { + if (isEnabled(match)) { + return MutationResult(false, "$name is already enabled.") + } + + enabledMatches.add(match.id) + match.participants.forEach { applyEffects(it.bukkit) } + return MutationResult(true, "Enabled $name.", this, true) + } + + override fun disable(match: Match): MutationResult { + enabledMatches.remove(match.id) + match.participants.forEach { removeEffects(it.bukkit) } + return MutationResult(true, "Disabled $name.") + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + fun onParticipantSpawn(event: ParticipantSpawnEvent) { + if (!isEnabled(event.match)) return + + val player = event.player.bukkit + player.server.scheduler.runTaskLater( + network.warzone.uranus.UranusPlugin.get(), + Runnable { applyEffects(player) }, + 1L + ) + } + + private fun applyEffects(player: Player) { + effects.forEach { mutationEffect -> + val type = mutationEffect.type() ?: return@forEach + player.addPotionEffect( + PotionEffect(type, INFINITE_DURATION, mutationEffect.amplifier), + true + ) + } + } + + private fun removeEffects(player: Player) { + effects.forEach { mutationEffect -> + val type = mutationEffect.type() ?: return@forEach + player.removePotionEffect(type) + } + } + + data class MutationEffect( + private val names: List, + val amplifier: Int + ) { + fun type(): PotionEffectType? { + return names.firstNotNullOfOrNull(PotionEffectType::getByName) + } + } + + companion object { + private const val INFINITE_DURATION = Int.MAX_VALUE + } + +} From 2b7917798abd97ec3010b5e22f6112f8774f30d1 Mon Sep 17 00:00:00 2001 From: Mew2K Date: Fri, 22 May 2026 18:09:13 -0700 Subject: [PATCH 5/5] Support legacy SportPaper runtime Co-authored-by: Codex --- build.gradle.kts | 18 ++++- .../listeners/GeneratorSplittingListener.kt | 11 +-- .../mutations/commands/MutationCommand.kt | 72 ++++++++++++------- .../mutations/warden/WardenSpawnMutation.kt | 23 ++++-- .../uranus/voice/commands/MuteCommand.kt | 16 ++--- .../voice/listeners/VoiceChatListener.kt | 47 ++++++++---- src/main/resources/paper-plugin.yml | 26 ------- src/main/resources/plugin.yml | 7 ++ 8 files changed, 130 insertions(+), 90 deletions(-) delete mode 100644 src/main/resources/paper-plugin.yml create mode 100644 src/main/resources/plugin.yml diff --git a/build.gradle.kts b/build.gradle.kts index 8691464..948b809 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,5 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { kotlin("jvm") version "2.2.0" @@ -14,6 +15,9 @@ repositories { maven("https://repo.papermc.io/repository/maven-public/") { name = "papermc-repo" } + maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") { + name = "spigotmc-repo" + } maven("https://oss.sonatype.org/content/groups/public/") { name = "sonatype" } @@ -22,7 +26,7 @@ repositories { } dependencies { - compileOnly("io.papermc.paper:paper-api:1.21.6-R0.1-SNAPSHOT") + compileOnly("org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") // implementation("org.incendo:cloud-paper:2.0.0-beta.10") // implementation("org.incendo:cloud-kotlin-coroutines-annotations:2.0.0") @@ -61,7 +65,15 @@ tasks { } } -val targetJavaVersion = 21 +val buildJavaVersion = 21 kotlin { - jvmToolchain(targetJavaVersion) + jvmToolchain(buildJavaVersion) + compilerOptions { + jvmTarget.set(JvmTarget.JVM_1_8) + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } diff --git a/src/main/kotlin/network/warzone/uranus/listeners/GeneratorSplittingListener.kt b/src/main/kotlin/network/warzone/uranus/listeners/GeneratorSplittingListener.kt index 418690c..2512bb3 100644 --- a/src/main/kotlin/network/warzone/uranus/listeners/GeneratorSplittingListener.kt +++ b/src/main/kotlin/network/warzone/uranus/listeners/GeneratorSplittingListener.kt @@ -2,14 +2,14 @@ package network.warzone.uranus.listeners import org.bukkit.event.EventHandler import org.bukkit.event.Listener -import org.bukkit.event.entity.EntityPickupItemEvent +import org.bukkit.event.player.PlayerPickupItemEvent import tc.oc.pgm.api.PGM import tc.oc.pgm.spawner.Spawner object GeneratorSplittingListener : Listener { @EventHandler - fun onItemPickup(event: EntityPickupItemEvent) { + fun onItemPickup(event: PlayerPickupItemEvent) { val item = event.item val metadata = item.getMetadata(Spawner.METADATA_KEY).firstOrNull { @@ -21,12 +21,13 @@ object GeneratorSplittingListener : Listener { } // Give the same amount of items to all nearby players - item.location.getNearbyPlayers(1.5) - .filter { it != event.entity } + item.world.players + .filter { it != event.player } + .filter { it.location.distanceSquared(item.location) <= 2.25 } .forEach { it.inventory.addItem(item.itemStack) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt b/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt index 5d3cd15..39b4977 100644 --- a/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt +++ b/src/main/kotlin/network/warzone/uranus/mutations/commands/MutationCommand.kt @@ -1,14 +1,11 @@ package network.warzone.uranus.mutations.commands -import net.kyori.adventure.key.Key -import net.kyori.adventure.sound.Sound import net.kyori.adventure.text.Component.text import net.kyori.adventure.text.format.NamedTextColor -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer -import net.kyori.adventure.title.Title.title import network.warzone.uranus.mutations.MutationManager import org.bukkit.Bukkit import org.bukkit.command.CommandSender +import org.bukkit.entity.Player import tc.oc.pgm.api.PGM import tc.oc.pgm.api.match.Match import tc.oc.pgm.api.match.MatchPhase @@ -17,7 +14,6 @@ import tc.oc.pgm.lib.org.incendo.cloud.annotations.Command import tc.oc.pgm.lib.org.incendo.cloud.annotations.CommandDescription import tc.oc.pgm.lib.org.incendo.cloud.annotations.Permission import tc.oc.pgm.util.Audience -import java.time.Duration class MutationCommand(private val mutations: MutationManager) { @@ -110,32 +106,54 @@ class MutationCommand(private val mutations: MutationManager) { } private fun announceEnabled(match: Match, mutationName: String) { - val legacy = LegacyComponentSerializer.legacySection() - val title = title( - legacy.deserialize("\u00A73\u00A7l\u00A7k[]\u00A7r \u00A73\u00A7lMutation \u00A7k[]"), - text(mutationName, NamedTextColor.GREEN), - net.kyori.adventure.title.Title.Times.times( - Duration.ofMillis(500), - Duration.ofSeconds(5), - Duration.ofMillis(750) - ) - ) - val sound = Sound.sound( - Key.key("minecraft:entity.ender_dragon.growl"), - Sound.Source.MASTER, - 1f, - 1f - ) - val message = text("[Mutations] ", NamedTextColor.DARK_AQUA) - .append(text("The ", NamedTextColor.GRAY)) - .append(text(mutationName, NamedTextColor.AQUA)) - .append(text(" mutation has been enabled!", NamedTextColor.GRAY)) + val title = "\u00A73\u00A7l\u00A7k[]\u00A7r \u00A73\u00A7lMutation \u00A7k[]" + val subtitle = "\u00A7a$mutationName" + val message = "\u00A73[Mutations] \u00A77The \u00A7b$mutationName \u00A77mutation has been enabled!" Bukkit.getOnlinePlayers().forEach { - it.showTitle(title) - it.playSound(sound) + sendTitle(it, title, subtitle) + playSound(it, "minecraft:entity.ender_dragon.growl") it.sendMessage(message) } } + private fun sendTitle(player: Player, title: String, subtitle: String) { + val playerClass = player.javaClass + val titleSent = runCatching { + val method = playerClass.getMethod( + "sendTitle", + String::class.java, + String::class.java, + Int::class.javaPrimitiveType, + Int::class.javaPrimitiveType, + Int::class.javaPrimitiveType + ) + method.invoke(player, title, subtitle, 10, 100, 15) + }.isSuccess + + if (titleSent) return + + val legacyTitleSent = runCatching { + val method = playerClass.getMethod("sendTitle", String::class.java, String::class.java) + method.invoke(player, title, subtitle) + }.isSuccess + + if (!legacyTitleSent) { + player.sendMessage("$title \u00A7r$subtitle") + } + } + + private fun playSound(player: Player, sound: String) { + runCatching { + val method = player.javaClass.getMethod( + "playSound", + org.bukkit.Location::class.java, + String::class.java, + Float::class.javaPrimitiveType, + Float::class.javaPrimitiveType + ) + method.invoke(player, player.location, sound, 1f, 1f) + } + } + } diff --git a/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt b/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt index 552c272..32445a2 100644 --- a/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt +++ b/src/main/kotlin/network/warzone/uranus/mutations/warden/WardenSpawnMutation.kt @@ -2,7 +2,6 @@ package network.warzone.uranus.mutations.warden import network.warzone.uranus.mutations.Mutation import network.warzone.uranus.mutations.MutationResult -import net.kyori.adventure.text.Component.text import network.warzone.uranus.UranusPlugin import org.bukkit.Bukkit import org.bukkit.Location @@ -40,7 +39,7 @@ class WardenSpawnMutation : Mutation, Listener { override fun isEnabled(match: Match): Boolean { val wardenIds = wardensByMatch[match.id] ?: return false - wardenIds.removeIf { match.world.getEntity(it) == null } + wardenIds.removeIf { id -> match.world.entities.none { it.uniqueId == id } } return wardenIds.isNotEmpty() } @@ -94,10 +93,10 @@ class WardenSpawnMutation : Mutation, Listener { val warden = match.world.spawnEntity(location, wardenType) if (warden is LivingEntity) { - warden.removeWhenFarAway = false - warden.isPersistent = true + invokeIfPresent(warden, "setRemoveWhenFarAway", false) + invokeIfPresent(warden, "setPersistent", true) tracker.entityTracker.trackEntity(warden, MobInfo(warden, TeamParticipantState(team, location))) - warden.customName(team.getName().append(text("'s Warden"))) + setCustomName(warden, "${team.nameLegacy}\u00A7r's Warden") warden.isCustomNameVisible = false } @@ -214,6 +213,20 @@ class WardenSpawnMutation : Mutation, Listener { return runCatching { EntityType.valueOf("WARDEN") }.getOrNull() } + private fun invokeIfPresent(entity: LivingEntity, methodName: String, value: Boolean) { + runCatching { + val method = entity.javaClass.getMethod(methodName, Boolean::class.javaPrimitiveType) + method.invoke(entity, value) + } + } + + private fun setCustomName(entity: LivingEntity, name: String) { + runCatching { + val method = entity.javaClass.getMethod("setCustomName", String::class.java) + method.invoke(entity, name) + } + } + companion object { private const val RESPAWN_DELAY_TICKS = 60L * 20L } diff --git a/src/main/kotlin/network/warzone/uranus/voice/commands/MuteCommand.kt b/src/main/kotlin/network/warzone/uranus/voice/commands/MuteCommand.kt index 2e42b5d..c881bd8 100644 --- a/src/main/kotlin/network/warzone/uranus/voice/commands/MuteCommand.kt +++ b/src/main/kotlin/network/warzone/uranus/voice/commands/MuteCommand.kt @@ -1,8 +1,6 @@ package network.warzone.uranus.voice.commands import de.maxhenkel.voicechat.api.VoicechatConnection -import net.kyori.adventure.text.Component.text -import net.kyori.adventure.text.format.NamedTextColor import network.warzone.uranus.voice.listeners.VoiceChatListener import network.warzone.uranus.voice.util.bukkit import org.bukkit.command.CommandSender @@ -22,13 +20,13 @@ class MuteCommand { val player = connection.bukkit()!! if (VoiceChatListener.isMuted(player.uniqueId)) { - sender.sendMessage(text("${player.name} is already muted in voice chat.", NamedTextColor.YELLOW)) + sender.sendMessage("\u00A7e${player.name} is already muted in voice chat.") return } VoiceChatListener.mutePlayer(player.uniqueId) - sender.sendMessage(text("Muted ${player.name} in voice chat.", NamedTextColor.GREEN)) - player.sendMessage(text("You have been muted in voice chat.", NamedTextColor.RED)) + sender.sendMessage("\u00A7aMuted ${player.name} in voice chat.") + player.sendMessage("\u00A7cYou have been muted in voice chat.") return } @@ -46,14 +44,14 @@ class UnmuteCommand { val player = connection.bukkit()!! if (!VoiceChatListener.isMuted(player.uniqueId)) { - sender.sendMessage(text("${player.name} is not muted in voice chat.", NamedTextColor.YELLOW)) + sender.sendMessage("\u00A7e${player.name} is not muted in voice chat.") return } VoiceChatListener.unmutePlayer(player.uniqueId) - sender.sendMessage(text("Unmuted ${player.name} in voice chat.", NamedTextColor.GREEN)) - player.sendMessage(text("You have been unmuted in voice chat.", NamedTextColor.GREEN)) + sender.sendMessage("\u00A7aUnmuted ${player.name} in voice chat.") + player.sendMessage("\u00A7aYou have been unmuted in voice chat.") return } -} \ No newline at end of file +} diff --git a/src/main/kotlin/network/warzone/uranus/voice/listeners/VoiceChatListener.kt b/src/main/kotlin/network/warzone/uranus/voice/listeners/VoiceChatListener.kt index e74731b..2ac16dc 100644 --- a/src/main/kotlin/network/warzone/uranus/voice/listeners/VoiceChatListener.kt +++ b/src/main/kotlin/network/warzone/uranus/voice/listeners/VoiceChatListener.kt @@ -5,15 +5,11 @@ import de.maxhenkel.voicechat.api.events.EventRegistration import de.maxhenkel.voicechat.api.events.MicrophonePacketEvent import de.maxhenkel.voicechat.api.events.PlayerConnectedEvent import de.maxhenkel.voicechat.api.events.PlayerDisconnectedEvent -import net.kyori.adventure.text.Component.text -import net.kyori.adventure.text.format.NamedTextColor -import net.kyori.adventure.title.Title -import net.kyori.adventure.title.Title.title import network.warzone.uranus.voice.util.bukkit import network.warzone.uranus.voice.util.matchPlayer +import org.bukkit.entity.Player import org.bukkit.event.Listener import tc.oc.pgm.teams.Team -import java.time.Duration import java.util.* object VoiceChatListener : Listener { @@ -35,15 +31,11 @@ object VoiceChatListener : Listener { val bukkitPlayer = event.connection.bukkit() ?: return - bukkitPlayer.showTitle(title( - text("Voice Chat Connected", NamedTextColor.YELLOW), - text("Please be aware that your microphone might be on!", NamedTextColor.GRAY), - Title.Times.times( - Duration.ofSeconds(1), - Duration.ofSeconds(5), - Duration.ofSeconds(1) - ) - )) + sendTitle( + bukkitPlayer, + "\u00A7eVoice Chat Connected", + "\u00A77Please be aware that your microphone might be on!" + ) } fun onDisconnect(event: PlayerDisconnectedEvent) { @@ -84,4 +76,29 @@ object VoiceChatListener : Listener { mutedPlayers.remove(player) } -} \ No newline at end of file + private fun sendTitle(player: Player, title: String, subtitle: String) { + val titleSent = runCatching { + val method = player.javaClass.getMethod( + "sendTitle", + String::class.java, + String::class.java, + Int::class.javaPrimitiveType, + Int::class.javaPrimitiveType, + Int::class.javaPrimitiveType + ) + method.invoke(player, title, subtitle, 20, 100, 20) + }.isSuccess + + if (titleSent) return + + val legacyTitleSent = runCatching { + val method = player.javaClass.getMethod("sendTitle", String::class.java, String::class.java) + method.invoke(player, title, subtitle) + }.isSuccess + + if (!legacyTitleSent) { + player.sendMessage("$title \u00A7r$subtitle") + } + } + +} diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml deleted file mode 100644 index 7f8901f..0000000 --- a/src/main/resources/paper-plugin.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Uranus -version: '1.0' -main: network.warzone.uranus.UranusPlugin -api-version: '1.21.8' -authors: [ Warzone ] -website: https://warzone.network/ -dependencies: - server: - PGM: - load: BEFORE - required: true - join-classpath: true - voicechat: - load: BEFORE - required: false - join-classpath: true -#depend: [ voicechat ] -#commands: -# vcmute: -# description: Mute a player in the voice chat. -# usage: /mute -# permission: uranus.voicechat.mute -# vcunmute: -# description: Unmute a player in the voice chat. -# usage: /unmute -# permission: uranus.voicechat.unmute diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..9f3b0eb --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,7 @@ +name: Uranus +version: '${version}' +main: network.warzone.uranus.UranusPlugin +authors: [ Warzone ] +website: https://warzone.network/ +depend: [ PGM ] +softdepend: [ voicechat ]