diff --git a/pom.xml b/pom.xml index 9f6d7c5..1f69437 100644 --- a/pom.xml +++ b/pom.xml @@ -163,6 +163,10 @@ jitpack.io https://jitpack.io + + opencollab-snapshot + https://repo.opencollab.dev/main/ + @@ -204,5 +208,17 @@ 1.7 provided + + org.geysermc.floodgate + api + 2.2.2-SNAPSHOT + provided + + + org.geysermc.cumulus + cumulus + 1.1.2 + provided + diff --git a/src/main/java/com/zetaplugins/essentialz/EssentialZ.java b/src/main/java/com/zetaplugins/essentialz/EssentialZ.java index de9f1f5..876d093 100644 --- a/src/main/java/com/zetaplugins/essentialz/EssentialZ.java +++ b/src/main/java/com/zetaplugins/essentialz/EssentialZ.java @@ -41,6 +41,7 @@ public final class EssentialZ extends ZetaCorePlugin { private final boolean hasPlaceholderApi = Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null; private final boolean hasVault = Bukkit.getPluginManager().getPlugin("Vault") != null; + private final boolean hasFloodgate = Bukkit.getPluginManager().getPlugin("floodgate") != null; @Override public void onEnable() { @@ -64,10 +65,22 @@ public void onEnable() { initPlaceholderAPI(); initBstats(); + initFloodgate(); getLogger().info("EssentialZ enabled!"); } + private void initFloodgate() { + if (hasFloodgate) { + boolean formsEnabled = getConfig().getBoolean("tpa.bedrock.formsEnabled", true); + if (formsEnabled) { + getLogger().info("Floodgate detected, TPA Bedrock forms enabled."); + } else { + getLogger().info("Floodgate detected, but TPA Bedrock forms are disabled in config."); + } + } + } + @Override public void onDisable() { getLogger().info("EssentialZ disabled!"); diff --git a/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpAcceptCommand.java b/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpAcceptCommand.java new file mode 100644 index 0000000..7817451 --- /dev/null +++ b/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpAcceptCommand.java @@ -0,0 +1,125 @@ +package com.zetaplugins.essentialz.commands.tpa; + +import com.zetaplugins.essentialz.EssentialZ; +import com.zetaplugins.essentialz.features.tpa.*; +import com.zetaplugins.essentialz.util.MessageManager; +import com.zetaplugins.essentialz.util.PluginMessage; +import com.zetaplugins.essentialz.util.commands.EszCommand; +import com.zetaplugins.zetacore.annotations.AutoRegisterCommand; +import com.zetaplugins.zetacore.annotations.InjectManager; +import com.zetaplugins.zetacore.commands.ArgumentList; +import com.zetaplugins.zetacore.commands.exceptions.CommandSenderMustBePlayerException; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + +@AutoRegisterCommand( + commands = "tpaccept", + description = "Accept a teleport request", + usage = "/tpaccept", + permission = "essentialz.tpaccept", + aliases = {"tpyes"} +) +public class TpAcceptCommand extends EszCommand { + + @InjectManager + private TpaManager tpaManager; + + @InjectManager + private TpaToggleManager toggleManager; + + @InjectManager + private TpaBedrockFormHandler bedrockFormHandler; + + private boolean floodgateEnabled = false; + + public TpAcceptCommand(EssentialZ plugin) { + super(plugin); + initFloodgate(); + } + + private void initFloodgate() { + if (getPlugin().getServer().getPluginManager().getPlugin("floodgate") != null) { + try { + Class.forName("org.geysermc.floodgate.api.FloodgateApi"); + floodgateEnabled = true; + } catch (ClassNotFoundException e) { + floodgateEnabled = false; + } + } + } + + @Override + public boolean execute(CommandSender sender, Command command, String label, ArgumentList args) throws CommandSenderMustBePlayerException { + if (!(sender instanceof Player player)) { + throw new CommandSenderMustBePlayerException(); + } + + List requests = tpaManager.getIncomingRequests(player.getUniqueId()); + + if (requests.isEmpty()) { + sender.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.TPA_NO_PENDING_REQUESTS)); + return true; + } + + // Bedrock player with multiple requests - show form + if (floodgateEnabled && isBedrockPlayer(player) && requests.size() > 1) { + bedrockFormHandler.sendPendingRequestsForm(player); + return true; + } + + // Accept the most recent request + TeleportRequest request = requests.get(requests.size() - 1); + acceptRequest(player, request); + tpaManager.removeRequest(request); + + TpaUtils.playSound(player, getPlugin().getConfig().getString("tpa.sounds.accept", "ENTITY_PLAYER_LEVELUP"), getPlugin()); + + return true; + } + + private void acceptRequest(Player acceptor, TeleportRequest request) { + Player senderPlayer = request.getSenderPlayer(); + Player targetPlayer = request.getTargetPlayer(); + + if (senderPlayer == null || !senderPlayer.isOnline()) { + acceptor.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.PLAYER_NOT_FOUND)); + return; + } + + if (targetPlayer == null || !targetPlayer.isOnline()) { + return; + } + + if (request.getType() == TpaRequestType.TPA) { + senderPlayer.teleport(targetPlayer.getLocation()); + } else { + targetPlayer.teleport(senderPlayer.getLocation()); + } + + senderPlayer.sendMessage(getMessageManager().getAndFormatMsg( + PluginMessage.TPA_ACCEPTED_SENDER, + new MessageManager.Replaceable<>("{player}", targetPlayer.getName()) + )); + + targetPlayer.sendMessage(getMessageManager().getAndFormatMsg( + PluginMessage.TPA_ACCEPTED_TARGET, + new MessageManager.Replaceable<>("{player}", senderPlayer.getName()) + )); + } + + @Override + public List tabComplete(CommandSender sender, Command command, ArgumentList args) { + return List.of(); + } + + private boolean isBedrockPlayer(Player player) { + try { + return org.geysermc.floodgate.api.FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId()); + } catch (Exception e) { + return false; + } + } +} diff --git a/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpDenyCommand.java b/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpDenyCommand.java new file mode 100644 index 0000000..207822d --- /dev/null +++ b/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpDenyCommand.java @@ -0,0 +1,76 @@ +package com.zetaplugins.essentialz.commands.tpa; + +import com.zetaplugins.essentialz.EssentialZ; +import com.zetaplugins.essentialz.features.tpa.TeleportRequest; +import com.zetaplugins.essentialz.features.tpa.TpaManager; +import com.zetaplugins.essentialz.features.tpa.TpaUtils; +import com.zetaplugins.essentialz.util.MessageManager; +import com.zetaplugins.essentialz.util.PluginMessage; +import com.zetaplugins.essentialz.util.commands.EszCommand; +import com.zetaplugins.zetacore.annotations.AutoRegisterCommand; +import com.zetaplugins.zetacore.annotations.InjectManager; +import com.zetaplugins.zetacore.commands.ArgumentList; +import com.zetaplugins.zetacore.commands.exceptions.CommandSenderMustBePlayerException; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + +@AutoRegisterCommand( + commands = "tpdeny", + description = "Deny a teleport request", + usage = "/tpdeny", + permission = "essentialz.tpdeny", + aliases = {"tpno"} +) +public class TpDenyCommand extends EszCommand { + + @InjectManager + private TpaManager tpaManager; + + public TpDenyCommand(EssentialZ plugin) { + super(plugin); + } + + @Override + public boolean execute(CommandSender sender, Command command, String label, ArgumentList args) throws CommandSenderMustBePlayerException { + if (!(sender instanceof Player player)) { + throw new CommandSenderMustBePlayerException(); + } + + List requests = tpaManager.getIncomingRequests(player.getUniqueId()); + + if (requests.isEmpty()) { + sender.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.TPA_NO_PENDING_REQUESTS)); + return true; + } + + // Deny the most recent request + TeleportRequest request = requests.get(requests.size() - 1); + denyRequest(player, request); + tpaManager.removeRequest(request); + + TpaUtils.playSound(player, getPlugin().getConfig().getString("tpa.sounds.deny", "ENTITY_VILLAGER_NO"), getPlugin()); + + return true; + } + + private void denyRequest(Player denier, TeleportRequest request) { + Player senderPlayer = request.getSenderPlayer(); + + if (senderPlayer != null && senderPlayer.isOnline()) { + senderPlayer.sendMessage(getMessageManager().getAndFormatMsg( + PluginMessage.TPA_DENIED_SENDER, + new MessageManager.Replaceable<>("{player}", denier.getName()) + )); + } + + denier.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.TPA_DENIED_TARGET)); + } + + @Override + public List tabComplete(CommandSender sender, Command command, ArgumentList args) { + return List.of(); + } +} diff --git a/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpaCancelCommand.java b/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpaCancelCommand.java new file mode 100644 index 0000000..2c93185 --- /dev/null +++ b/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpaCancelCommand.java @@ -0,0 +1,64 @@ +package com.zetaplugins.essentialz.commands.tpa; + +import com.zetaplugins.essentialz.EssentialZ; +import com.zetaplugins.essentialz.features.tpa.TeleportRequest; +import com.zetaplugins.essentialz.features.tpa.TpaManager; +import com.zetaplugins.essentialz.util.MessageManager; +import com.zetaplugins.essentialz.util.PluginMessage; +import com.zetaplugins.essentialz.util.commands.EszCommand; +import com.zetaplugins.zetacore.annotations.AutoRegisterCommand; +import com.zetaplugins.zetacore.annotations.InjectManager; +import com.zetaplugins.zetacore.commands.ArgumentList; +import com.zetaplugins.zetacore.commands.exceptions.CommandSenderMustBePlayerException; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + +@AutoRegisterCommand( + commands = "tpacancel", + description = "Cancel all outgoing teleport requests", + usage = "/tpacancel", + permission = "essentialz.tpacancel" +) +public class TpaCancelCommand extends EszCommand { + + @InjectManager + private TpaManager tpaManager; + + public TpaCancelCommand(EssentialZ plugin) { + super(plugin); + } + + @Override + public boolean execute(CommandSender sender, Command command, String label, ArgumentList args) throws CommandSenderMustBePlayerException { + if (!(sender instanceof Player player)) { + throw new CommandSenderMustBePlayerException(); + } + + List requests = tpaManager.getOutgoingRequests(player.getUniqueId()); + + if (requests.isEmpty()) { + sender.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.TPA_NO_OUTGOING_REQUESTS)); + return true; + } + + // Cancel all outgoing requests + for (TeleportRequest request : requests) { + tpaManager.removeRequest(request); + } + + sender.sendMessage(getMessageManager().getAndFormatMsg( + PluginMessage.TPA_REQUESTS_CANCELLED, + new MessageManager.Replaceable<>("{count}", String.valueOf(requests.size())) + )); + + return true; + } + + @Override + public List tabComplete(CommandSender sender, Command command, ArgumentList args) { + return List.of(); + } +} diff --git a/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpaCommand.java b/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpaCommand.java new file mode 100644 index 0000000..011313a --- /dev/null +++ b/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpaCommand.java @@ -0,0 +1,153 @@ +package com.zetaplugins.essentialz.commands.tpa; + +import com.zetaplugins.essentialz.EssentialZ; +import com.zetaplugins.essentialz.features.tpa.*; +import com.zetaplugins.essentialz.util.MessageManager; +import com.zetaplugins.essentialz.util.PluginMessage; +import com.zetaplugins.essentialz.util.commands.EszCommand; +import com.zetaplugins.zetacore.annotations.AutoRegisterCommand; +import com.zetaplugins.zetacore.annotations.InjectManager; +import com.zetaplugins.zetacore.commands.ArgumentList; +import com.zetaplugins.zetacore.commands.exceptions.CommandSenderMustBePlayerException; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.geysermc.floodgate.api.FloodgateApi; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@AutoRegisterCommand( + commands = "tpa", + description = "Request to teleport to another player", + usage = "/tpa ", + permission = "essentialz.tpa" +) +public class TpaCommand extends EszCommand { + + @InjectManager + private TpaManager tpaManager; + + @InjectManager + private TpaToggleManager toggleManager; + + @InjectManager + private TpaBedrockFormHandler bedrockFormHandler; + + private boolean floodgateEnabled = false; + + public TpaCommand(EssentialZ plugin) { + super(plugin); + initFloodgate(); + } + + private void initFloodgate() { + if (getPlugin().getServer().getPluginManager().getPlugin("floodgate") != null) { + try { + Class.forName("org.geysermc.floodgate.api.FloodgateApi"); + floodgateEnabled = true; + } catch (ClassNotFoundException e) { + floodgateEnabled = false; + } + } + } + + @Override + public boolean execute(CommandSender sender, Command command, String label, ArgumentList args) throws CommandSenderMustBePlayerException { + if (!(sender instanceof Player player)) { + throw new CommandSenderMustBePlayerException(); + } + + Player target = args.getPlayer(0, getPlugin()); + + // Bedrock player with no args - show form + if (target == null && floodgateEnabled && isBedrockPlayer(player)) { + bedrockFormHandler.sendPlayerSelectionForm(player, TpaRequestType.TPA); + return true; + } + + if (target == null) { + // Try partial match with raw argument + String rawArg = args.getJoinedString(0); + if (rawArg.isEmpty()) { + sender.sendMessage(getMessageManager().getAndFormatMsg( + PluginMessage.USAGE_ERROR, + new MessageManager.Replaceable<>("{usage}", "/tpa ") + )); + return true; + } + target = TpaUtils.findPlayer(rawArg); + } + if (target == null) { + sender.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.PLAYER_NOT_FOUND)); + return true; + } + + if (target.equals(player)) { + sender.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.TPA_CANNOT_SELF)); + return true; + } + + if (!toggleManager.isEnabled(target.getUniqueId())) { + sender.sendMessage(getMessageManager().getAndFormatMsg( + PluginMessage.TPA_DISABLED_TARGET, + new MessageManager.Replaceable<>("{player}", target.getName()) + )); + return true; + } + + TeleportRequest request = tpaManager.createRequest( + player.getUniqueId(), + target.getUniqueId(), + TpaRequestType.TPA + ); + + if (request == null) { + sender.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.TPA_REQUEST_ALREADY_PENDING)); + return true; + } + + sender.sendMessage(getMessageManager().getAndFormatMsg( + PluginMessage.TPA_REQUEST_SENT, + new MessageManager.Replaceable<>("{player}", target.getName()) + )); + + if (floodgateEnabled && isBedrockPlayer(target)) { + bedrockFormHandler.sendRequestNotification(target, player, TpaRequestType.TPA); + } else { + target.sendMessage(getMessageManager().getAndFormatMsg( + PluginMessage.TPA_REQUEST_RECEIVED, + new MessageManager.Replaceable<>("{player}", player.getName()), + new MessageManager.Replaceable<>("{type}", "teleport to you") + )); + target.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.TPA_REQUEST_INSTRUCTIONS, false)); + } + + TpaUtils.playSound(target, getPlugin().getConfig().getString("tpa.sounds.request", "ENTITY_EXPERIENCE_ORB_PICKUP"), getPlugin()); + + return true; + } + + @Override + public List tabComplete(CommandSender sender, Command command, ArgumentList args) { + if (args.getCurrentArgIndex() == 0) { + return Bukkit.getOnlinePlayers().stream() + .filter(p -> !p.equals(sender)) + .filter(p -> toggleManager.isEnabled(p.getUniqueId())) + .map(Player::getName) + .filter(name -> name.toLowerCase().startsWith(args.getCurrentArg().toLowerCase())) + .collect(Collectors.toList()); + } + return new ArrayList<>(); + } + + private boolean isBedrockPlayer(Player player) { + try { + return FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId()); + } catch (Exception e) { + return false; + } + } +} diff --git a/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpaHereCommand.java b/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpaHereCommand.java new file mode 100644 index 0000000..5ef0f61 --- /dev/null +++ b/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpaHereCommand.java @@ -0,0 +1,152 @@ +package com.zetaplugins.essentialz.commands.tpa; + +import com.zetaplugins.essentialz.EssentialZ; +import com.zetaplugins.essentialz.features.tpa.*; +import com.zetaplugins.essentialz.util.MessageManager; +import com.zetaplugins.essentialz.util.PluginMessage; +import com.zetaplugins.essentialz.util.commands.EszCommand; +import com.zetaplugins.zetacore.annotations.AutoRegisterCommand; +import com.zetaplugins.zetacore.annotations.InjectManager; +import com.zetaplugins.zetacore.commands.ArgumentList; +import com.zetaplugins.zetacore.commands.exceptions.CommandSenderMustBePlayerException; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@AutoRegisterCommand( + commands = "tpahere", + description = "Request another player to teleport to you", + usage = "/tpahere ", + permission = "essentialz.tpahere" +) +public class TpaHereCommand extends EszCommand { + + @InjectManager + private TpaManager tpaManager; + + @InjectManager + private TpaToggleManager toggleManager; + + @InjectManager + private TpaBedrockFormHandler bedrockFormHandler; + + private boolean floodgateEnabled = false; + + public TpaHereCommand(EssentialZ plugin) { + super(plugin); + initFloodgate(); + } + + private void initFloodgate() { + if (getPlugin().getServer().getPluginManager().getPlugin("floodgate") != null) { + try { + Class.forName("org.geysermc.floodgate.api.FloodgateApi"); + floodgateEnabled = true; + } catch (ClassNotFoundException e) { + floodgateEnabled = false; + } + } + } + + @Override + public boolean execute(CommandSender sender, Command command, String label, ArgumentList args) throws CommandSenderMustBePlayerException { + if (!(sender instanceof Player player)) { + throw new CommandSenderMustBePlayerException(); + } + + Player target = args.getPlayer(0, getPlugin()); + + // Bedrock player with no args - show form + if (target == null && floodgateEnabled && isBedrockPlayer(player)) { + bedrockFormHandler.sendPlayerSelectionForm(player, TpaRequestType.TPA_HERE); + return true; + } + + if (target == null) { + // Try partial match with raw argument + String rawArg = args.getJoinedString(0); + if (rawArg.isEmpty()) { + sender.sendMessage(getMessageManager().getAndFormatMsg( + PluginMessage.USAGE_ERROR, + new MessageManager.Replaceable<>("{usage}", "/tpahere ") + )); + return true; + } + target = TpaUtils.findPlayer(rawArg); + } + if (target == null) { + sender.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.PLAYER_NOT_FOUND)); + return true; + } + + if (target.equals(player)) { + sender.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.TPA_CANNOT_SELF)); + return true; + } + + if (!toggleManager.isEnabled(target.getUniqueId())) { + sender.sendMessage(getMessageManager().getAndFormatMsg( + PluginMessage.TPA_DISABLED_TARGET, + new MessageManager.Replaceable<>("{player}", target.getName()) + )); + return true; + } + + TeleportRequest request = tpaManager.createRequest( + player.getUniqueId(), + target.getUniqueId(), + TpaRequestType.TPA_HERE + ); + + if (request == null) { + sender.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.TPA_REQUEST_ALREADY_PENDING)); + return true; + } + + sender.sendMessage(getMessageManager().getAndFormatMsg( + PluginMessage.TPA_REQUEST_SENT, + new MessageManager.Replaceable<>("{player}", target.getName()) + )); + + if (floodgateEnabled && isBedrockPlayer(target)) { + bedrockFormHandler.sendRequestNotification(target, player, TpaRequestType.TPA_HERE); + } else { + target.sendMessage(getMessageManager().getAndFormatMsg( + PluginMessage.TPA_REQUEST_RECEIVED, + new MessageManager.Replaceable<>("{player}", player.getName()), + new MessageManager.Replaceable<>("{type}", "teleport you to them") + )); + target.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.TPA_REQUEST_INSTRUCTIONS, false)); + } + + TpaUtils.playSound(target, getPlugin().getConfig().getString("tpa.sounds.request", "ENTITY_EXPERIENCE_ORB_PICKUP"), getPlugin()); + + return true; + } + + @Override + public List tabComplete(CommandSender sender, Command command, ArgumentList args) { + if (args.getCurrentArgIndex() == 0) { + return Bukkit.getOnlinePlayers().stream() + .filter(p -> !p.equals(sender)) + .filter(p -> toggleManager.isEnabled(p.getUniqueId())) + .map(Player::getName) + .filter(name -> name.toLowerCase().startsWith(args.getCurrentArg().toLowerCase())) + .collect(Collectors.toList()); + } + return new ArrayList<>(); + } + + private boolean isBedrockPlayer(Player player) { + try { + return org.geysermc.floodgate.api.FloodgateApi.getInstance().isFloodgatePlayer(player.getUniqueId()); + } catch (Exception e) { + return false; + } + } +} diff --git a/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpaToggleCommand.java b/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpaToggleCommand.java new file mode 100644 index 0000000..1f7e2a7 --- /dev/null +++ b/src/main/java/com/zetaplugins/essentialz/commands/tpa/TpaToggleCommand.java @@ -0,0 +1,53 @@ +package com.zetaplugins.essentialz.commands.tpa; + +import com.zetaplugins.essentialz.EssentialZ; +import com.zetaplugins.essentialz.features.tpa.TpaToggleManager; +import com.zetaplugins.essentialz.util.PluginMessage; +import com.zetaplugins.essentialz.util.commands.EszCommand; +import com.zetaplugins.zetacore.annotations.AutoRegisterCommand; +import com.zetaplugins.zetacore.annotations.InjectManager; +import com.zetaplugins.zetacore.commands.ArgumentList; +import com.zetaplugins.zetacore.commands.exceptions.CommandSenderMustBePlayerException; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + +@AutoRegisterCommand( + commands = "tpatoggle", + description = "Toggle receiving teleport requests", + usage = "/tpatoggle", + permission = "essentialz.tpatoggle" +) +public class TpaToggleCommand extends EszCommand { + + @InjectManager + private TpaToggleManager toggleManager; + + public TpaToggleCommand(EssentialZ plugin) { + super(plugin); + } + + @Override + public boolean execute(CommandSender sender, Command command, String label, ArgumentList args) throws CommandSenderMustBePlayerException { + if (!(sender instanceof Player player)) { + throw new CommandSenderMustBePlayerException(); + } + + toggleManager.toggle(player.getUniqueId()); + + if (toggleManager.isEnabled(player.getUniqueId())) { + sender.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.TPA_TOGGLE_ENABLED)); + } else { + sender.sendMessage(getMessageManager().getAndFormatMsg(PluginMessage.TPA_TOGGLE_DISABLED)); + } + + return true; + } + + @Override + public List tabComplete(CommandSender sender, Command command, ArgumentList args) { + return List.of(); + } +} diff --git a/src/main/java/com/zetaplugins/essentialz/features/tpa/TeleportRequest.java b/src/main/java/com/zetaplugins/essentialz/features/tpa/TeleportRequest.java new file mode 100644 index 0000000..945a07c --- /dev/null +++ b/src/main/java/com/zetaplugins/essentialz/features/tpa/TeleportRequest.java @@ -0,0 +1,66 @@ +package com.zetaplugins.essentialz.features.tpa; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.UUID; + +/** + * Represents a teleport request between two players. + */ +public class TeleportRequest { + + private final UUID sender; + private final UUID target; + private final TpaRequestType type; + private final long creationTime; + private final long expiryTime; + + public TeleportRequest(UUID sender, UUID target, TpaRequestType type, int expirySeconds) { + this.sender = sender; + this.target = target; + this.type = type; + this.creationTime = System.currentTimeMillis(); + this.expiryTime = creationTime + (expirySeconds * 1000L); + } + + public UUID getSender() { + return sender; + } + + public UUID getTarget() { + return target; + } + + public TpaRequestType getType() { + return type; + } + + public long getCreationTime() { + return creationTime; + } + + public long getExpiryTime() { + return expiryTime; + } + + public boolean isExpired() { + return System.currentTimeMillis() > expiryTime; + } + + /** + * Gets the sender player if online. + * @return The sender player or null if offline + */ + public Player getSenderPlayer() { + return Bukkit.getPlayer(sender); + } + + /** + * Gets the target player if online. + * @return The target player or null if offline + */ + public Player getTargetPlayer() { + return Bukkit.getPlayer(target); + } +} diff --git a/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaBedrockFormHandler.java b/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaBedrockFormHandler.java new file mode 100644 index 0000000..0dbeece --- /dev/null +++ b/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaBedrockFormHandler.java @@ -0,0 +1,340 @@ +package com.zetaplugins.essentialz.features.tpa; + +import com.zetaplugins.essentialz.EssentialZ; +import com.zetaplugins.essentialz.util.MessageManager; +import com.zetaplugins.essentialz.util.PluginMessage; +import com.zetaplugins.zetacore.annotations.InjectManager; +import com.zetaplugins.zetacore.annotations.Manager; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.geysermc.cumulus.form.ModalForm; +import org.geysermc.cumulus.form.SimpleForm; +import org.geysermc.cumulus.util.FormImage; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.player.FloodgatePlayer; + +import java.util.ArrayList; +import java.util.List; + +/** + * Handles Bedrock forms for TPA functionality. + */ +@Manager +public class TpaBedrockFormHandler { + + private final EssentialZ plugin; + + @InjectManager + private TpaManager tpaManager; + + @InjectManager + private TpaToggleManager toggleManager; + + @InjectManager + private MessageManager messageManager; + + public TpaBedrockFormHandler(EssentialZ plugin) { + this.plugin = plugin; + } + + /** + * Sends a player selection form for TPA. + * @param sender The player sending the request + * @param type The type of TPA request + */ + public void sendPlayerSelectionForm(Player sender, TpaRequestType type) { + FloodgatePlayer floodgatePlayer = FloodgateApi.getInstance().getPlayer(sender.getUniqueId()); + if (floodgatePlayer == null) return; + + boolean showHeads = plugin.getConfig().getBoolean("tpa.bedrock.showPlayerHeads", true); + + SimpleForm.Builder builder = SimpleForm.builder() + .title(type == TpaRequestType.TPA ? "Teleport To Player" : "Teleport Player Here") + .content("Select a player:"); + + List availablePlayers = new ArrayList<>(); + + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.equals(sender)) continue; + if (!toggleManager.isEnabled(player.getUniqueId())) continue; + + availablePlayers.add(player); + + String buttonText = player.getName(); + if (showHeads) { + builder.button(buttonText, FormImage.Type.URL, "https://mc-heads.net/avatar/" + player.getName() + "/64"); + } else { + builder.button(buttonText); + } + } + + if (availablePlayers.isEmpty()) { + builder.content("No players available for teleportation."); + } + + builder.validResultHandler(response -> { + int index = response.clickedButtonId(); + if (index < 0 || index >= availablePlayers.size()) return; + + Player target = availablePlayers.get(index); + if (type == TpaRequestType.TPA) { + sendTpaConfirmationForm(sender, target); + } else { + sendTpaRequest(sender, target, TpaRequestType.TPA_HERE); + } + }); + + floodgatePlayer.sendForm(builder); + } + + private void sendTpaConfirmationForm(Player sender, Player target) { + FloodgatePlayer floodgatePlayer = FloodgateApi.getInstance().getPlayer(sender.getUniqueId()); + if (floodgatePlayer == null) return; + + ModalForm form = ModalForm.builder() + .title("Teleport Options") + .content("Choose teleport type for " + target.getName()) + .button1("Teleport to " + target.getName()) + .button2("Teleport " + target.getName() + " here") + .validResultHandler(response -> { + if (response.clickedButtonId() == 0) { + sendTpaRequest(sender, target, TpaRequestType.TPA); + } else { + sendTpaRequest(sender, target, TpaRequestType.TPA_HERE); + } + }) + .build(); + + floodgatePlayer.sendForm(form); + } + + /** + * Sends a request notification form to a Bedrock player. + * @param recipient The player receiving the request + * @param sender The player who sent the request + * @param type The type of request + */ + public void sendRequestNotification(Player recipient, Player sender, TpaRequestType type) { + FloodgatePlayer floodgatePlayer = FloodgateApi.getInstance().getPlayer(recipient.getUniqueId()); + if (floodgatePlayer == null) return; + + String content = type == TpaRequestType.TPA + ? sender.getName() + " wants to teleport to you." + : sender.getName() + " wants to teleport you to them."; + + long startTime = System.currentTimeMillis(); + + ModalForm.Builder builder = ModalForm.builder() + .title("Teleport Request") + .content(content) + .button1("Accept") + .button2("Deny"); + + builder.validResultHandler(response -> { + TeleportRequest request = tpaManager.getLatestRequest(recipient.getUniqueId()); + if (request == null || !request.getSender().equals(sender.getUniqueId())) { + recipient.sendMessage(messageManager.getAndFormatMsg(PluginMessage.TPA_NO_PENDING_REQUESTS)); + return; + } + + if (response.clickedButtonId() == 0) { + acceptRequest(recipient, request); + } else { + denyRequest(recipient, request); + } + + tpaManager.removeRequest(request); + }); + + builder.closedResultHandler(() -> retryRequestForm(recipient, sender, type, startTime)); + builder.invalidResultHandler(() -> retryRequestForm(recipient, sender, type, startTime)); + + floodgatePlayer.sendForm(builder.build()); + } + + private void retryRequestForm(Player recipient, Player sender, TpaRequestType type, long startTime) { + if ((System.currentTimeMillis() - startTime) < 30000) { + Bukkit.getScheduler().runTaskLater(plugin, () -> { + if (recipient.isOnline() && sender.isOnline()) { + sendRequestNotification(recipient, sender, type); + } + }, 20L); + } + } + + /** + * Sends a form showing all pending requests. + * @param player The player viewing requests + */ + public void sendPendingRequestsForm(Player player) { + FloodgatePlayer floodgatePlayer = FloodgateApi.getInstance().getPlayer(player.getUniqueId()); + if (floodgatePlayer == null) return; + + List requests = tpaManager.getIncomingRequests(player.getUniqueId()); + + if (requests.isEmpty()) { + player.sendMessage(messageManager.getAndFormatMsg(PluginMessage.TPA_NO_PENDING_REQUESTS)); + return; + } + + SimpleForm.Builder builder = SimpleForm.builder() + .title("Pending Teleport Requests") + .content("Select a request to manage:"); + + for (TeleportRequest request : requests) { + Player sender = Bukkit.getPlayer(request.getSender()); + if (sender != null) { + String typeStr = request.getType() == TpaRequestType.TPA ? "to you" : "you to them"; + builder.button(sender.getName() + " - Teleport " + typeStr); + } + } + + builder.validResultHandler(response -> { + int index = response.clickedButtonId(); + if (index < 0 || index >= requests.size()) return; + + TeleportRequest request = requests.get(index); + sendRequestManagementForm(player, request); + }); + + floodgatePlayer.sendForm(builder); + } + + /** + * Sends a form for managing a specific request. + * @param player The player managing the request + * @param request The request to manage + */ + public void sendRequestManagementForm(Player player, TeleportRequest request) { + FloodgatePlayer floodgatePlayer = FloodgateApi.getInstance().getPlayer(player.getUniqueId()); + if (floodgatePlayer == null) return; + + Player sender = Bukkit.getPlayer(request.getSender()); + if (sender == null) { + player.sendMessage(messageManager.getAndFormatMsg(PluginMessage.PLAYER_NOT_FOUND)); + return; + } + + String content = "Request from " + sender.getName() + "\n" + + "Type: " + (request.getType() == TpaRequestType.TPA + ? "Teleport to you" : "Teleport you to them"); + + long startTime = System.currentTimeMillis(); + + ModalForm.Builder builder = ModalForm.builder() + .title("Manage Request") + .content(content) + .button1("Accept") + .button2("Deny"); + + builder.validResultHandler(response -> { + if (response.clickedButtonId() == 0) { + acceptRequest(player, request); + } else { + denyRequest(player, request); + } + tpaManager.removeRequest(request); + }); + + builder.closedResultHandler(() -> retryManagementForm(player, request, startTime)); + builder.invalidResultHandler(() -> retryManagementForm(player, request, startTime)); + + floodgatePlayer.sendForm(builder.build()); + } + + private void retryManagementForm(Player player, TeleportRequest request, long startTime) { + if ((System.currentTimeMillis() - startTime) < 30000) { + Bukkit.getScheduler().runTaskLater(plugin, () -> { + if (player.isOnline() && !request.isExpired()) { + sendRequestManagementForm(player, request); + } + }, 20L); + } + } + + private void sendTpaRequest(Player sender, Player target, TpaRequestType type) { + if (!toggleManager.isEnabled(target.getUniqueId())) { + sender.sendMessage(messageManager.getAndFormatMsg( + PluginMessage.TPA_DISABLED_TARGET, + new MessageManager.Replaceable<>("{player}", target.getName()) + )); + return; + } + + TeleportRequest request = tpaManager.createRequest( + sender.getUniqueId(), + target.getUniqueId(), + type + ); + + if (request == null) { + sender.sendMessage(messageManager.getAndFormatMsg(PluginMessage.TPA_REQUEST_ALREADY_PENDING)); + return; + } + + sender.sendMessage(messageManager.getAndFormatMsg( + PluginMessage.TPA_REQUEST_SENT, + new MessageManager.Replaceable<>("{player}", target.getName()) + )); + + if (FloodgateApi.getInstance().isFloodgatePlayer(target.getUniqueId())) { + sendRequestNotification(target, sender, type); + } else { + String typeDescription = type == TpaRequestType.TPA ? "teleport to you" : "teleport you to them"; + target.sendMessage(messageManager.getAndFormatMsg( + PluginMessage.TPA_REQUEST_RECEIVED, + new MessageManager.Replaceable<>("{player}", sender.getName()), + new MessageManager.Replaceable<>("{type}", typeDescription) + )); + target.sendMessage(messageManager.getAndFormatMsg(PluginMessage.TPA_REQUEST_INSTRUCTIONS, false)); + } + + TpaUtils.playSound(target, plugin.getConfig().getString("tpa.sounds.request", "ENTITY_EXPERIENCE_ORB_PICKUP"), plugin); + } + + private void acceptRequest(Player acceptor, TeleportRequest request) { + Player senderPlayer = request.getSenderPlayer(); + Player targetPlayer = request.getTargetPlayer(); + + if (senderPlayer == null || !senderPlayer.isOnline()) { + acceptor.sendMessage(messageManager.getAndFormatMsg(PluginMessage.PLAYER_NOT_FOUND)); + return; + } + + if (targetPlayer == null || !targetPlayer.isOnline()) { + return; + } + + if (request.getType() == TpaRequestType.TPA) { + senderPlayer.teleport(targetPlayer.getLocation()); + } else { + targetPlayer.teleport(senderPlayer.getLocation()); + } + + senderPlayer.sendMessage(messageManager.getAndFormatMsg( + PluginMessage.TPA_ACCEPTED_SENDER, + new MessageManager.Replaceable<>("{player}", targetPlayer.getName()) + )); + + targetPlayer.sendMessage(messageManager.getAndFormatMsg( + PluginMessage.TPA_ACCEPTED_TARGET, + new MessageManager.Replaceable<>("{player}", senderPlayer.getName()) + )); + + TpaUtils.playSound(acceptor, plugin.getConfig().getString("tpa.sounds.accept", "ENTITY_PLAYER_LEVELUP"), plugin); + } + + private void denyRequest(Player denier, TeleportRequest request) { + Player senderPlayer = request.getSenderPlayer(); + + if (senderPlayer != null && senderPlayer.isOnline()) { + senderPlayer.sendMessage(messageManager.getAndFormatMsg( + PluginMessage.TPA_DENIED_SENDER, + new MessageManager.Replaceable<>("{player}", denier.getName()) + )); + } + + denier.sendMessage(messageManager.getAndFormatMsg(PluginMessage.TPA_DENIED_TARGET)); + TpaUtils.playSound(denier, plugin.getConfig().getString("tpa.sounds.deny", "ENTITY_VILLAGER_NO"), plugin); + } +} diff --git a/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaManager.java b/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaManager.java new file mode 100644 index 0000000..2b9d509 --- /dev/null +++ b/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaManager.java @@ -0,0 +1,171 @@ +package com.zetaplugins.essentialz.features.tpa; + +import com.zetaplugins.essentialz.EssentialZ; +import com.zetaplugins.zetacore.annotations.Manager; +import com.zetaplugins.zetacore.annotations.PostManagerConstruct; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages TPA requests between players. + */ +@Manager +public class TpaManager { + + private final EssentialZ plugin; + private final Map> incomingRequests; + private final Map> outgoingRequests; + + private int requestExpiryTime = 120; + + public TpaManager(EssentialZ plugin) { + this.plugin = plugin; + this.incomingRequests = new ConcurrentHashMap<>(); + this.outgoingRequests = new ConcurrentHashMap<>(); + } + + @PostManagerConstruct + public void init() { + loadConfig(); + startExpiryTask(); + } + + private void loadConfig() { + requestExpiryTime = plugin.getConfig().getInt("tpa.requestExpiryTime", 120); + } + + private void startExpiryTask() { + new BukkitRunnable() { + @Override + public void run() { + cleanExpiredRequests(); + } + }.runTaskTimer(plugin, 20L, 20L); + } + + /** + * Creates a new teleport request. + * @param sender The UUID of the player sending the request + * @param target The UUID of the player receiving the request + * @param type The type of request (TPA or TPA_HERE) + * @return The created request, or null if a request already exists + */ + public TeleportRequest createRequest(UUID sender, UUID target, TpaRequestType type) { + if (hasRequest(sender, target)) { + return null; + } + + TeleportRequest request = new TeleportRequest(sender, target, type, requestExpiryTime); + + incomingRequests.computeIfAbsent(target, k -> new ArrayList<>()).add(request); + outgoingRequests.computeIfAbsent(sender, k -> new ArrayList<>()).add(request); + + return request; + } + + /** + * Checks if a request already exists between two players. + * @param sender The sender's UUID + * @param target The target's UUID + * @return true if a non-expired request exists + */ + public boolean hasRequest(UUID sender, UUID target) { + List requests = incomingRequests.get(target); + if (requests == null) return false; + + return requests.stream() + .anyMatch(r -> r.getSender().equals(sender) && !r.isExpired()); + } + + /** + * Gets the latest non-expired request for a player. + * @param target The target player's UUID + * @return The latest request, or null if none exists + */ + public TeleportRequest getLatestRequest(UUID target) { + List requests = incomingRequests.get(target); + if (requests == null || requests.isEmpty()) return null; + + for (int i = requests.size() - 1; i >= 0; i--) { + TeleportRequest request = requests.get(i); + if (!request.isExpired()) { + return request; + } + } + return null; + } + + /** + * Gets all incoming non-expired requests for a player. + * @param target The target player's UUID + * @return List of incoming requests + */ + public List getIncomingRequests(UUID target) { + List requests = incomingRequests.get(target); + if (requests == null) return new ArrayList<>(); + + requests.removeIf(TeleportRequest::isExpired); + return new ArrayList<>(requests); + } + + /** + * Gets all outgoing non-expired requests for a player. + * @param sender The sender player's UUID + * @return List of outgoing requests + */ + public List getOutgoingRequests(UUID sender) { + List requests = outgoingRequests.get(sender); + if (requests == null) return new ArrayList<>(); + + requests.removeIf(TeleportRequest::isExpired); + return new ArrayList<>(requests); + } + + /** + * Removes a specific request. + * @param request The request to remove + */ + public void removeRequest(TeleportRequest request) { + List incoming = incomingRequests.get(request.getTarget()); + if (incoming != null) { + incoming.remove(request); + if (incoming.isEmpty()) { + incomingRequests.remove(request.getTarget()); + } + } + + List outgoing = outgoingRequests.get(request.getSender()); + if (outgoing != null) { + outgoing.remove(request); + if (outgoing.isEmpty()) { + outgoingRequests.remove(request.getSender()); + } + } + } + + /** + * Cleans up all expired requests and notifies players. + */ + public void cleanExpiredRequests() { + incomingRequests.values().forEach(list -> list.removeIf(TeleportRequest::isExpired)); + outgoingRequests.values().forEach(list -> list.removeIf(TeleportRequest::isExpired)); + + incomingRequests.entrySet().removeIf(entry -> entry.getValue().isEmpty()); + outgoingRequests.entrySet().removeIf(entry -> entry.getValue().isEmpty()); + } + + /** + * Clears all requests for a player (called on quit). + * @param player The player's UUID + */ + public void clearPlayerRequests(UUID player) { + incomingRequests.remove(player); + outgoingRequests.remove(player); + } + + public int getRequestExpiryTime() { + return requestExpiryTime; + } +} diff --git a/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaRequestType.java b/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaRequestType.java new file mode 100644 index 0000000..23106cb --- /dev/null +++ b/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaRequestType.java @@ -0,0 +1,19 @@ +package com.zetaplugins.essentialz.features.tpa; + +/** + * Represents the type of a TPA request. + */ +public enum TpaRequestType { + TPA("tpa"), + TPA_HERE("tpahere"); + + private final String name; + + TpaRequestType(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaToggleManager.java b/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaToggleManager.java new file mode 100644 index 0000000..dc8ebe7 --- /dev/null +++ b/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaToggleManager.java @@ -0,0 +1,109 @@ +package com.zetaplugins.essentialz.features.tpa; + +import com.zetaplugins.essentialz.EssentialZ; +import com.zetaplugins.zetacore.annotations.Manager; +import com.zetaplugins.zetacore.annotations.PostManagerConstruct; + +import java.io.*; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * Manages TPA toggle state for players. + */ +@Manager +public class TpaToggleManager { + + private final EssentialZ plugin; + private final Set disabledPlayers; + private final File toggleFile; + + public TpaToggleManager(EssentialZ plugin) { + this.plugin = plugin; + this.disabledPlayers = new HashSet<>(); + this.toggleFile = new File(plugin.getDataFolder(), "tpa-toggles.dat"); + } + + @PostManagerConstruct + public void init() { + loadToggles(); + } + + /** + * Checks if TPA is enabled for a player. + * @param player The player's UUID + * @return true if TPA is enabled for the player + */ + public boolean isEnabled(UUID player) { + return !disabledPlayers.contains(player); + } + + /** + * Toggles TPA state for a player. + * @param player The player's UUID + */ + public void toggle(UUID player) { + if (disabledPlayers.contains(player)) { + disabledPlayers.remove(player); + } else { + disabledPlayers.add(player); + } + saveToggles(); + } + + /** + * Sets the TPA state for a player. + * @param player The player's UUID + * @param enabled Whether TPA should be enabled + */ + public void setEnabled(UUID player, boolean enabled) { + if (enabled) { + disabledPlayers.remove(player); + } else { + disabledPlayers.add(player); + } + saveToggles(); + } + + /** + * Loads toggle states from file. + */ + public void loadToggles() { + if (!toggleFile.exists()) return; + + try (BufferedReader reader = new BufferedReader(new FileReader(toggleFile))) { + String line; + while ((line = reader.readLine()) != null) { + try { + UUID uuid = UUID.fromString(line.trim()); + disabledPlayers.add(uuid); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid UUID in tpa-toggles.dat: " + line); + } + } + } catch (IOException e) { + plugin.getLogger().severe("Failed to load TPA toggles: " + e.getMessage()); + } + } + + /** + * Saves toggle states to file. + */ + public void saveToggles() { + try { + if (!toggleFile.exists()) { + toggleFile.getParentFile().mkdirs(); + toggleFile.createNewFile(); + } + + try (PrintWriter writer = new PrintWriter(new FileWriter(toggleFile))) { + for (UUID uuid : disabledPlayers) { + writer.println(uuid.toString()); + } + } + } catch (IOException e) { + plugin.getLogger().severe("Failed to save TPA toggles: " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaUtils.java b/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaUtils.java new file mode 100644 index 0000000..61009ee --- /dev/null +++ b/src/main/java/com/zetaplugins/essentialz/features/tpa/TpaUtils.java @@ -0,0 +1,77 @@ +package com.zetaplugins.essentialz.features.tpa; + +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility methods for TPA functionality. + */ +public final class TpaUtils { + + private TpaUtils() {} + + /** + * Finds a player by name with partial matching support. + * @param name The name to search for + * @return The matched player or null if no unique match found + */ + public static Player findPlayer(String name) { + // Try exact match first + Player exact = Bukkit.getPlayerExact(name); + if (exact != null) return exact; + + // Try partial match + List matches = new ArrayList<>(); + String lowerName = name.toLowerCase(); + + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.getName().toLowerCase().startsWith(lowerName)) { + matches.add(player); + } + } + + // Return if single match found + if (matches.size() == 1) { + return matches.get(0); + } + + // Try contains match if no startsWith matches + if (matches.isEmpty()) { + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.getName().toLowerCase().contains(lowerName)) { + matches.add(player); + } + } + + if (matches.size() == 1) { + return matches.get(0); + } + } + + return null; + } + + /** + * Plays a sound to a player if sounds are enabled. + * @param player The player to play the sound to + * @param soundName The name of the sound + * @param plugin The plugin instance + */ + public static void playSound(Player player, String soundName, JavaPlugin plugin) { + if (player == null || soundName == null) return; + + if (!plugin.getConfig().getBoolean("tpa.sounds.enabled", true)) return; + + try { + Sound sound = Sound.valueOf(soundName); + player.playSound(player.getLocation(), sound, 1.0f, 1.0f); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid TPA sound: " + soundName); + } + } +} diff --git a/src/main/java/com/zetaplugins/essentialz/listeners/TpaPlayerQuitListener.java b/src/main/java/com/zetaplugins/essentialz/listeners/TpaPlayerQuitListener.java new file mode 100644 index 0000000..8084378 --- /dev/null +++ b/src/main/java/com/zetaplugins/essentialz/listeners/TpaPlayerQuitListener.java @@ -0,0 +1,23 @@ +package com.zetaplugins.essentialz.listeners; + +import com.zetaplugins.essentialz.features.tpa.TpaManager; +import com.zetaplugins.zetacore.annotations.AutoRegisterListener; +import com.zetaplugins.zetacore.annotations.InjectManager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +/** + * Listener for cleaning up TPA requests when players quit. + */ +@AutoRegisterListener +public class TpaPlayerQuitListener implements Listener { + + @InjectManager + private TpaManager tpaManager; + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + tpaManager.clearPlayerRequests(event.getPlayer().getUniqueId()); + } +} diff --git a/src/main/java/com/zetaplugins/essentialz/util/PluginMessage.java b/src/main/java/com/zetaplugins/essentialz/util/PluginMessage.java index 5e65c2e..01d5333 100644 --- a/src/main/java/com/zetaplugins/essentialz/util/PluginMessage.java +++ b/src/main/java/com/zetaplugins/essentialz/util/PluginMessage.java @@ -109,10 +109,24 @@ public enum PluginMessage { GLOW_ENABLED("glowEnabled", "&7Glow effect enabled for {ac}{player}&7.", MessageStyle.FUN), GLOW_DISABLED("glowDisabled", "&7Glow effect disabled for {ac}{player}&7.", MessageStyle.FUN), DEATH_MESSAGE("deathMessage", "&8[&c☠&8] &7{message}", MessageStyle.NONE), - /* - * Still missing: - * - communication - */ + + // TPA Messages + TPA_REQUEST_SENT("tpaRequestSent", "&7Teleport request sent to {ac}{player}&7.", MessageStyle.MOVEMENT), + TPA_REQUEST_RECEIVED("tpaRequestReceived", "{ac}{player}&7 wants to {type}.", MessageStyle.MOVEMENT), + TPA_REQUEST_INSTRUCTIONS("tpaRequestInstructions", "&7Type {ac}/tpaccept &7to accept or {ac}/tpdeny &7to deny.", MessageStyle.MOVEMENT), + TPA_ACCEPTED_SENDER("tpaAcceptedSender", "{ac}{player}&7 accepted your teleport request.", MessageStyle.SUCCESS), + TPA_ACCEPTED_TARGET("tpaAcceptedTarget", "&7You accepted {ac}{player}&7's teleport request.", MessageStyle.SUCCESS), + TPA_DENIED_SENDER("tpaDeniedSender", "{ac}{player}&7 denied your teleport request.", MessageStyle.ERROR), + TPA_DENIED_TARGET("tpaDeniedTarget", "&7You denied the teleport request.", MessageStyle.MOVEMENT), + TPA_REQUEST_ALREADY_PENDING("tpaRequestAlreadyPending", "{ac}You already have a pending request to this player.", MessageStyle.ERROR), + TPA_NO_PENDING_REQUESTS("tpaNoPendingRequests", "{ac}You have no pending teleport requests.", MessageStyle.ERROR), + TPA_NO_OUTGOING_REQUESTS("tpaNoOutgoingRequests", "{ac}You have no outgoing teleport requests.", MessageStyle.ERROR), + TPA_REQUESTS_CANCELLED("tpaRequestsCancelled", "&7Cancelled {ac}{count}&7 outgoing request(s).", MessageStyle.MOVEMENT), + TPA_CANNOT_SELF("tpaCannotSelf", "{ac}You cannot teleport to yourself.", MessageStyle.ERROR), + TPA_DISABLED_TARGET("tpaDisabledTarget", "{ac}{player} has teleport requests disabled.", MessageStyle.ERROR), + TPA_TOGGLE_ENABLED("tpaToggleEnabled", "&7You have {ac}enabled&7 teleport requests.", MessageStyle.SUCCESS), + TPA_TOGGLE_DISABLED("tpaToggleDisabled", "&7You have {ac}disabled&7 teleport requests.", MessageStyle.WARNING), + TPA_REQUEST_EXPIRED("tpaRequestExpired", "&7Your teleport request to {ac}{player}&7 has expired.", MessageStyle.WARNING), ; private final String key; diff --git a/src/main/java/com/zetaplugins/essentialz/util/permissions/Permission.java b/src/main/java/com/zetaplugins/essentialz/util/permissions/Permission.java index 92a2db2..f3b8f87 100644 --- a/src/main/java/com/zetaplugins/essentialz/util/permissions/Permission.java +++ b/src/main/java/com/zetaplugins/essentialz/util/permissions/Permission.java @@ -71,6 +71,12 @@ public enum Permission implements PermissionNode { TIME("time", PermissionDefault.OP, "Allows the user to change the time in their world"), LIGHTNING("lightning", PermissionDefault.OP, "Allows the user to strike lightning at a player's location"), GLOW("glow", PermissionDefault.OP, "Allows the user to make themselves or another player glow"), + TPA("tpa", PermissionDefault.TRUE, "Allows the user to request to teleport to another player"), + TPAHERE("tpahere", PermissionDefault.TRUE, "Allows the user to request another player to teleport to them"), + TPACCEPT("tpaccept", PermissionDefault.TRUE, "Allows the user to accept teleport requests"), + TPDENY("tpdeny", PermissionDefault.TRUE, "Allows the user to deny teleport requests"), + TPACANCEL("tpacancel", PermissionDefault.TRUE, "Allows the user to cancel outgoing teleport requests"), + TPATOGGLE("tpatoggle", PermissionDefault.TRUE, "Allows the user to toggle receiving teleport requests"), ; private final String node; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 4b4ed71..c34c908 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -67,4 +67,27 @@ styles: prefix: "&8[<#FF4081>🎉&8] " # This is the server spawn location. Use the format: world,x,y,z,yaw,pitch. You can also use the /setspawn command to set it. -spawnLocation: "" \ No newline at end of file +spawnLocation: "" + +# TPA (Teleport Ask) settings +tpa: + # How long (in seconds) before a teleport request expires + requestExpiryTime: 120 + + # Sound settings for TPA events + sounds: + # Whether to play sounds for TPA events + enabled: true + # Sound played when a request is received + request: "ENTITY_EXPERIENCE_ORB_PICKUP" + # Sound played when a request is accepted + accept: "ENTITY_PLAYER_LEVELUP" + # Sound played when a request is denied + deny: "ENTITY_VILLAGER_NO" + + # Bedrock Edition settings (requires Floodgate) + bedrock: + # Whether to use GUI forms for Bedrock players + formsEnabled: true + # Whether to show player heads in selection forms + showPlayerHeads: true \ No newline at end of file diff --git a/src/main/resources/lang/en-US.yml b/src/main/resources/lang/en-US.yml index 635678c..6234144 100644 --- a/src/main/resources/lang/en-US.yml +++ b/src/main/resources/lang/en-US.yml @@ -146,4 +146,22 @@ invalidLightningAmount: "{ac}The amount of lightning strikes must be between 1 a lightningStrikes: "&7Struck {ac}{player} &7with lightning {ac}{amount} &7time(s)." glowEnabled: "&7Glow effect enabled for {ac}{player}&7." glowDisabled: "&7Glow effect disabled for {ac}{player}&7." -deathMessage: "&8[&c☠&8] &7{message}" \ No newline at end of file +deathMessage: "&8[&c☠&8] &7{message}" + +# TPA Messages +tpaRequestSent: "&7Teleport request sent to {ac}{player}&7." +tpaRequestReceived: "{ac}{player}&7 wants to {type}." +tpaRequestInstructions: "&7Type {ac}/tpaccept &7to accept or {ac}/tpdeny &7to deny." +tpaAcceptedSender: "{ac}{player}&7 accepted your teleport request." +tpaAcceptedTarget: "&7You accepted {ac}{player}&7's teleport request." +tpaDeniedSender: "{ac}{player}&7 denied your teleport request." +tpaDeniedTarget: "&7You denied the teleport request." +tpaRequestAlreadyPending: "{ac}You already have a pending request to this player." +tpaNoPendingRequests: "{ac}You have no pending teleport requests." +tpaNoOutgoingRequests: "{ac}You have no outgoing teleport requests." +tpaRequestsCancelled: "&7Cancelled {ac}{count}&7 outgoing request(s)." +tpaCannotSelf: "{ac}You cannot teleport to yourself." +tpaDisabledTarget: "{ac}{player} has teleport requests disabled." +tpaToggleEnabled: "&7You have {ac}enabled&7 teleport requests." +tpaToggleDisabled: "&7You have {ac}disabled&7 teleport requests." +tpaRequestExpired: "&7Your teleport request to {ac}{player}&7 has expired." \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 9500bfa..e201623 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -10,3 +10,4 @@ website: https://strassburger.org softdepend: - PlaceholderAPI - Vault + - floodgate diff --git a/src/main/resources/storage.yml b/src/main/resources/storage.yml index a1f7d1b..232cd0c 100644 --- a/src/main/resources/storage.yml +++ b/src/main/resources/storage.yml @@ -1,7 +1,7 @@ # === Storage === # The type of storage to use. You have the following options: -# "SQLite", "MySQL", "MariaDB" +# "SQLite", "MySQL" type: "SQLite" # This section is only relevant if you use a MySQL database