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) {