Skip to content

Commit cd04aa8

Browse files
authored
Merge pull request #2883 from BentoBoxWorld/feature/dynmap-hook
Add Dynmap hook to display island borders
2 parents 3ceb725 + e0d9a78 commit cd04aa8

File tree

4 files changed

+677
-0
lines changed

4 files changed

+677
-0
lines changed

build.gradle.kts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ val jdtAnnotationVersion = "2.2.600"
109109
val multilibVersion = "1.1.13"
110110
val oraxenVersion = "1.193.1"
111111
val blueMapApiVersion = "v2.6.2"
112+
val dynmapApiVersion = "3.4"
112113

113114
// Store versions in extra properties for resource filtering (used in plugin.yml, config.yml)
114115
extra["java.version"] = javaVersion
@@ -187,6 +188,7 @@ repositories {
187188
maven("https://repo.fancyplugins.de/releases") { name = "FancyPlugins-Releases" }
188189
maven("https://repo.pyr.lol/snapshots") { name = "Pyr-Snapshots" }
189190
maven("https://maven.devs.beer/") { name = "MatteoDev" }
191+
maven("https://repo.mikeprimm.com/") { name = "Dynmap" }
190192
maven("https://repo.oraxen.com/releases") { name = "Oraxen" } // Custom items plugin
191193
maven("https://repo.codemc.org/repository/bentoboxworld/") { name = "BentoBoxWorld-Repo" }
192194
maven("https://repo.extendedclip.com/releases/") { name = "Placeholder-API-Releases" }
@@ -238,6 +240,14 @@ dependencies {
238240
compileOnly("commons-lang:commons-lang:$commonsLangVersion")
239241
compileOnly("com.github.BlueMap-Minecraft:BlueMapAPI:$blueMapApiVersion")
240242
testImplementation("com.github.BlueMap-Minecraft:BlueMapAPI:$blueMapApiVersion")
243+
compileOnly("us.dynmap:DynmapCoreAPI:$dynmapApiVersion")
244+
compileOnly("us.dynmap:dynmap-api:$dynmapApiVersion") {
245+
exclude(group = "org.bukkit", module = "bukkit")
246+
}
247+
testImplementation("us.dynmap:DynmapCoreAPI:$dynmapApiVersion")
248+
testImplementation("us.dynmap:dynmap-api:$dynmapApiVersion") {
249+
exclude(group = "org.bukkit", module = "bukkit")
250+
}
241251
compileOnly("io.th0rgal:oraxen:$oraxenVersion") {
242252
exclude(group = "me.gabytm.util", module = "actions-spigot")
243253
exclude(group = "org.jetbrains", module = "annotations")

src/main/java/world/bentobox/bentobox/hooks/BentoBoxHookRegistrar.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class BentoBoxHookRegistrar {
1313
private static final String MV5_CLASS = "org.mvplugins.multiverse.core.MultiverseCore";
1414
private static final String MV4_CLASS = "com.onarandombox.MultiverseCore.MultiverseCore";
1515
private static final String BLUEMAP_CLASS = "de.bluecolored.bluemap.api.BlueMapAPI";
16+
private static final String DYNMAP_CLASS = "org.dynmap.DynmapAPI";
1617

1718
private final BentoBox plugin;
1819
private final HooksManager hooksManager;
@@ -58,6 +59,9 @@ public void registerLateHooks() {
5859
if (hasClass(BLUEMAP_CLASS)) {
5960
hooksManager.registerHook(new BlueMapHook());
6061
}
62+
if (hasClass(DYNMAP_CLASS)) {
63+
hooksManager.registerHook(new DynmapHook());
64+
}
6165
}
6266

6367
private boolean hasClass(String className) {
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
package world.bentobox.bentobox.hooks;
2+
3+
import java.util.HashMap;
4+
import java.util.Locale;
5+
import java.util.Map;
6+
7+
import org.bukkit.Bukkit;
8+
import org.bukkit.Material;
9+
import org.bukkit.World;
10+
import org.bukkit.event.EventHandler;
11+
import org.bukkit.event.EventPriority;
12+
import org.bukkit.event.Listener;
13+
import org.dynmap.DynmapAPI;
14+
import org.dynmap.markers.AreaMarker;
15+
import org.dynmap.markers.Marker;
16+
import org.dynmap.markers.MarkerAPI;
17+
import org.dynmap.markers.MarkerSet;
18+
import org.eclipse.jdt.annotation.NonNull;
19+
20+
import world.bentobox.bentobox.BentoBox;
21+
import world.bentobox.bentobox.api.addons.GameModeAddon;
22+
import world.bentobox.bentobox.api.events.island.IslandDeleteEvent;
23+
import world.bentobox.bentobox.api.events.island.IslandNameEvent;
24+
import world.bentobox.bentobox.api.events.island.IslandNewIslandEvent;
25+
import world.bentobox.bentobox.api.events.island.IslandResettedEvent;
26+
import world.bentobox.bentobox.api.hooks.Hook;
27+
import world.bentobox.bentobox.api.user.User;
28+
import world.bentobox.bentobox.database.objects.Island;
29+
30+
/**
31+
* Hook to display island markers on Dynmap.
32+
* @author tastybento
33+
* @since 3.12.0
34+
*/
35+
public class DynmapHook extends Hook implements Listener {
36+
37+
private final BentoBox plugin;
38+
private MarkerAPI markerAPI;
39+
/**
40+
* One marker set per game mode; key is the marker set ID (derived from friendly name).
41+
*/
42+
private final Map<String, MarkerSet> markerSets = new HashMap<>();
43+
44+
public DynmapHook() {
45+
super("dynmap", Material.FILLED_MAP);
46+
this.plugin = BentoBox.getInstance();
47+
}
48+
49+
@Override
50+
public boolean hook() {
51+
try {
52+
DynmapAPI dynmapAPI = (DynmapAPI) getPlugin();
53+
if (dynmapAPI == null) {
54+
return false;
55+
}
56+
MarkerAPI markers = dynmapAPI.getMarkerAPI();
57+
if (markers == null) {
58+
return false;
59+
}
60+
markerAPI = markers;
61+
} catch (Exception e) {
62+
return false;
63+
}
64+
// Register markers for all game mode addons known at hook time
65+
plugin.getAddonsManager().getGameModeAddons().forEach(this::registerGameMode);
66+
// Listen for future island events
67+
Bukkit.getPluginManager().registerEvents(this, plugin);
68+
return true;
69+
}
70+
71+
/**
72+
* Register all islands for a given game mode addon.
73+
* @param addon the game mode addon
74+
*/
75+
public void registerGameMode(@NonNull GameModeAddon addon) {
76+
String friendlyName = addon.getWorldSettings().getFriendlyName();
77+
String markerSetId = friendlyName.toLowerCase(Locale.ENGLISH) + ".markers";
78+
plugin.logDebug("Setting markers for Game Mode '" + friendlyName + "'");
79+
MarkerSet markerSet = markerSets.computeIfAbsent(friendlyName, k -> {
80+
plugin.logDebug("Making a new marker set for '" + k + "'");
81+
// Dynmap persists marker sets — check for existing one first
82+
MarkerSet existing = markerAPI.getMarkerSet(markerSetId);
83+
if (existing != null) {
84+
existing.setMarkerSetLabel(friendlyName);
85+
return existing;
86+
}
87+
return markerAPI.createMarkerSet(markerSetId, friendlyName, null, true);
88+
});
89+
// Clear stale markers from previous runs
90+
markerSet.getMarkers().forEach(Marker::deleteMarker);
91+
markerSet.getAreaMarkers().forEach(AreaMarker::deleteMarker);
92+
// Create a marker for each owned island in this addon's overworld
93+
plugin.getIslands().getIslands(addon.getOverWorld()).stream()
94+
.filter(is -> is.getOwner() != null)
95+
.forEach(island -> {
96+
plugin.logDebug("Creating marker for " + island.getCenter());
97+
setMarker(markerSet, island);
98+
});
99+
}
100+
101+
private void setMarker(MarkerSet markerSet, Island island) {
102+
String label = getIslandLabel(island);
103+
String id = island.getUniqueId();
104+
World w = island.getCenter().getWorld();
105+
if (w == null) {
106+
return;
107+
}
108+
String worldName = w.getName();
109+
plugin.logDebug("Adding a marker called '" + label + "' for island " + id);
110+
// Remove existing markers if present
111+
Marker existingMarker = markerSet.findMarker(id);
112+
if (existingMarker != null) {
113+
existingMarker.deleteMarker();
114+
}
115+
AreaMarker existingArea = markerSet.findAreaMarker(id + "_area");
116+
if (existingArea != null) {
117+
existingArea.deleteMarker();
118+
}
119+
// Point marker at island center for the label/icon
120+
markerSet.createMarker(id, label, worldName,
121+
island.getCenter().getX(), island.getCenter().getY(), island.getCenter().getZ(),
122+
markerAPI.getMarkerIcon("default"), true);
123+
// Area marker showing the protected island border
124+
double[] xCorners = { island.getMinProtectedX(), island.getMaxProtectedX(),
125+
island.getMaxProtectedX(), island.getMinProtectedX() };
126+
double[] zCorners = { island.getMinProtectedZ(), island.getMinProtectedZ(),
127+
island.getMaxProtectedZ(), island.getMaxProtectedZ() };
128+
AreaMarker area = markerSet.createAreaMarker(id + "_area", label, false, worldName,
129+
xCorners, zCorners, true);
130+
if (area != null) {
131+
area.setLineStyle(2, 0.8, 0x3388FF);
132+
area.setFillStyle(0.15, 0x3388FF);
133+
}
134+
}
135+
136+
private String getIslandLabel(Island island) {
137+
if (island.getName() != null && !island.getName().isBlank()) {
138+
return island.getName();
139+
} else if (island.getOwner() != null) {
140+
User owner = User.getInstance(island.getOwner());
141+
if (owner != null) {
142+
return owner.getName();
143+
}
144+
}
145+
return island.getUniqueId();
146+
}
147+
148+
@Override
149+
public String getFailureCause() {
150+
return "Dynmap is not loaded or its Marker API is unavailable.";
151+
}
152+
153+
private void add(Island island, GameModeAddon addon) {
154+
MarkerSet markerSet = markerSets.get(addon.getWorldSettings().getFriendlyName());
155+
if (markerSet != null) {
156+
setMarker(markerSet, island);
157+
}
158+
}
159+
160+
private void remove(String islandUniqueId, GameModeAddon addon) {
161+
MarkerSet markerSet = markerSets.get(addon.getWorldSettings().getFriendlyName());
162+
if (markerSet != null) {
163+
Marker marker = markerSet.findMarker(islandUniqueId);
164+
if (marker != null) {
165+
marker.deleteMarker();
166+
}
167+
AreaMarker area = markerSet.findAreaMarker(islandUniqueId + "_area");
168+
if (area != null) {
169+
area.deleteMarker();
170+
}
171+
}
172+
}
173+
174+
// --- Public addon API ---
175+
176+
/**
177+
* Returns the Dynmap MarkerAPI for addons to create custom markers.
178+
* @return the MarkerAPI instance
179+
*/
180+
@NonNull
181+
public MarkerAPI getMarkerAPI() {
182+
return markerAPI;
183+
}
184+
185+
/**
186+
* Gets the marker set for the given game mode addon, if one has been registered.
187+
* @param addon the game mode addon
188+
* @return the MarkerSet, or null if not registered
189+
*/
190+
public MarkerSet getMarkerSet(@NonNull GameModeAddon addon) {
191+
return markerSets.get(addon.getWorldSettings().getFriendlyName());
192+
}
193+
194+
/**
195+
* Creates or retrieves a custom marker set. Useful for addons like Warps
196+
* that want to display their own markers on Dynmap.
197+
* @param id unique identifier for the marker set
198+
* @param label display label for the marker set
199+
* @return the MarkerSet
200+
*/
201+
@NonNull
202+
public MarkerSet createMarkerSet(@NonNull String id, @NonNull String label) {
203+
MarkerSet existing = markerAPI.getMarkerSet(id);
204+
if (existing != null) {
205+
return existing;
206+
}
207+
return markerAPI.createMarkerSet(id, label, null, true);
208+
}
209+
210+
// --- Event handlers ---
211+
212+
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
213+
public void onNewIsland(IslandNewIslandEvent e) {
214+
plugin.logDebug(e.getEventName());
215+
plugin.getIWM().getAddon(e.getIsland().getWorld()).ifPresent(addon -> add(e.getIsland(), addon));
216+
}
217+
218+
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
219+
public void onIslandDelete(IslandDeleteEvent e) {
220+
plugin.logDebug(e.getEventName());
221+
plugin.getIWM().getAddon(e.getIsland().getWorld())
222+
.ifPresent(addon -> remove(e.getIsland().getUniqueId(), addon));
223+
}
224+
225+
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
226+
public void onIslandName(IslandNameEvent e) {
227+
plugin.logDebug(e.getEventName());
228+
plugin.getIWM().getAddon(e.getIsland().getWorld()).ifPresent(addon -> {
229+
remove(e.getIsland().getUniqueId(), addon);
230+
add(e.getIsland(), addon);
231+
});
232+
}
233+
234+
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
235+
public void onIslandReset(IslandResettedEvent e) {
236+
plugin.logDebug(e.getEventName());
237+
plugin.getIWM().getAddon(e.getIsland().getWorld()).ifPresent(addon -> {
238+
remove(e.getOldIsland().getUniqueId(), addon);
239+
add(e.getIsland(), addon);
240+
});
241+
}
242+
}

0 commit comments

Comments
 (0)