diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/com/dvorakmortonxd/qoltweaks/QoLtweaks.java b/src/main/java/com/dvorakmortonxd/qoltweaks/QoLtweaks.java index e71924c..bc3c3e6 100644 --- a/src/main/java/com/dvorakmortonxd/qoltweaks/QoLtweaks.java +++ b/src/main/java/com/dvorakmortonxd/qoltweaks/QoLtweaks.java @@ -1,18 +1,35 @@ package com.dvorakmortonxd.qoltweaks; import com.dvorakmortonxd.qoltweaks.listeners.JoinListener; +import com.dvorakmortonxd.qoltweaks.nopvp.NoPvPCommand; +import com.dvorakmortonxd.qoltweaks.nopvp.NoPvPListener; +import com.dvorakmortonxd.qoltweaks.nopvp.NoPvPManager; +import org.bukkit.command.PluginCommand; import org.bukkit.plugin.java.JavaPlugin; public final class QoLtweaks extends JavaPlugin { + private NoPvPManager noPvPManager; + @Override public void onEnable() { // Plugin startup logic + noPvPManager = new NoPvPManager(this); + getServer().getPluginManager().registerEvents(new JoinListener(), this); + getServer().getPluginManager().registerEvents(new NoPvPListener(noPvPManager), this); + + PluginCommand noPvPCommand = getCommand("nopvp"); + if (noPvPCommand != null) { + noPvPCommand.setExecutor(new NoPvPCommand(noPvPManager)); + } } @Override public void onDisable() { // Plugin shutdown logic + if (noPvPManager != null) { + noPvPManager.save(); + } } } diff --git a/src/main/java/com/dvorakmortonxd/qoltweaks/nopvp/NoPvPCommand.java b/src/main/java/com/dvorakmortonxd/qoltweaks/nopvp/NoPvPCommand.java new file mode 100644 index 0000000..b0ddfbd --- /dev/null +++ b/src/main/java/com/dvorakmortonxd/qoltweaks/nopvp/NoPvPCommand.java @@ -0,0 +1,61 @@ +package com.dvorakmortonxd.qoltweaks.nopvp; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public final class NoPvPCommand implements CommandExecutor { + + private final NoPvPManager manager; + + public NoPvPCommand(NoPvPManager manager) { + this.manager = manager; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player player)) { + sender.sendMessage("§cOnly players can use this command."); + return true; + } + if (!player.hasPermission("qoltweaks.nopvp")) { + player.sendMessage("§cYou don't have permission to use this command."); + return true; + } + + if (manager.isEnabled(player)) { + manager.disable(player); + player.sendMessage("§cYou turned off no-PvP. You can now be attacked by other players again."); + player.sendMessage("§cYou must wait 1 hour before you can turn no-PvP back on."); + return true; + } + + long remaining = manager.getRemainingCooldownMillis(player); + if (remaining > 0) { + player.sendMessage("§cYou must wait " + formatDuration(remaining) + " before enabling no-PvP again."); + return true; + } + + manager.enable(player); + player.sendMessage("§aYou enabled no-PvP. Other players can no longer damage you, and you can no longer damage other players."); + return true; + } + + private String formatDuration(long millis) { + long totalSeconds = millis / 1000; + long hours = totalSeconds / 3600; + long minutes = (totalSeconds % 3600) / 60; + long seconds = totalSeconds % 60; + + StringBuilder builder = new StringBuilder(); + if (hours > 0) { + builder.append(hours).append("h "); + } + if (minutes > 0) { + builder.append(minutes).append("m "); + } + builder.append(seconds).append("s"); + return builder.toString().trim(); + } +} diff --git a/src/main/java/com/dvorakmortonxd/qoltweaks/nopvp/NoPvPListener.java b/src/main/java/com/dvorakmortonxd/qoltweaks/nopvp/NoPvPListener.java new file mode 100644 index 0000000..e1240fe --- /dev/null +++ b/src/main/java/com/dvorakmortonxd/qoltweaks/nopvp/NoPvPListener.java @@ -0,0 +1,44 @@ +package com.dvorakmortonxd.qoltweaks.nopvp; + +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; + +public final class NoPvPListener implements Listener { + + private final NoPvPManager manager; + + public NoPvPListener(NoPvPManager manager) { + this.manager = manager; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (!(event.getEntity() instanceof Player victim)) { + return; + } + + Player attacker = resolveAttacker(event.getDamager()); + if (attacker == null) { + return; + } + + if (manager.isEnabled(attacker) || manager.isEnabled(victim)) { + event.setCancelled(true); + } + } + + private Player resolveAttacker(Entity damager) { + if (damager instanceof Player player) { + return player; + } + if (damager instanceof Projectile projectile && projectile.getShooter() instanceof Player player) { + return player; + } + return null; + } +} diff --git a/src/main/java/com/dvorakmortonxd/qoltweaks/nopvp/NoPvPManager.java b/src/main/java/com/dvorakmortonxd/qoltweaks/nopvp/NoPvPManager.java new file mode 100644 index 0000000..59e2928 --- /dev/null +++ b/src/main/java/com/dvorakmortonxd/qoltweaks/nopvp/NoPvPManager.java @@ -0,0 +1,95 @@ +package com.dvorakmortonxd.qoltweaks.nopvp; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public final class NoPvPManager { + + public static final long COOLDOWN_MILLIS = 60 * 60 * 1000L; // 1 hour + + private final JavaPlugin plugin; + private final File dataFile; + private final Map enabled = new ConcurrentHashMap<>(); + private final Map cooldownUntil = new ConcurrentHashMap<>(); + + public NoPvPManager(JavaPlugin plugin) { + this.plugin = plugin; + this.dataFile = new File(plugin.getDataFolder(), "nopvp.yml"); + load(); + } + + public boolean isEnabled(Player player) { + return enabled.getOrDefault(player.getUniqueId(), false); + } + + public long getRemainingCooldownMillis(Player player) { + long until = cooldownUntil.getOrDefault(player.getUniqueId(), 0L); + return Math.max(0L, until - System.currentTimeMillis()); + } + + public void enable(Player player) { + UUID uuid = player.getUniqueId(); + enabled.put(uuid, true); + cooldownUntil.put(uuid, 0L); + scheduleSave(); + } + + public void disable(Player player) { + UUID uuid = player.getUniqueId(); + enabled.put(uuid, false); + cooldownUntil.put(uuid, System.currentTimeMillis() + COOLDOWN_MILLIS); + scheduleSave(); + } + + private void load() { + if (!dataFile.exists()) { + return; + } + YamlConfiguration config = YamlConfiguration.loadConfiguration(dataFile); + ConfigurationSection section = config.getConfigurationSection("players"); + if (section == null) { + return; + } + for (String key : section.getKeys(false)) { + UUID uuid; + try { + uuid = UUID.fromString(key); + } catch (IllegalArgumentException ignored) { + continue; + } + enabled.put(uuid, section.getBoolean(key + ".enabled", false)); + cooldownUntil.put(uuid, section.getLong(key + ".cooldown-until", 0L)); + } + } + + private void scheduleSave() { + plugin.getServer().getGlobalRegionScheduler().run(plugin, task -> save()); + } + + public void save() { + if (!dataFile.getParentFile().exists() && !dataFile.getParentFile().mkdirs()) { + plugin.getLogger().severe("Could not create data folder for nopvp.yml"); + return; + } + YamlConfiguration config = new YamlConfiguration(); + ConfigurationSection section = config.createSection("players"); + for (UUID uuid : enabled.keySet()) { + String path = uuid.toString(); + section.set(path + ".enabled", enabled.getOrDefault(uuid, false)); + section.set(path + ".cooldown-until", cooldownUntil.getOrDefault(uuid, 0L)); + } + try { + config.save(dataFile); + } catch (IOException e) { + plugin.getLogger().severe("Could not save nopvp.yml: " + e.getMessage()); + } + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 6c7e8d0..5dd6c2f 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -9,3 +9,13 @@ load: POSTWORLD authors: [ dvorakmortonxd ] website: dvorakmortonxd.dev + +commands: + nopvp: + description: Toggle no-PvP mode so other players cannot damage you and you cannot damage them. + usage: /nopvp + +permissions: + qoltweaks.nopvp: + description: Allows use of /nopvp + default: true