diff --git a/pom.xml b/pom.xml index 03c42ce..d14e25d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ 4.0.0 com.modnmetl virtualrealty - 2.8.4 + 2.8.5 jar A plot creation and management plugin for Minecraft @@ -134,6 +134,7 @@ bluecolored https://repo.bluecolored.de/releases + @@ -221,11 +222,11 @@ 6.3.0 compile - - - - - - + + maven.modrinth + pl3xmap + 1.20.4-492 + provided + diff --git a/src/main/java/com/modnmetl/virtualrealty/VirtualRealty.java b/src/main/java/com/modnmetl/virtualrealty/VirtualRealty.java index 512cb04..655cb31 100644 --- a/src/main/java/com/modnmetl/virtualrealty/VirtualRealty.java +++ b/src/main/java/com/modnmetl/virtualrealty/VirtualRealty.java @@ -46,6 +46,7 @@ import com.modnmetl.virtualrealty.manager.DynmapManager; import com.modnmetl.virtualrealty.manager.MetricsManager; import com.modnmetl.virtualrealty.manager.PlotManager; +import com.modnmetl.virtualrealty.manager.Pl3xMapManager; import com.modnmetl.virtualrealty.model.other.ServerVersion; import com.modnmetl.virtualrealty.model.plot.Plot; import com.modnmetl.virtualrealty.model.plot.PlotSize; @@ -64,6 +65,7 @@ public final class VirtualRealty extends JavaPlugin { // CORE public List jarFiles = new ArrayList<>(); public DynmapManager dynmapManager; + public Pl3xMapManager pl3xMapManager; public Locale locale; @Getter private static VirtualRealty instance; @@ -135,6 +137,7 @@ public void onEnable() { } loadPlotsData(); loadDynMapHook(); + loadPl3xMapHook(); registerCommands(); loadCommandsConfiguration(); updateCommandsConfig(); @@ -296,6 +299,13 @@ public void loadDynMapHook() { } } + public void loadPl3xMapHook() { + if (getPluginConfiguration().pl3xmapMarkers) { + pl3xMapManager = new Pl3xMapManager(this); + pl3xMapManager.registerPl3xMap(); + } + } + public void loadMetrics() { metricsManager = new MetricsManager(this, 14066); metricsManager.registerMetrics(); @@ -454,6 +464,10 @@ public static DynmapManager getDynmapManager() { return getInstance().dynmapManager; } + public static Pl3xMapManager getPl3xMapManager() { + return getInstance().pl3xMapManager; + } + public static Database getDatabase() { return Database.getInstance(); } diff --git a/src/main/java/com/modnmetl/virtualrealty/configs/PluginConfiguration.java b/src/main/java/com/modnmetl/virtualrealty/configs/PluginConfiguration.java index db700a0..f85f539 100644 --- a/src/main/java/com/modnmetl/virtualrealty/configs/PluginConfiguration.java +++ b/src/main/java/com/modnmetl/virtualrealty/configs/PluginConfiguration.java @@ -26,7 +26,7 @@ public class PluginConfiguration extends OkaeriConfig { public String initServerVersion = ServerVersion.MODERN.toString(); @Comment("Debug mode (Displays more detailed info about plugin executions)") - public boolean debugMode = false; + public boolean debugMode = true; @Comment("Local loader mode (for devs)") public boolean loaderDebugMode = false; @@ -102,6 +102,16 @@ public GameMode getDefaultPlotGamemode() { @CustomKey("dynmap-markers") public MarkerColor dynmapMarkersColor = new MarkerColor(); + @Comment("Enables pl3xmap plots highlighting") + @CustomKey("enable-pl3xmap-markers") + public boolean pl3xmapMarkers = true; + + @Comment("Choose which type of plots should be highlighted on Pl3xMap page | Choose from: { ALL, AVAILABLE, OWNED }") + public HighlightType pl3xmapType = HighlightType.ALL; + + @CustomKey("pl3xmap-markers") + public Pl3xMapMarkerColor pl3xmapMarkersColor = new Pl3xMapMarkerColor(); + @Names(strategy = NameStrategy.IDENTITY) public static class MarkerColor extends OkaeriConfig { @@ -134,6 +144,30 @@ public int getHexColor() { } + @Names(strategy = NameStrategy.IDENTITY) + public static class Pl3xMapMarkerColor extends OkaeriConfig { + + public Pl3xMapAvailable available = new Pl3xMapAvailable(); + public Pl3xMapOwned owned = new Pl3xMapOwned(); + + @Names(strategy = NameStrategy.IDENTITY) + public static class Pl3xMapAvailable extends OkaeriConfig { + + public String color = "#80eb34"; + public double opacity = 0.3; + + } + + @Names(strategy = NameStrategy.IDENTITY) + public static class Pl3xMapOwned extends OkaeriConfig { + + public String color = "#ffbf00"; + public double opacity = 0.45; + + } + + } + @Comment("Enables plots enter/leave sounds") @CustomKey("plot-sounds") public boolean plotSound = true; diff --git a/src/main/java/com/modnmetl/virtualrealty/listener/premium/PanelListener.java b/src/main/java/com/modnmetl/virtualrealty/listener/premium/PanelListener.java index 78c781e..3aa8030 100644 --- a/src/main/java/com/modnmetl/virtualrealty/listener/premium/PanelListener.java +++ b/src/main/java/com/modnmetl/virtualrealty/listener/premium/PanelListener.java @@ -227,7 +227,7 @@ public void run() { OfflinePlayer offlinePlayer = PanelUtil.SELECTED_MEMBER.get(player.getUniqueId()); PlotMember plotMember = plot.getMember(offlinePlayer.getUniqueId()); ManagementPermission managementPermission = ManagementPermission.getPermission(e.getSlot() - 12); - if (plot.getPlotOwner().getUniqueId() != player.getUniqueId()) { + if (!plot.getPlotOwner().getUniqueId().equals(player.getUniqueId())) { ChatMessage.of(VirtualRealty.getMessages().noAccess).sendWithPrefix(player); return; } diff --git a/src/main/java/com/modnmetl/virtualrealty/listener/protection/PlotProtectionListener.java b/src/main/java/com/modnmetl/virtualrealty/listener/protection/PlotProtectionListener.java index 023835c..a7fcb7b 100644 --- a/src/main/java/com/modnmetl/virtualrealty/listener/protection/PlotProtectionListener.java +++ b/src/main/java/com/modnmetl/virtualrealty/listener/protection/PlotProtectionListener.java @@ -64,11 +64,18 @@ public class PlotProtectionListener extends VirtualListener { public static final LinkedList SWITCHES = new LinkedList<>(); public static final LinkedList STORAGES = new LinkedList<>(); - public boolean isDoor(Material type) { - if (type.name().endsWith("DOOR")) - return true; - return false; - } + public boolean isDoor(Material type) { + // Only treat actual doors as "doors" for permission purposes + // Dripleaf is now handled separately to allow tilting without door permissions + if (type.name().endsWith("DOOR")) + return true; + return false; + } + + private boolean isDripleaf(Material type) { + String name = type.name(); + return name.contains("DRIPLEAF"); + } public boolean isInteractable(Material type) { if (INTERACT.contains(type)) { @@ -192,9 +199,10 @@ public void onBlockInteract(PlayerInteractEvent e) { if (e.getHand() == EquipmentSlot.OFF_HAND) return; - if (!e.getClickedBlock().getType().isInteractable() - && !(e.getClickedBlock().getType().name().endsWith("PRESSURE_PLATE"))) - return; + if (!e.getClickedBlock().getType().isInteractable() + && !(e.getClickedBlock().getType().name().endsWith("PRESSURE_PLATE")) + && !isDripleaf(e.getClickedBlock().getType())) + return; Plot plot = PlotManager.getInstance().getPlot(e.getClickedBlock().getLocation()); if (plot == null) @@ -234,7 +242,15 @@ public void onBlockInteract(PlayerInteractEvent e) { return; } } + // Allow dripleaf interactions (tilting) without permission checks for members + if (isDripleaf(e.getClickedBlock().getType())) { + return; // Allow all dripleaf interactions for members + } } else { + // Allow dripleaf interactions (tilting) without permission checks for non-members too + if (isDripleaf(e.getClickedBlock().getType())) { + return; // Allow all dripleaf interactions for non-members + } if (isModernSwitch || isLegacySwitch || e.getClickedBlock().getType().name().endsWith("PRESSURE_PLATE")) { if (!plot.hasPermission(RegionPermission.SWITCH)) { e.setCancelled(true); @@ -484,6 +500,9 @@ public void onBlockBreak(BlockBreakEvent e) { return; if (hasPermission(player, PLOT_BUILD)) return; + + // For dripleaf blocks, always require BREAK permission for actual breaking + // State changes (tilting) are handled in the EntityChangeBlockEvent handler if (plot.hasMembershipAccess(player.getUniqueId())) { PlotMember plotMember = plot.getMember(player.getUniqueId()); if (plot.isOwnershipExpired()) { @@ -505,6 +524,23 @@ public void onBlockBreak(BlockBreakEvent e) { } } + @EventHandler(priority = EventPriority.LOW) + public void onDripleafStateChange(EntityChangeBlockEvent e) { + if (e.isCancelled()) + return; + + // Only handle dripleaf tilting (state changes, not breaking) + if (!isDripleaf(e.getBlock().getType())) + return; + + // If the dripleaf is being broken (set to AIR), let the BlockBreakEvent handle it + if (e.getTo() == Material.AIR) + return; + + // This is a state change (tilting), allow it without requiring BREAK permission + // No permission check needed for dripleaf tilting + } + @EventHandler(priority = EventPriority.LOW) public void onPistonRetract(BlockPistonRetractEvent e) { if (e.isCancelled()) @@ -957,13 +993,24 @@ public void onFireBurn(BlockBurnEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onEndermanChangeBlock(EntityChangeBlockEvent e) { + Plot plot = PlotManager.getInstance().getPlot(e.getBlock().getLocation()); + if (plot == null) { + return; + } + + // Protect against Enderman block changes if (e.getEntityType() == EntityType.ENDERMAN) { - Plot plot = PlotManager.getInstance().getPlot(e.getBlock().getLocation()); - if (plot == null) { - return; - } else { + e.setCancelled(true); + return; + } + + // Protect dripleaves from breaking while allowing tilting/interaction + if (isDripleaf(e.getBlock().getType())) { + // Check if this is a dripleaf breaking (changing to air or different block) + if (e.getTo() == Material.AIR || !isDripleaf(e.getTo())) { e.setCancelled(true); } + // Allow dripleaf state changes (tilting) by not cancelling other dripleaf-to-dripleaf changes } } diff --git a/src/main/java/com/modnmetl/virtualrealty/listener/protection/WorldProtectionListener.java b/src/main/java/com/modnmetl/virtualrealty/listener/protection/WorldProtectionListener.java index c7c6e47..ce22739 100644 --- a/src/main/java/com/modnmetl/virtualrealty/listener/protection/WorldProtectionListener.java +++ b/src/main/java/com/modnmetl/virtualrealty/listener/protection/WorldProtectionListener.java @@ -31,6 +31,12 @@ public WorldProtectionListener(VirtualRealty plugin) { super(plugin); } + public boolean isDoor(Material type) { + if (type.name().endsWith("DOOR")) + return true; + return false; + } + @EventHandler(priority = EventPriority.LOWEST) public void onBlockInteract(PlayerInteractEvent e) { if (e.isCancelled()) @@ -45,7 +51,8 @@ public void onBlockInteract(PlayerInteractEvent e) { if (player.isSneaking() && e.isBlockInHand()) return; if (!(PlotProtectionListener.INTERACT.contains(e.getClickedBlock().getType()) - || PlotProtectionListener.SWITCHES.contains(e.getClickedBlock().getType()))) + || PlotProtectionListener.SWITCHES.contains(e.getClickedBlock().getType()) + || isDoor(e.getClickedBlock().getType()))) return; if (e.getHand() == EquipmentSlot.OFF_HAND) @@ -76,6 +83,15 @@ public void onBlockInteract(PlayerInteractEvent e) { e.setCancelled(true); player.sendMessage(VirtualRealty.PREFIX + VirtualRealty.getMessages().cantInteract); } + return; + } + if (isDoor(e.getClickedBlock().getType())) { + Class.forName("com.modnmetl.virtualrealty.premiumloader.PremiumLoader", false, + VirtualRealty.getLoader()); + if (!WorldUtil.hasPermission(RegionPermission.DOORS)) { + e.setCancelled(true); + player.sendMessage(VirtualRealty.PREFIX + VirtualRealty.getMessages().cantInteract); + } } } catch (Exception ignored) { } diff --git a/src/main/java/com/modnmetl/virtualrealty/manager/Pl3xMapManager.java b/src/main/java/com/modnmetl/virtualrealty/manager/Pl3xMapManager.java new file mode 100644 index 0000000..25bbb67 --- /dev/null +++ b/src/main/java/com/modnmetl/virtualrealty/manager/Pl3xMapManager.java @@ -0,0 +1,215 @@ +package com.modnmetl.virtualrealty.manager; + +import com.modnmetl.virtualrealty.VirtualRealty; +import com.modnmetl.virtualrealty.model.other.HighlightType; +import com.modnmetl.virtualrealty.model.plot.Plot; +import lombok.Getter; +import net.pl3x.map.core.Pl3xMap; +import net.pl3x.map.core.markers.Point; +import net.pl3x.map.core.markers.layer.SimpleLayer; +import net.pl3x.map.core.markers.marker.Marker; +import net.pl3x.map.core.markers.marker.Rectangle; +import net.pl3x.map.core.markers.option.Fill; +import net.pl3x.map.core.markers.option.Options; +import net.pl3x.map.core.markers.option.Stroke; +import net.pl3x.map.core.markers.option.Tooltip; +import net.pl3x.map.core.world.World; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.awt.Color; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; + +public class Pl3xMapManager { + + private final VirtualRealty instance; + + @Getter + private boolean pl3xMapPresent = false; + private Pl3xMap pl3xMapAPI = null; + private final Map worldLayers = new HashMap<>(); + private static final String MARKER_STRING = "

