commit b0235050f037ea7b56e25c82b8ce6a9936116ac8 Author: Xelara Networks Date: Thu Jun 11 20:49:27 2026 -0400 added diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..8028c34 --- /dev/null +++ b/pom.xml @@ -0,0 +1,47 @@ + + 4.0.0 + + com.bitnix + DirtHead + 1.0-SNAPSHOT + jar + + DirtHead + Drops glowing player heads on PvP death with configurable particles. + + + UTF-8 + 21 + + + + + papermc-repo + https://repo.papermc.io/repository/maven-public/ + + + + + + io.papermc.paper + paper-api + 1.21.1-R0.1-SNAPSHOT + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 21 + + + + + diff --git a/src/main/java/com/bitnix/dirthead/DirtHeadPlugin.java b/src/main/java/com/bitnix/dirthead/DirtHeadPlugin.java new file mode 100644 index 0000000..362a40f --- /dev/null +++ b/src/main/java/com/bitnix/dirthead/DirtHeadPlugin.java @@ -0,0 +1,221 @@ +package com.bitnix.dirthead; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitTask; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +public class DirtHeadPlugin extends JavaPlugin implements Listener { + + private final Map particleTasks = new HashMap<>(); + private final LegacyComponentSerializer legacy = LegacyComponentSerializer.legacyAmpersand(); + + @Override + public void onEnable() { + saveDefaultConfig(); + getServer().getPluginManager().registerEvents(this, this); + Objects.requireNonNull(getCommand("dirthead")).setExecutor((sender, command, label, args) -> { + if (!sender.hasPermission("dirthead.admin")) { + sender.sendMessage(color(getConfig().getString("messages.prefix", "&8[&6DirtHead&8] ") + + getConfig().getString("messages.no-permission", "&cYou do not have permission."))); + return true; + } + + if (args.length == 1 && args[0].equalsIgnoreCase("reload")) { + reloadConfig(); + sender.sendMessage(color(getConfig().getString("messages.prefix", "&8[&6DirtHead&8] ") + + getConfig().getString("messages.reload", "&aConfig reloaded."))); + return true; + } + + sender.sendMessage(color(getConfig().getString("messages.prefix", "&8[&6DirtHead&8] ") + + getConfig().getString("messages.usage", "&eUse: /dirthead reload"))); + return true; + }); + } + + @Override + public void onDisable() { + for (BukkitTask task : particleTasks.values()) { + if (task != null) { + task.cancel(); + } + } + particleTasks.clear(); + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + FileConfiguration config = getConfig(); + + if (!config.getBoolean("enabled", true)) { + return; + } + + if (!config.getBoolean("head.drop-on-pvp-death", true)) { + return; + } + + Player victim = event.getPlayer(); + Player killer = victim.getKiller(); + + if (killer == null) { + return; + } + + if (victim.hasPermission("dirthead.bypass")) { + return; + } + + if (config.getBoolean("head.op-bypass", false) && victim.isOp()) { + return; + } + + ItemStack head = createPlayerHead(victim, killer); + if (head == null) { + return; + } + + Location dropLocation = victim.getLocation().clone(); + Item dropped = victim.getWorld().dropItemNaturally(dropLocation, head); + + if (config.getBoolean("head.glowing.enabled", true)) { + dropped.setGlowing(true); + } + + dropped.setInvulnerable(config.getBoolean("head.invulnerable", true)); + dropped.setGravity(config.getBoolean("head.gravity", false)); + dropped.setPickupDelay(Math.max(0, config.getInt("head.pickup-delay-ticks", 40))); + + int despawnSeconds = Math.max(1, config.getInt("head.despawn-after-seconds", 120)); + getServer().getScheduler().runTaskLater(this, () -> { + if (dropped.isValid() && !dropped.isDead()) { + cancelParticleTask(dropped.getUniqueId()); + dropped.remove(); + } + }, despawnSeconds * 20L); + + startParticleTask(dropped); + + if (!config.getBoolean("head.keep-vanilla-drops", true)) { + event.getDrops().removeIf(item -> item.getType() == Material.PLAYER_HEAD); + } + } + + private ItemStack createPlayerHead(Player victim, Player killer) { + ItemStack item = new ItemStack(Material.PLAYER_HEAD); + ItemMeta rawMeta = item.getItemMeta(); + if (!(rawMeta instanceof SkullMeta meta)) { + return null; + } + + meta.setOwningPlayer(victim); + + String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + String displayName = getConfig().getString("head.name", "&6&l%player%'s Head") + .replace("%player%", victim.getName()) + .replace("%killer%", killer.getName()) + .replace("%time%", time); + + meta.displayName(color(displayName)); + + List loreLines = getConfig().getStringList("head.lore"); + if (!loreLines.isEmpty()) { + List lore = new ArrayList<>(); + for (String line : loreLines) { + lore.add(color(line + .replace("%player%", victim.getName()) + .replace("%killer%", killer.getName()) + .replace("%time%", time))); + } + meta.lore(lore); + } + + item.setItemMeta(meta); + return item; + } + + private void startParticleTask(Item dropped) { + FileConfiguration config = getConfig(); + + if (!config.getBoolean("particles.enabled", true)) { + return; + } + + Particle particle; + try { + particle = Particle.valueOf(config.getString("particles.particle", "DUST").toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException ex) { + getLogger().warning("Invalid particle in config. Falling back to DUST."); + particle = Particle.DUST; + } + + int interval = Math.max(1, config.getInt("particles.interval-ticks", 10)); + int count = Math.max(1, config.getInt("particles.count", 8)); + double offsetX = config.getDouble("particles.offset-x", 0.18); + double offsetY = config.getDouble("particles.offset-y", 0.25); + double offsetZ = config.getDouble("particles.offset-z", 0.18); + double extra = config.getDouble("particles.extra", 0.0); + + Particle finalParticle = particle; + + BukkitTask task = getServer().getScheduler().runTaskTimer(this, () -> { + if (!dropped.isValid() || dropped.isDead()) { + cancelParticleTask(dropped.getUniqueId()); + return; + } + + Location loc = dropped.getLocation().clone().add(0.0, 0.2, 0.0); + + if (finalParticle == Particle.DUST) { + int red = clampColor(getConfig().getInt("particles.color.red", 255)); + int green = clampColor(getConfig().getInt("particles.color.green", 215)); + int blue = clampColor(getConfig().getInt("particles.color.blue", 0)); + float size = (float) getConfig().getDouble("particles.color.size", 1.2); + + Particle.DustOptions dustOptions = new Particle.DustOptions( + Color.fromRGB(red, green, blue), + Math.max(0.1f, size) + ); + + dropped.getWorld().spawnParticle(finalParticle, loc, count, offsetX, offsetY, offsetZ, extra, dustOptions); + } else { + dropped.getWorld().spawnParticle(finalParticle, loc, count, offsetX, offsetY, offsetZ, extra); + } + }, 0L, interval); + + particleTasks.put(dropped.getUniqueId(), task); + } + + private void cancelParticleTask(UUID uuid) { + BukkitTask task = particleTasks.remove(uuid); + if (task != null) { + task.cancel(); + } + } + + private int clampColor(int value) { + return Math.max(0, Math.min(255, value)); + } + + private Component color(String text) { + return legacy.deserialize(text == null ? "" : text); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..d8d0231 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,37 @@ +enabled: true + +head: + drop-on-pvp-death: true + keep-vanilla-drops: true + op-bypass: false + name: "&6&l%player%'s Head" + lore: + - "&7Killed by: &c%killer%" + - "&7Time: &e%time%" + glowing: + enabled: true + invulnerable: true + gravity: false + pickup-delay-ticks: 40 + despawn-after-seconds: 120 + +particles: + enabled: true + particle: "DUST" + count: 8 + offset-x: 0.18 + offset-y: 0.25 + offset-z: 0.18 + extra: 0.0 + interval-ticks: 10 + color: + red: 255 + green: 215 + blue: 0 + size: 1.2 + +messages: + prefix: "&8[&6DirtHead&8] " + reload: "&aConfig reloaded." + no-permission: "&cYou do not have permission." + usage: "&eUse: /dirthead reload" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..c97409c --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,18 @@ +name: DirtHead +version: 1.0-SNAPSHOT +main: com.bitnix.dirthead.DirtHeadPlugin +api-version: '1.21' +author: bitnix +description: Drops glowing player heads on PvP death with configurable particles. +commands: + dirthead: + description: Reload DirtHead config + usage: /dirthead reload + permission: dirthead.admin +permissions: + dirthead.admin: + description: Allows reloading the DirtHead config + default: op + dirthead.bypass: + description: Prevents your head from dropping on PvP death + default: false diff --git a/target/DirtHead-1.0-SNAPSHOT.jar b/target/DirtHead-1.0-SNAPSHOT.jar new file mode 100644 index 0000000..527f0f5 Binary files /dev/null and b/target/DirtHead-1.0-SNAPSHOT.jar differ diff --git a/target/classes/com/bitnix/dirthead/DirtHeadPlugin.class b/target/classes/com/bitnix/dirthead/DirtHeadPlugin.class new file mode 100644 index 0000000..b994390 Binary files /dev/null and b/target/classes/com/bitnix/dirthead/DirtHeadPlugin.class differ diff --git a/target/classes/config.yml b/target/classes/config.yml new file mode 100644 index 0000000..d8d0231 --- /dev/null +++ b/target/classes/config.yml @@ -0,0 +1,37 @@ +enabled: true + +head: + drop-on-pvp-death: true + keep-vanilla-drops: true + op-bypass: false + name: "&6&l%player%'s Head" + lore: + - "&7Killed by: &c%killer%" + - "&7Time: &e%time%" + glowing: + enabled: true + invulnerable: true + gravity: false + pickup-delay-ticks: 40 + despawn-after-seconds: 120 + +particles: + enabled: true + particle: "DUST" + count: 8 + offset-x: 0.18 + offset-y: 0.25 + offset-z: 0.18 + extra: 0.0 + interval-ticks: 10 + color: + red: 255 + green: 215 + blue: 0 + size: 1.2 + +messages: + prefix: "&8[&6DirtHead&8] " + reload: "&aConfig reloaded." + no-permission: "&cYou do not have permission." + usage: "&eUse: /dirthead reload" diff --git a/target/classes/plugin.yml b/target/classes/plugin.yml new file mode 100644 index 0000000..c97409c --- /dev/null +++ b/target/classes/plugin.yml @@ -0,0 +1,18 @@ +name: DirtHead +version: 1.0-SNAPSHOT +main: com.bitnix.dirthead.DirtHeadPlugin +api-version: '1.21' +author: bitnix +description: Drops glowing player heads on PvP death with configurable particles. +commands: + dirthead: + description: Reload DirtHead config + usage: /dirthead reload + permission: dirthead.admin +permissions: + dirthead.admin: + description: Allows reloading the DirtHead config + default: op + dirthead.bypass: + description: Prevents your head from dropping on PvP death + default: false diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..2e1bda6 --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Thu Jun 11 20:29:18 EDT 2026 +artifactId=DirtHead +groupId=com.bitnix +version=1.0-SNAPSHOT diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..cc5dae8 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1 @@ +com/bitnix/dirthead/DirtHeadPlugin.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..0331504 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1 @@ +/home/bitnix/Desktop/DirtHead/src/main/java/com/bitnix/dirthead/DirtHeadPlugin.java