World whitelists for beheading and broadcasting

This commit is contained in:
NotAlexNoyle committed 2026-06-16 10:44:53 -04:00
1 parent bf4b54744b
commit 3f71a49c7c
7 files changed
+164 -1

No files matched your search

+2
View File
@@ -50,11 +50,13 @@ repositories {
mavenCentral() // Import the Maven Central Maven Repository.
gradlePluginPortal() // Import the Gradle Plugin Portal Maven Repository.
maven { url = uri("https://repo.purpurmc.org/snapshots") } // Import the PurpurMC Maven Repository.
maven { url = uri("https://repo.lucko.me/") } // Import the LuckPerms Maven Repository.
}
/* ---------------------- Java project deps ---------------------------- */
dependencies {
compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare Purpur API version to be packaged.
compileOnly("net.luckperms:api:5.5") // Declare LuckPerms API for prefix/name color lookups.
implementation("org.jetbrains.kotlin:kotlin-stdlib") // Import Kotlin standard library.
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") // Import Kotlin async library.
}
+1 -1
View File
@@ -1 +1 @@
rootProject.name = "MobHeads-OG"
rootProject.name = "MobHeads-OG"
@@ -0,0 +1,102 @@
package net.trueog.mobheadsog
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.minimessage.MiniMessage
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer
import net.luckperms.api.LuckPermsProvider
import org.bukkit.Bukkit
import org.bukkit.entity.Creeper
import org.bukkit.entity.Entity
import org.bukkit.entity.EntityType
import org.bukkit.entity.Player
object Broadcast {
private val mm = MiniMessage.miniMessage()
private val legacy = LegacyComponentSerializer.builder().character('&').hexColors().build()
/** Announce that [killer] beheaded [entity] to every online player in a whitelisted world. */
fun beheaded(killer: Player, entity: Entity) {
val config = MobHeadsOG.config
if (!config.broadcastEnabled) {
return
}
if (config.broadcastWorlds.isEmpty()) {
return
}
val rawPrefix = (getPrefix(killer) ?: "").replace('§', '&')
val prefix = legacy.deserialize(rawPrefix)
val name = legacy.deserialize(trailingColor(rawPrefix) + killer.name)
val mob = Component.text(mobName(entity))
val message =
mm.deserialize(
config.broadcastMessage,
Placeholder.component("prefix", prefix),
Placeholder.component("name", name),
Placeholder.component("mob", mob),
)
for (player in Bukkit.getOnlinePlayers()) {
if (player.world.name in config.broadcastWorlds) {
player.sendMessage(message)
}
}
}
/** @return The player's LuckPerms prefix, or null if LuckPerms is absent or the player has none. */
private fun getPrefix(player: Player): String? {
if (Bukkit.getPluginManager().getPlugin("LuckPerms") == null) {
return null
}
return try {
val luckPerms = LuckPermsProvider.get()
val user = luckPerms.getPlayerAdapter(Player::class.java).getUser(player)
user.cachedData.metaData.prefix
} catch (_: Exception) {
null
}
}
/** @return The last color/hex code (with its '&') in [raw], or an empty string if there is none. */
private fun trailingColor(raw: String): String {
var result = ""
var i = 0
while (i < raw.length) {
if (raw[i] != '&' || i + 1 >= raw.length) {
i++
continue
}
val code = raw[i + 1].lowercaseChar()
when {
code == '#' && i + 7 < raw.length -> {
result = raw.substring(i, i + 8) // &#rrggbb
i += 8
}
code in "0123456789abcdef" -> {
result = raw.substring(i, i + 2)
i += 2
}
code == 'r' -> {
result = "" // Reset clears the active color.
i += 2
}
code in "klmno" -> i += 2 // Formatting codes leave the color untouched.
else -> i++
}
}
return result
}
/** @return A human readable name for [entity] to display in the broadcast. */
private fun mobName(entity: Entity): String {
if (entity.type == EntityType.CREEPER) {
return if ((entity as Creeper).isPowered) "Charged Creeper" else "Creeper"
}
return Heads.getNameForEntity(entity)
?: entity.type.name.split("_").joinToString(" ") { word ->
word.lowercase().replaceFirstChar(Char::uppercase)
}
}
}
@@ -9,6 +9,14 @@ class Config {
var chargedCreeperChance: Double = 0.0
val beheadWorlds: MutableList<String> = mutableListOf()
var broadcastEnabled: Boolean = false
val broadcastWorlds: MutableList<String> = mutableListOf()
var broadcastMessage: String = ""
/** @return True if successful */
fun load(): Boolean {
val file = File(MobHeadsOG.plugin.dataFolder, "config.yml")
@@ -17,6 +25,28 @@ class Config {
}
val config = YamlConfiguration.loadConfiguration(file)
val beheadSection = config.getConfigurationSection("behead")
if (beheadSection == null) {
MobHeadsOG.plugin.logger.severe("Couldn't find a valid behead section in the config")
return false
}
beheadWorlds.clear()
beheadWorlds.addAll(beheadSection.getStringList("worlds"))
val broadcastSection = config.getConfigurationSection("broadcast")
if (broadcastSection == null) {
MobHeadsOG.plugin.logger.severe("Couldn't find a valid broadcast section in the config")
return false
}
broadcastEnabled = broadcastSection.getBoolean("enabled")
broadcastWorlds.clear()
broadcastWorlds.addAll(broadcastSection.getStringList("worlds"))
broadcastMessage = broadcastSection.getString("message") ?: ""
if (broadcastEnabled && broadcastMessage.isEmpty()) {
MobHeadsOG.plugin.logger.severe("Couldn't find a valid broadcast message in the config")
return false
}
val dropChancesSection = config.getConfigurationSection("dropChances")
if (dropChancesSection == null) {
MobHeadsOG.plugin.logger.severe("Couldn't find a valid dropChances section in the config")
@@ -39,6 +39,10 @@ class Events : Listener {
return
}
if (event.entity.world.name !in MobHeadsOG.config.beheadWorlds) {
return
}
val dropChance =
if (event.entityType == EntityType.CREEPER && (event.entity as Creeper).isPowered) {
MobHeadsOG.config.chargedCreeperChance
@@ -54,16 +58,20 @@ class Events : Listener {
return
}
val killer = event.entity.killer as Player
when (event.entityType) {
EntityType.SKELETON -> {
val skull = ItemStack(Material.SKELETON_SKULL, 1)
event.drops.add(skull)
Broadcast.beheaded(killer, event.entity)
return
}
EntityType.ZOMBIE -> {
val head = ItemStack(Material.ZOMBIE_HEAD, 1)
event.drops.add(head)
Broadcast.beheaded(killer, event.entity)
return
}
@@ -71,6 +79,7 @@ class Events : Listener {
if (!(event.entity as Creeper).isPowered) {
val head = ItemStack(Material.CREEPER_HEAD, 1)
event.drops.add(head)
Broadcast.beheaded(killer, event.entity)
return
}
}
@@ -98,6 +107,7 @@ class Events : Listener {
headMeta.playerProfile = profile
head.itemMeta = headMeta
event.drops.add(head)
Broadcast.beheaded(killer, event.entity)
}
@EventHandler
+17
View File
@@ -1,3 +1,20 @@
behead:
# Heads only drop in these worlds.
worlds:
- world
- world_nether
- world_the_end
broadcast:
enabled: true
# Only players in these worlds see the broadcast.
worlds:
- world
- world_nether
- world_the_end
# Placeholders: <prefix>, <name>, <mob>.
message: "<dark_red>☠</dark_red> <prefix><name> <gray>decapitated a</gray> <mob><gray>!</gray>"
dropChances:
allay: 0.025
axolotl: 0.025
+2
View File
@@ -2,3 +2,5 @@ name: MobHeads-OG
version: '${version}'
main: net.trueog.mobheadsog.MobHeadsOG
api-version: '${apiVersion}'
softdepend:
- LuckPerms