Plot #%s

Owned By: Available"; + private static final String MARKER_OWNED_STRING = "

Plot #%s

Owned By: %s
Owned Until: %s"; + + public Pl3xMapManager(VirtualRealty instance) { + this.instance = instance; + } + + public void registerPl3xMap() { + VirtualRealty.debug("Attempting to register Pl3xMap integration..."); + new BukkitRunnable() { + @Override + public void run() { + Plugin plugin = Bukkit.getServer().getPluginManager().getPlugin("Pl3xMap"); + if (plugin == null) { + VirtualRealty.debug("Pl3xMap plugin not found, skipping integration"); + return; + } + + VirtualRealty.debug("Pl3xMap plugin found, setting up integration..."); + pl3xMapPresent = true; + if (!plugin.isEnabled()) { + VirtualRealty.debug("Pl3xMap plugin is not enabled, skipping integration"); + return; + } + + try { + pl3xMapAPI = Pl3xMap.api(); + if (pl3xMapAPI == null) { + VirtualRealty.debug("Pl3xMap API is null, retrying..."); + return; + } + + VirtualRealty.debug("Pl3xMap API initialized successfully"); + VirtualRealty.debug("Registering pl3xmap plot markers for " + PlotManager.getInstance().getPlots().size() + " plots..."); + for (Plot plot : PlotManager.getInstance().getPlots()) { + resetPlotMarker(plot); + } + VirtualRealty.debug("Registered pl3xmap plot markers successfully"); + this.cancel(); + } catch (Exception e) { + VirtualRealty.debug("Failed to initialize Pl3xMap API: " + e.getMessage()); + this.cancel(); + } + } + }.runTaskTimer(instance, 20, 20 * 5); + } + + private SimpleLayer getOrCreateLayer(String worldName) { + if (!worldLayers.containsKey(worldName)) { + try { + World pl3xWorld = pl3xMapAPI.getWorldRegistry().get(worldName); + if (pl3xWorld == null) { + VirtualRealty.debug("Pl3xMap world not found: " + worldName); + return null; + } + + SimpleLayer layer = new SimpleLayer("virtualrealty_plots", () -> "VirtualRealty Plots"); + layer.setLabel("Plots"); + layer.setDefaultHidden(false); + layer.setPriority(100); + layer.setZIndex(100); + + pl3xWorld.getLayerRegistry().register(layer); + worldLayers.put(worldName, layer); + VirtualRealty.debug("Created Pl3xMap layer for world: " + worldName); + } catch (Exception e) { + VirtualRealty.debug("Failed to create Pl3xMap layer for world " + worldName + ": " + e.getMessage()); + return null; + } + } + return worldLayers.get(worldName); + } + + public static void resetPlotMarker(Plot plot) { + VirtualRealty vrInstance = VirtualRealty.getInstance(); + if (vrInstance.pl3xMapManager == null || !vrInstance.pl3xMapManager.isPl3xMapPresent()) return; + + vrInstance.pl3xMapManager.updatePlotMarker(plot); + } + + private void updatePlotMarker(Plot plot) { + try { + VirtualRealty.debug("Creating Pl3xMap marker for plot #" + plot.getID() + " in world " + plot.getCreatedWorldRaw()); + LocalDateTime localDateTime = plot.getOwnedUntilDate(); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + String ownedBy; + String fillColorHex; + double opacity; + + if (plot.getOwnedBy() == null) { + ownedBy = "Available"; + fillColorHex = VirtualRealty.getPluginConfiguration().pl3xmapMarkersColor.available.color; + opacity = VirtualRealty.getPluginConfiguration().pl3xmapMarkersColor.available.opacity; + } else { + ownedBy = plot.getPlotOwner().getName(); + fillColorHex = VirtualRealty.getPluginConfiguration().pl3xmapMarkersColor.owned.color; + opacity = VirtualRealty.getPluginConfiguration().pl3xmapMarkersColor.owned.opacity; + } + + if (VirtualRealty.getPluginConfiguration().pl3xmapType == HighlightType.OWNED && plot.getOwnedBy() == null) { + VirtualRealty.debug("Skipping plot #" + plot.getID() + " - configured to show only OWNED plots, but plot is available"); + return; + } + if (VirtualRealty.getPluginConfiguration().pl3xmapType == HighlightType.AVAILABLE && plot.getOwnedBy() != null) { + VirtualRealty.debug("Skipping plot #" + plot.getID() + " - configured to show only AVAILABLE plots, but plot is owned"); + return; + } + + SimpleLayer layer = getOrCreateLayer(plot.getCreatedWorldRaw()); + if (layer == null) { + VirtualRealty.debug("Failed to get or create layer for world: " + plot.getCreatedWorldRaw()); + return; + } + + String markerId = "virtualrealty_plot_" + plot.getID(); + + // Create rectangle marker + Rectangle rectangle = Rectangle.of( + markerId, + plot.getXMin(), + plot.getZMin(), + plot.getXMax(), + plot.getZMax() + ); + + // Parse color from hex string + Color color = Color.decode(fillColorHex.startsWith("#") ? fillColorHex : "#" + fillColorHex); + + // Convert Color to ARGB integer + int fillColor = (int) (opacity * 255) << 24 | color.getRGB() & 0x00FFFFFF; + int strokeColor = Color.GRAY.getRGB() | 0xFF000000; // Full opacity gray + + // Create fill and stroke options + Fill fill = new Fill(fillColor).setType(Fill.Type.EVENODD); + Stroke stroke = new Stroke(2, strokeColor); + + // Create tooltip with popup content + String popupContent = plot.getOwnedBy() == null + ? String.format(MARKER_STRING, plot.getID()) + : String.format(MARKER_OWNED_STRING, plot.getID(), ownedBy, dateTimeFormatter.format(localDateTime)); + + Tooltip tooltip = new Tooltip(popupContent); + + // Set marker options + Options options = Options.builder() + .fill(fill) + .stroke(stroke) + .tooltip(tooltip) + .build(); + rectangle.setOptions(options); + + // Add or update marker + layer.addMarker(rectangle); + VirtualRealty.debug("Successfully created Pl3xMap marker for plot #" + plot.getID() + " at (" + + plot.getXMin() + "," + plot.getZMin() + ") to (" + plot.getXMax() + "," + plot.getZMax() + ")"); + + } catch (Exception e) { + VirtualRealty.debug("Failed to update Pl3xMap marker for plot " + plot.getID() + ": " + e.getMessage()); + } + } + + public static void removePl3xMapMarker(Plot plot) { + VirtualRealty vrInstance = VirtualRealty.getInstance(); + if (vrInstance.pl3xMapManager == null || !vrInstance.pl3xMapManager.isPl3xMapPresent()) return; + + vrInstance.pl3xMapManager.removeMarker(plot); + } + + private void removeMarker(Plot plot) { + try { + SimpleLayer layer = worldLayers.get(plot.getCreatedWorldRaw()); + if (layer == null) return; + + String markerId = "virtualrealty_plot_" + plot.getID(); + layer.removeMarker(markerId); + + } catch (Exception e) { + VirtualRealty.debug("Failed to remove Pl3xMap marker for plot " + plot.getID() + ": " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/modnmetl/virtualrealty/model/plot/Plot.java b/src/main/java/com/modnmetl/virtualrealty/model/plot/Plot.java index 95295f6..f20c8dc 100644 --- a/src/main/java/com/modnmetl/virtualrealty/model/plot/Plot.java +++ b/src/main/java/com/modnmetl/virtualrealty/model/plot/Plot.java @@ -191,7 +191,7 @@ public void teleportPlayer(Player player) { public boolean hasMembershipAccess(UUID uuid) { PlotMember member = getMember(uuid); - return member != null || (ownedBy != null && getPlotOwner().getUniqueId() == uuid); + return member != null || (ownedBy != null && getPlotOwner().getUniqueId().equals(uuid)); } public void togglePermission(RegionPermission plotPermission) {