commit 7aad977d666a0997be4854c1e8a8ee32ba58f2fb Author: Xelara Networks Date: Sat Jun 20 12:07:10 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..3ad7cea --- /dev/null +++ b/pom.xml @@ -0,0 +1,48 @@ + + 4.0.0 + + com.bitnix + DirtPVP + 1.0 + jar + + DirtPVP + Advanced anti-flight and combat restriction plugin for Paper. + + + 21 + UTF-8 + + + + + papermc + https://repo.papermc.io/repository/maven-public/ + + + + + + io.papermc.paper + paper-api + 1.21.8-R0.1-SNAPSHOT + provided + + + + + DirtPVP + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 21 + + + + + diff --git a/src/main/java/com/bitnix/dirtpvp/DirtPVPPlugin.java b/src/main/java/com/bitnix/dirtpvp/DirtPVPPlugin.java new file mode 100644 index 0000000..779e889 --- /dev/null +++ b/src/main/java/com/bitnix/dirtpvp/DirtPVPPlugin.java @@ -0,0 +1,785 @@ +package com.bitnix.dirtpvp; + +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +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.EntityDamageEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerToggleFlightEvent; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitTask; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public final class DirtPVPPlugin extends JavaPlugin implements Listener, TabExecutor { + + private final Map combatUntil = new HashMap<>(); + private final Map expiryTasks = new HashMap<>(); + private final Map previousAllowFlight = new HashMap<>(); + private final Set notifyToggledOff = new HashSet<>(); + private final List killRewardRules = new ArrayList<>(); + + private long combatDurationTicks; + private long combatDurationMillis; + + private boolean disableFlightOnDamage; + private boolean disableFlightOnAttack; + private boolean disableFlightOnJoinIfTagged; + private boolean restoreFlightAfterCombat; + + private boolean opBypass; + private boolean permissionBypass; + private String bypassPermission; + + private boolean anyDamage; + private boolean playerVsPlayerOnly; + private boolean attackAnyEntity; + private boolean projectileCounts; + + private String worldsMode; + private Set configuredWorlds; + + private boolean blockedCommandsEnabled; + private Set blockedCommands; + + private boolean killRewardsEnabled; + + private boolean notificationsEnabled; + private boolean notifyConsole; + private boolean requireNotifyPermission; + private String notifyPermission; + private boolean notifyOnCombatTag; + private boolean notifyOnCommandBlock; + private boolean notifyOnFlightBlock; + private boolean notifyOnKillReward; + private boolean notifyOnKillRewardBlockedByIp; + + private String prefix; + private String msgCombatStartDamaged; + private String msgCombatStartAttack; + private String msgCombatFlyBlocked; + private String msgCombatEnded; + private String msgCommandBlocked; + private String msgNotifyCommandBlocked; + private String msgNotifyCombatTagged; + private String msgNotifyFlightBlocked; + private String msgNotifyKillReward; + private String msgNotifyKillRewardBlockedByIp; + private String msgReloadSuccess; + private String msgNoPermission; + private String msgNotifyEnabled; + private String msgNotifyDisabled; + private String msgUsage; + private String msgStatusSelf; + private String msgStatusOther; + private String msgPlayerNotFound; + private String msgOnlyPlayers; + + @Override + public void onEnable() { + saveDefaultConfig(); + loadPluginSettings(); + getServer().getPluginManager().registerEvents(this, this); + + if (getCommand("dirtpvp") != null) { + getCommand("dirtpvp").setExecutor(this); + getCommand("dirtpvp").setTabCompleter(this); + } + + getLogger().info("DirtPVP enabled."); + } + + @Override + public void onDisable() { + for (BukkitTask task : expiryTasks.values()) { + if (task != null) { + task.cancel(); + } + } + + expiryTasks.clear(); + combatUntil.clear(); + previousAllowFlight.clear(); + notifyToggledOff.clear(); + killRewardRules.clear(); + } + + private void loadPluginSettings() { + reloadConfig(); + FileConfiguration config = getConfig(); + + int seconds = config.getInt("combat.duration-seconds", 300); + if (seconds < 1) { + seconds = 300; + } + this.combatDurationMillis = seconds * 1000L; + this.combatDurationTicks = seconds * 20L; + + this.disableFlightOnDamage = config.getBoolean("flight.disable-on-damage", true); + this.disableFlightOnAttack = config.getBoolean("flight.disable-on-attack", true); + this.disableFlightOnJoinIfTagged = config.getBoolean("flight.disable-on-join-if-tagged", true); + this.restoreFlightAfterCombat = config.getBoolean("flight.restore-flight-after-combat", false); + + this.opBypass = config.getBoolean("bypass.op-bypass", true); + this.permissionBypass = config.getBoolean("bypass.permission-bypass", true); + this.bypassPermission = config.getString("bypass.permission", "dirtpvp.bypass"); + + this.anyDamage = config.getBoolean("tag-triggers.any-damage", true); + this.playerVsPlayerOnly = config.getBoolean("tag-triggers.player-vs-player-only", false); + this.attackAnyEntity = config.getBoolean("tag-triggers.attack-any-entity", true); + this.projectileCounts = config.getBoolean("tag-triggers.projectile-counts", true); + + this.worldsMode = config.getString("worlds.mode", "blacklist").toLowerCase(Locale.ROOT); + this.configuredWorlds = new HashSet<>(); + for (String worldName : config.getStringList("worlds.list")) { + if (worldName != null && !worldName.isBlank()) { + configuredWorlds.add(worldName.toLowerCase(Locale.ROOT)); + } + } + + this.blockedCommandsEnabled = config.getBoolean("blocked-commands.enabled", true); + this.blockedCommands = new HashSet<>(); + for (String command : config.getStringList("blocked-commands.commands")) { + if (command == null) { + continue; + } + String cleaned = normalizeCommand(command); + if (!cleaned.isEmpty()) { + blockedCommands.add(cleaned); + } + } + + this.killRewardsEnabled = config.getBoolean("kill-rewards.enabled", false); + this.killRewardRules.clear(); + ConfigurationSection rewardsSection = config.getConfigurationSection("kill-rewards.rewards"); + if (rewardsSection != null) { + for (String key : rewardsSection.getKeys(false)) { + ConfigurationSection rewardSection = rewardsSection.getConfigurationSection(key); + if (rewardSection == null) { + continue; + } + + boolean enabled = rewardSection.getBoolean("enabled", true); + boolean ipCheck = rewardSection.getBoolean("ip-check", false); + List commands = new ArrayList<>(); + for (String command : rewardSection.getStringList("commands")) { + if (command != null && !command.isBlank()) { + commands.add(command); + } + } + + killRewardRules.add(new KillRewardRule(key, enabled, ipCheck, commands)); + } + } + + this.notificationsEnabled = config.getBoolean("notifications.enabled", true); + this.notifyConsole = config.getBoolean("notifications.notify-console", false); + this.requireNotifyPermission = config.getBoolean("notifications.require-permission", true); + this.notifyPermission = config.getString("notifications.permission", "dirtpvp.notify"); + this.notifyOnCombatTag = config.getBoolean("notifications.send-on-combat-tag", false); + this.notifyOnCommandBlock = config.getBoolean("notifications.send-on-command-block", true); + this.notifyOnFlightBlock = config.getBoolean("notifications.send-on-flight-block", false); + this.notifyOnKillReward = config.getBoolean("notifications.send-on-kill-reward", false); + this.notifyOnKillRewardBlockedByIp = config.getBoolean("notifications.send-on-kill-reward-blocked-by-ip", false); + + this.prefix = color(config.getString("messages.prefix", "&8[&6DirtPVP&8] &r")); + this.msgCombatStartDamaged = color(config.getString("messages.combat-start-damaged", "&cFlight disabled: you were damaged and are now in combat for &e%time%&c seconds.")); + this.msgCombatStartAttack = color(config.getString("messages.combat-start-attack", "&cFlight disabled: you attacked something and are now in combat for &e%time%&c seconds.")); + this.msgCombatFlyBlocked = color(config.getString("messages.combat-fly-blocked", "&cYou cannot fly while in combat. Time left: &e%time%&c seconds.")); + this.msgCombatEnded = color(config.getString("messages.combat-ended", "&aYour combat timer ended. You may fly again if you still have permission.")); + this.msgCommandBlocked = color(config.getString("messages.command-blocked", "&cYou cannot use &e/%command% &cwhile in combat. Time left: &e%time%&c seconds.")); + this.msgNotifyCommandBlocked = color(config.getString("messages.notify-command-blocked", "&e%player% &7tried to use &f/%command% &7while in combat.")); + this.msgNotifyCombatTagged = color(config.getString("messages.notify-combat-tagged", "&e%player% &7was combat tagged.")); + this.msgNotifyFlightBlocked = color(config.getString("messages.notify-flight-blocked", "&e%player% &7tried to fly while in combat.")); + this.msgNotifyKillReward = color(config.getString("messages.notify-kill-reward", "&eExecuted kill reward '&f%reward%&e' for &f%killer% &7after killing &f%victim%&7.")); + this.msgNotifyKillRewardBlockedByIp = color(config.getString("messages.notify-kill-reward-blocked-by-ip", "&cSkipped kill reward '&f%reward%&c' for &f%killer% &7because killer/victim IP matched.")); + this.msgReloadSuccess = color(config.getString("messages.reload-success", "&aDirtPVP config reloaded.")); + this.msgNoPermission = color(config.getString("messages.no-permission", "&cYou do not have permission.")); + this.msgNotifyEnabled = color(config.getString("messages.notify-enabled", "&aYou will now receive DirtPVP notifications.")); + this.msgNotifyDisabled = color(config.getString("messages.notify-disabled", "&cYou will no longer receive DirtPVP notifications.")); + this.msgUsage = color(config.getString("messages.usage", "&eUsage: /dirtpvp reload &7| &e/dirtpvp notify &7| &e/dirtpvp status [player]")); + this.msgStatusSelf = color(config.getString("messages.status-self", "&eCombat status: &f%status% &7| Time left: &f%time%s")); + this.msgStatusOther = color(config.getString("messages.status-other", "&e%player%'s combat status: &f%status% &7| Time left: &f%time%s")); + this.msgPlayerNotFound = color(config.getString("messages.player-not-found", "&cPlayer not found.")); + this.msgOnlyPlayers = color(config.getString("messages.only-players", "&cOnly players can use this command.")); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerDamaged(EntityDamageEvent event) { + if (!disableFlightOnDamage) { + return; + } + if (!(event.getEntity() instanceof Player player)) { + return; + } + if (!isWorldEnabled(player.getWorld())) { + return; + } + if (shouldIgnore(player)) { + return; + } + if (playerVsPlayerOnly) { + return; + } + if (!anyDamage) { + return; + } + + enterCombat(player, msgCombatStartDamaged, notifyOnCombatTag); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onEntityDamagedByEntity(EntityDamageByEntityEvent event) { + Player attacker = getResponsiblePlayer(event.getDamager()); + boolean projectileHit = event.getDamager() instanceof Projectile; + + if (attacker != null && disableFlightOnAttack) { + if (isWorldEnabled(attacker.getWorld()) && !shouldIgnore(attacker)) { + if (!projectileHit || projectileCounts) { + if (attackAnyEntity || event.getEntity() instanceof Player) { + enterCombat(attacker, msgCombatStartAttack, notifyOnCombatTag); + } + } + } + } + + if (!(event.getEntity() instanceof Player victim)) { + return; + } + if (!disableFlightOnDamage) { + return; + } + if (!isWorldEnabled(victim.getWorld())) { + return; + } + if (shouldIgnore(victim)) { + return; + } + + if (playerVsPlayerOnly) { + if (attacker == null) { + return; + } + if (projectileHit && !projectileCounts) { + return; + } + enterCombat(victim, msgCombatStartDamaged, notifyOnCombatTag); + return; + } + + if (attacker != null) { + if (projectileHit && !projectileCounts) { + return; + } + enterCombat(victim, msgCombatStartDamaged, notifyOnCombatTag); + return; + } + + if (anyDamage) { + enterCombat(victim, msgCombatStartDamaged, notifyOnCombatTag); + } + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + if (!killRewardsEnabled) { + return; + } + + Player victim = event.getEntity(); + Player killer = victim.getKiller(); + + if (killer == null) { + return; + } + + if (!isWorldEnabled(victim.getWorld())) { + return; + } + + boolean sameIp = hasSameIp(killer, victim); + + for (KillRewardRule rule : killRewardRules) { + if (!rule.enabled()) { + continue; + } + if (rule.commands().isEmpty()) { + continue; + } + + if (rule.ipCheck() && sameIp) { + if (notifyOnKillRewardBlockedByIp) { + notifyStaff( + msgNotifyKillRewardBlockedByIp + .replace("%reward%", rule.name()) + .replace("%killer%", killer.getName()) + .replace("%victim%", victim.getName()) + ); + } + continue; + } + + for (String rawCommand : rule.commands()) { + String finalCommand = rawCommand + .replace("%killer%", killer.getName()) + .replace("%victim%", victim.getName()); + + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), finalCommand); + } + + if (notifyOnKillReward) { + notifyStaff( + msgNotifyKillReward + .replace("%reward%", rule.name()) + .replace("%killer%", killer.getName()) + .replace("%victim%", victim.getName()) + ); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onFlightToggle(PlayerToggleFlightEvent event) { + Player player = event.getPlayer(); + + if (!isWorldEnabled(player.getWorld())) { + return; + } + if (shouldIgnore(player)) { + return; + } + if (!isInCombat(player.getUniqueId())) { + return; + } + + event.setCancelled(true); + disableFlightNow(player); + player.sendMessage(withPrefix(replaceTime(msgCombatFlyBlocked, getRemainingSeconds(player.getUniqueId())))); + + if (notifyOnFlightBlock) { + notifyStaff(replacePlayerAndCommand(msgNotifyFlightBlocked, player.getName(), "")); + } + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + + if (!disableFlightOnJoinIfTagged) { + return; + } + if (!isWorldEnabled(player.getWorld())) { + return; + } + if (shouldIgnore(player)) { + return; + } + if (isInCombat(player.getUniqueId())) { + disableFlightNow(player); + } + } + + @EventHandler(ignoreCancelled = true) + public void onCommandPreprocess(PlayerCommandPreprocessEvent event) { + Player player = event.getPlayer(); + + if (!blockedCommandsEnabled) { + return; + } + if (!isWorldEnabled(player.getWorld())) { + return; + } + if (shouldIgnore(player)) { + return; + } + if (!isInCombat(player.getUniqueId())) { + return; + } + + String message = event.getMessage(); + if (message == null || message.isBlank() || !message.startsWith("/")) { + return; + } + + String raw = message.substring(1).trim(); + if (raw.isEmpty()) { + return; + } + + String[] split = raw.split("\\s+"); + String label = normalizeCommand(split[0]); + + if (!isBlockedCommand(label)) { + return; + } + + event.setCancelled(true); + player.sendMessage(withPrefix( + replaceTime( + replaceCommand(msgCommandBlocked, label), + getRemainingSeconds(player.getUniqueId()) + ) + )); + + if (notifyOnCommandBlock) { + notifyStaff(replacePlayerAndCommand(msgNotifyCommandBlocked, player.getName(), label)); + } + } + + private void enterCombat(Player player, String startMessage, boolean sendNotify) { + UUID uuid = player.getUniqueId(); + boolean alreadyInCombat = isInCombat(uuid); + + if (!previousAllowFlight.containsKey(uuid)) { + previousAllowFlight.put(uuid, player.getAllowFlight()); + } + + disableFlightNow(player); + + long expiresAt = System.currentTimeMillis() + combatDurationMillis; + combatUntil.put(uuid, expiresAt); + + BukkitTask oldTask = expiryTasks.remove(uuid); + if (oldTask != null) { + oldTask.cancel(); + } + + BukkitTask newTask = Bukkit.getScheduler().runTaskLater(this, () -> { + Long stored = combatUntil.get(uuid); + if (stored != null && stored <= System.currentTimeMillis()) { + combatUntil.remove(uuid); + expiryTasks.remove(uuid); + + Player online = Bukkit.getPlayer(uuid); + Boolean hadAllowFlight = previousAllowFlight.remove(uuid); + + if (online != null && online.isOnline()) { + if (restoreFlightAfterCombat && Boolean.TRUE.equals(hadAllowFlight)) { + online.setAllowFlight(true); + } + online.sendMessage(withPrefix(msgCombatEnded)); + } + } + }, combatDurationTicks); + + expiryTasks.put(uuid, newTask); + + if (!alreadyInCombat) { + player.sendMessage(withPrefix(replaceTime(startMessage, combatDurationMillis / 1000L))); + if (sendNotify) { + notifyStaff(replacePlayerAndCommand(msgNotifyCombatTagged, player.getName(), "")); + } + } + } + + private void disableFlightNow(Player player) { + if (player.isFlying()) { + player.setFlying(false); + } + if (player.getAllowFlight()) { + player.setAllowFlight(false); + } + } + + private boolean isBlockedCommand(String label) { + if (blockedCommands.contains(label)) { + return true; + } + + for (String blocked : blockedCommands) { + if (label.equals(blocked) || label.startsWith(blocked + ":")) { + return true; + } + } + + return false; + } + + private Player getResponsiblePlayer(Entity damager) { + if (damager instanceof Player player) { + return player; + } + + if (damager instanceof Projectile projectile) { + if (!projectileCounts) { + return null; + } + Object shooter = projectile.getShooter(); + if (shooter instanceof Player player) { + return player; + } + } + + return null; + } + + private boolean hasSameIp(Player first, Player second) { + InetSocketAddress firstAddress = first.getAddress(); + InetSocketAddress secondAddress = second.getAddress(); + + if (firstAddress == null || secondAddress == null) { + return false; + } + if (firstAddress.getAddress() == null || secondAddress.getAddress() == null) { + return false; + } + + return firstAddress.getAddress().getHostAddress().equals(secondAddress.getAddress().getHostAddress()); + } + + private boolean shouldIgnore(Player player) { + if (player.getGameMode() == GameMode.CREATIVE || player.getGameMode() == GameMode.SPECTATOR) { + return true; + } + if (opBypass && player.isOp()) { + return true; + } + return permissionBypass + && bypassPermission != null + && !bypassPermission.isBlank() + && player.hasPermission(bypassPermission); + } + + private boolean isWorldEnabled(World world) { + String worldName = world.getName().toLowerCase(Locale.ROOT); + + if (worldsMode.equals("whitelist")) { + return configuredWorlds.contains(worldName); + } + + if (worldsMode.equals("blacklist")) { + return !configuredWorlds.contains(worldName); + } + + return true; + } + + private boolean isInCombat(UUID uuid) { + Long expiresAt = combatUntil.get(uuid); + if (expiresAt == null) { + return false; + } + + if (expiresAt <= System.currentTimeMillis()) { + combatUntil.remove(uuid); + + BukkitTask task = expiryTasks.remove(uuid); + if (task != null) { + task.cancel(); + } + + previousAllowFlight.remove(uuid); + return false; + } + + return true; + } + + private long getRemainingSeconds(UUID uuid) { + Long expiresAt = combatUntil.get(uuid); + if (expiresAt == null) { + return 0L; + } + + long remaining = expiresAt - System.currentTimeMillis(); + if (remaining <= 0L) { + return 0L; + } + + return (remaining + 999L) / 1000L; + } + + private void notifyStaff(String message) { + if (!notificationsEnabled) { + return; + } + + String formatted = withPrefix(message); + + if (notifyConsole) { + Bukkit.getConsoleSender().sendMessage(formatted); + } + + for (Player online : Bukkit.getOnlinePlayers()) { + if (notifyToggledOff.contains(online.getUniqueId())) { + continue; + } + + if (requireNotifyPermission) { + if (notifyPermission == null || notifyPermission.isBlank() || !online.hasPermission(notifyPermission)) { + continue; + } + } + + online.sendMessage(formatted); + } + } + + private String normalizeCommand(String command) { + String cleaned = command.toLowerCase(Locale.ROOT).trim(); + if (cleaned.startsWith("/")) { + cleaned = cleaned.substring(1); + } + return cleaned; + } + + private String replaceTime(String message, long seconds) { + return message.replace("%time%", String.valueOf(seconds)); + } + + private String replaceCommand(String message, String command) { + return message.replace("%command%", command); + } + + private String replacePlayerAndCommand(String message, String player, String command) { + return message.replace("%player%", player).replace("%command%", command); + } + + private String withPrefix(String message) { + return prefix + message; + } + + private String color(String input) { + return input == null ? "" : input.replace("&", "ยง"); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (args.length == 0) { + sender.sendMessage(withPrefix(msgUsage)); + return true; + } + + if (args[0].equalsIgnoreCase("reload")) { + if (!sender.hasPermission("dirtpvp.reload")) { + sender.sendMessage(withPrefix(msgNoPermission)); + return true; + } + + loadPluginSettings(); + sender.sendMessage(withPrefix(msgReloadSuccess)); + return true; + } + + if (args[0].equalsIgnoreCase("notify")) { + if (!(sender instanceof Player player)) { + sender.sendMessage(withPrefix(msgOnlyPlayers)); + return true; + } + + if (requireNotifyPermission) { + if (notifyPermission == null || notifyPermission.isBlank() || !player.hasPermission(notifyPermission)) { + player.sendMessage(withPrefix(msgNoPermission)); + return true; + } + } + + UUID uuid = player.getUniqueId(); + if (notifyToggledOff.contains(uuid)) { + notifyToggledOff.remove(uuid); + player.sendMessage(withPrefix(msgNotifyEnabled)); + } else { + notifyToggledOff.add(uuid); + player.sendMessage(withPrefix(msgNotifyDisabled)); + } + return true; + } + + if (args[0].equalsIgnoreCase("status")) { + if (args.length == 1) { + if (!(sender instanceof Player player)) { + sender.sendMessage(withPrefix(msgOnlyPlayers)); + return true; + } + + boolean inCombat = isInCombat(player.getUniqueId()); + long time = getRemainingSeconds(player.getUniqueId()); + String status = inCombat ? "IN_COMBAT" : "SAFE"; + + player.sendMessage(withPrefix( + msgStatusSelf + .replace("%status%", status) + .replace("%time%", String.valueOf(time)) + )); + return true; + } + + Player target = Bukkit.getPlayerExact(args[1]); + if (target == null) { + sender.sendMessage(withPrefix(msgPlayerNotFound)); + return true; + } + + boolean inCombat = isInCombat(target.getUniqueId()); + long time = getRemainingSeconds(target.getUniqueId()); + String status = inCombat ? "IN_COMBAT" : "SAFE"; + + sender.sendMessage(withPrefix( + msgStatusOther + .replace("%player%", target.getName()) + .replace("%status%", status) + .replace("%time%", String.valueOf(time)) + )); + return true; + } + + sender.sendMessage(withPrefix(msgUsage)); + return true; + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + List completions = new ArrayList<>(); + + if (args.length == 1) { + String input = args[0].toLowerCase(Locale.ROOT); + + if ("reload".startsWith(input)) { + completions.add("reload"); + } + if ("notify".startsWith(input)) { + completions.add("notify"); + } + if ("status".startsWith(input)) { + completions.add("status"); + } + return completions; + } + + if (args.length == 2 && args[0].equalsIgnoreCase("status")) { + String input = args[1].toLowerCase(Locale.ROOT); + for (Player online : Bukkit.getOnlinePlayers()) { + if (online.getName().toLowerCase(Locale.ROOT).startsWith(input)) { + completions.add(online.getName()); + } + } + } + + return completions; + } + + private record KillRewardRule(String name, boolean enabled, boolean ipCheck, List commands) { + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..971e226 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,89 @@ +combat: + duration-seconds: 300 + +flight: + disable-on-damage: true + disable-on-attack: true + disable-on-join-if-tagged: true + restore-flight-after-combat: false + +bypass: + op-bypass: true + permission-bypass: true + permission: "dirtpvp.bypass" + +tag-triggers: + any-damage: true + player-vs-player-only: false + attack-any-entity: true + projectile-counts: true + +worlds: + mode: "blacklist" + list: + - "spawn" + +blocked-commands: + enabled: true + commands: + - "spawn" + - "home" + - "rt" + - "wild" + - "tpa" + - "tpahere" + +kill-rewards: + enabled: false + rewards: + money: + enabled: true + ip-check: true + commands: + - "eco give %killer% 1000" + shards: + enabled: false + ip-check: true + commands: + - "shards give %killer% 5" + broadcast: + enabled: false + ip-check: false + commands: + - "broadcast &e%killer% &7killed &e%victim%&7." + +notifications: + enabled: true + notify-console: false + require-permission: true + permission: "dirtpvp.notify" + send-on-combat-tag: false + send-on-command-block: true + send-on-flight-block: false + send-on-kill-reward: false + send-on-kill-reward-blocked-by-ip: false + +messages: + prefix: "&8[&6DirtPVP&8] &r" + + combat-start-damaged: "&cFlight disabled: you were damaged and are now in combat for &e%time%&c seconds." + combat-start-attack: "&cFlight disabled: you attacked something and are now in combat for &e%time%&c seconds." + combat-fly-blocked: "&cYou cannot fly while in combat. Time left: &e%time%&c seconds." + combat-ended: "&aYour combat timer ended. You may fly again if you still have permission." + + command-blocked: "&cYou cannot use &e/%command% &cwhile in combat. Time left: &e%time%&c seconds." + notify-command-blocked: "&e%player% &7tried to use &f/%command% &7while in combat." + notify-combat-tagged: "&e%player% &7was combat tagged." + notify-flight-blocked: "&e%player% &7tried to fly while in combat." + notify-kill-reward: "&eExecuted kill reward '&f%reward%&e' for &f%killer% &7after killing &f%victim%&7." + notify-kill-reward-blocked-by-ip: "&cSkipped kill reward '&f%reward%&c' for &f%killer% &7because killer/victim IP matched." + + reload-success: "&aDirtPVP config reloaded." + no-permission: "&cYou do not have permission." + notify-enabled: "&aYou will now receive DirtPVP notifications." + notify-disabled: "&cYou will no longer receive DirtPVP notifications." + usage: "&eUsage: /dirtpvp reload &7| &e/dirtpvp notify &7| &e/dirtpvp status [player]" + status-self: "&eCombat status: &f%status% &7| Time left: &f%time%s" + status-other: "&e%player%'s combat status: &f%status% &7| Time left: &f%time%s" + player-not-found: "&cPlayer not found." + only-players: "&cOnly players can use this command." diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..24728ef --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,29 @@ +name: DirtPVP +version: 1.0 +main: com.bitnix.dirtpvp.DirtPVPPlugin +api-version: '1.21' +author: bitnix +description: Advanced anti-flight and combat restriction plugin. + +commands: + dirtpvp: + description: Main DirtPVP command + usage: /dirtpvp + aliases: [antiflypvp] + +permissions: + dirtpvp.use: + description: Allows use of DirtPVP + default: true + + dirtpvp.reload: + description: Allows reloading DirtPVP config + default: op + + dirtpvp.bypass: + description: Bypass combat restrictions + default: op + + dirtpvp.notify: + description: Receive DirtPVP staff notifications + default: op diff --git a/target/DirtPVP.jar b/target/DirtPVP.jar new file mode 100644 index 0000000..64e78b2 Binary files /dev/null and b/target/DirtPVP.jar differ diff --git a/target/classes/com/bitnix/dirtpvp/DirtPVPPlugin$KillRewardRule.class b/target/classes/com/bitnix/dirtpvp/DirtPVPPlugin$KillRewardRule.class new file mode 100644 index 0000000..01bd2ee Binary files /dev/null and b/target/classes/com/bitnix/dirtpvp/DirtPVPPlugin$KillRewardRule.class differ diff --git a/target/classes/com/bitnix/dirtpvp/DirtPVPPlugin.class b/target/classes/com/bitnix/dirtpvp/DirtPVPPlugin.class new file mode 100644 index 0000000..efa051c Binary files /dev/null and b/target/classes/com/bitnix/dirtpvp/DirtPVPPlugin.class differ diff --git a/target/classes/config.yml b/target/classes/config.yml new file mode 100644 index 0000000..971e226 --- /dev/null +++ b/target/classes/config.yml @@ -0,0 +1,89 @@ +combat: + duration-seconds: 300 + +flight: + disable-on-damage: true + disable-on-attack: true + disable-on-join-if-tagged: true + restore-flight-after-combat: false + +bypass: + op-bypass: true + permission-bypass: true + permission: "dirtpvp.bypass" + +tag-triggers: + any-damage: true + player-vs-player-only: false + attack-any-entity: true + projectile-counts: true + +worlds: + mode: "blacklist" + list: + - "spawn" + +blocked-commands: + enabled: true + commands: + - "spawn" + - "home" + - "rt" + - "wild" + - "tpa" + - "tpahere" + +kill-rewards: + enabled: false + rewards: + money: + enabled: true + ip-check: true + commands: + - "eco give %killer% 1000" + shards: + enabled: false + ip-check: true + commands: + - "shards give %killer% 5" + broadcast: + enabled: false + ip-check: false + commands: + - "broadcast &e%killer% &7killed &e%victim%&7." + +notifications: + enabled: true + notify-console: false + require-permission: true + permission: "dirtpvp.notify" + send-on-combat-tag: false + send-on-command-block: true + send-on-flight-block: false + send-on-kill-reward: false + send-on-kill-reward-blocked-by-ip: false + +messages: + prefix: "&8[&6DirtPVP&8] &r" + + combat-start-damaged: "&cFlight disabled: you were damaged and are now in combat for &e%time%&c seconds." + combat-start-attack: "&cFlight disabled: you attacked something and are now in combat for &e%time%&c seconds." + combat-fly-blocked: "&cYou cannot fly while in combat. Time left: &e%time%&c seconds." + combat-ended: "&aYour combat timer ended. You may fly again if you still have permission." + + command-blocked: "&cYou cannot use &e/%command% &cwhile in combat. Time left: &e%time%&c seconds." + notify-command-blocked: "&e%player% &7tried to use &f/%command% &7while in combat." + notify-combat-tagged: "&e%player% &7was combat tagged." + notify-flight-blocked: "&e%player% &7tried to fly while in combat." + notify-kill-reward: "&eExecuted kill reward '&f%reward%&e' for &f%killer% &7after killing &f%victim%&7." + notify-kill-reward-blocked-by-ip: "&cSkipped kill reward '&f%reward%&c' for &f%killer% &7because killer/victim IP matched." + + reload-success: "&aDirtPVP config reloaded." + no-permission: "&cYou do not have permission." + notify-enabled: "&aYou will now receive DirtPVP notifications." + notify-disabled: "&cYou will no longer receive DirtPVP notifications." + usage: "&eUsage: /dirtpvp reload &7| &e/dirtpvp notify &7| &e/dirtpvp status [player]" + status-self: "&eCombat status: &f%status% &7| Time left: &f%time%s" + status-other: "&e%player%'s combat status: &f%status% &7| Time left: &f%time%s" + player-not-found: "&cPlayer not found." + only-players: "&cOnly players can use this command." diff --git a/target/classes/plugin.yml b/target/classes/plugin.yml new file mode 100644 index 0000000..24728ef --- /dev/null +++ b/target/classes/plugin.yml @@ -0,0 +1,29 @@ +name: DirtPVP +version: 1.0 +main: com.bitnix.dirtpvp.DirtPVPPlugin +api-version: '1.21' +author: bitnix +description: Advanced anti-flight and combat restriction plugin. + +commands: + dirtpvp: + description: Main DirtPVP command + usage: /dirtpvp + aliases: [antiflypvp] + +permissions: + dirtpvp.use: + description: Allows use of DirtPVP + default: true + + dirtpvp.reload: + description: Allows reloading DirtPVP config + default: op + + dirtpvp.bypass: + description: Bypass combat restrictions + default: op + + dirtpvp.notify: + description: Receive DirtPVP staff notifications + default: op diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..866f24e --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Sun Jun 14 18:05:11 EDT 2026 +artifactId=DirtPVP +groupId=com.bitnix +version=1.0 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..d335f65 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,2 @@ +com/bitnix/dirtpvp/DirtPVPPlugin.class +com/bitnix/dirtpvp/DirtPVPPlugin$KillRewardRule.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..668f8b0 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1 @@ +/home/bitnix/Desktop/DirtPVP/src/main/java/com/bitnix/dirtpvp/DirtPVPPlugin.java