BlueMap addon, shop points, map actions wired into Paper and Fabric; client opens shop windows

This commit is contained in:
Claude committed 2026-09-30 20:24:02 +00:00
1 parent ad7285efac
commit 2817ad2ac5
33 files changed
+754 -13

No files matched your search

@@ -39,6 +39,16 @@ public final class CommandSet {
throw new IllegalArgumentException("no command " + name);
}
/** What this command set knows that the map can show. */
public net.scopenet.core.map.MapSource mapSource() {
return new net.scopenet.core.map.MapSource() {
@Override public java.util.Map<String, Pos> warps() { return essentials.allWarps(); }
@Override public java.util.Map<java.util.UUID, java.util.Map<String, Pos>> homes() { return essentials.allHomes(); }
@Override public java.util.Map<String, Pos> guildHomes() { return guildCommands.guildHomes(); }
@Override public java.util.Collection<ShopPoints.Shop> shops() { return shops.all(); }
};
}
/** Guild homes by guild id (for the map). */
public java.util.Map<String, Pos> guildHomes() { return guildCommands.guildHomes(); }
@@ -52,6 +52,14 @@ public final class MapModel {
return ((int) Math.round((r + m) * 255) << 16) | ((int) Math.round((g + m) * 255) << 8) | (int) Math.round((b + m) * 255);
}
/** Which Minecraft dimension a BlueMap map shows, judged from its id and name ("world_nether" -> the nether). */
public static String dimensionOf(String id, String name) {
List<String> words = Arrays.asList(((id == null ? "" : id) + " " + (name == null ? "" : name)).toLowerCase(Locale.ROOT).split("[^a-z]+"));
if (words.contains("nether")) return "minecraft:the_nether";
if (words.contains("end")) return "minecraft:the_end";
return "minecraft:overworld";
}
/** The icon address: a full web address as is, a path on the panel made absolute, empty if none. */
public static String iconAddress(String icon, String panelBase) {
if (icon == null || icon.isBlank()) return "";
@@ -17,7 +17,7 @@ public final class MapService {
private final Env env;
private final ClaimIndex claims;
private final CommandSet commands;
private final MapSource places;
private final PlayerCache cache;
private final String panelBase;
private final boolean showHomes;
@@ -26,8 +26,8 @@ public final class MapService {
private volatile long marketAt = Long.MIN_VALUE / 2;
private volatile boolean fetching;
public MapService(Env env, ClaimIndex claims, CommandSet commands, PlayerCache cache, String panelBase, boolean showHomes, String serverName) {
this.env = env; this.claims = claims; this.commands = commands; this.cache = cache; this.panelBase = panelBase; this.showHomes = showHomes; this.serverName = serverName;
public MapService(Env env, ClaimIndex claims, MapSource places, PlayerCache cache, String panelBase, boolean showHomes, String serverName) {
this.env = env; this.claims = claims; this.places = places; this.cache = cache; this.panelBase = panelBase; this.showHomes = showHomes; this.serverName = serverName;
}
public String serverName() { return serverName; }
@@ -52,12 +52,12 @@ public final class MapService {
Map<String, MapModel.NamedPos> guildHomes = new TreeMap<>();
Map<String, String> labels = new HashMap<>();
claims.byDimension().values().forEach(list -> list.forEach(g -> labels.put(g.id(), "[" + g.tag() + "] " + g.name())));
commands.guildHomes().forEach((id, pos) -> guildHomes.put(id, new MapModel.NamedPos(labels.getOrDefault(id, "Guild home"), pos)));
places.guildHomes().forEach((id, pos) -> guildHomes.put(id, new MapModel.NamedPos(labels.getOrDefault(id, "Guild home"), pos)));
Pos spawn = env.platform.worldSpawn("minecraft:overworld");
MapModel.Sources sources = new MapModel.Sources(
env.features.guilds() && env.features.landClaiming() ? claims.byDimension() : Map.of(),
env.features.essentials() ? commands.essentials.allWarps() : Map.of(),
List.of(spawn), showHomes ? commands.essentials.allHomes() : Map.of(), guildHomes, commands.shops.all(),
env.features.essentials() ? places.warps() : Map.of(),
List.of(spawn), showHomes ? places.homes() : Map.of(), guildHomes, places.shops(),
market, players, panelBase, showHomes, names);
return MapModel.build(sources);
}
@@ -0,0 +1,17 @@
package net.scopenet.core.map;
import net.scopenet.core.Pos;
import net.scopenet.core.ShopPoints;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
/** Where a platform keeps the places the map shows. Each method returns a copy that is safe to read from any thread. */
public interface MapSource {
Map<String, Pos> warps();
Map<UUID, Map<String, Pos>> homes();
/** Guild homes by guild id. */
Map<String, Pos> guildHomes();
Collection<ShopPoints.Shop> shops();
}
@@ -151,6 +151,13 @@ class MapTest {
assertTrue(MapModel.build(sources(Map.of(), List.of(shop), new JsonArray(), false)).pins().stream().anyMatch(p -> p.detail().contains("No listings")));
}
@Test void mapsAreMatchedToDimensions() {
assertEquals("minecraft:overworld", MapModel.dimensionOf("world", "World"));
assertEquals("minecraft:the_nether", MapModel.dimensionOf("world_nether", "Nether"));
assertEquals("minecraft:the_end", MapModel.dimensionOf("world_the_end", "The End"));
assertEquals("minecraft:overworld", MapModel.dimensionOf("legends", "Friends & Legends"), "'end' inside another word isn't the End");
}
@Test void playersJsonHasOnlyPublicFacts() {
JsonObject j = MapModel.playersJson("SMP", List.of(new MapData.OnlinePlayer("u-1", "Alex", 12, "Miner", 4, "Iron", "IRON", "leader", "VIP", 7300)), 123L);
JsonObject a = j.getAsJsonObject("players").getAsJsonObject("u-1");
@@ -300,7 +307,7 @@ class MapTest {
ClaimIndex claims = new ClaimIndex();
claims.apply(JsonParser.parseString("{\"revision\":\"1\",\"guilds\":[{\"id\":\"g1\",\"name\":\"Iron\",\"tag\":\"IRON\",\"icon_url\":\"/textures/x\"}],"
+ "\"claims\":[[\"minecraft:overworld\",0,0,0],[\"minecraft:overworld\",1,0,0]],\"members\":{\"g1\":[]}}").getAsJsonObject());
MapService service = new MapService(kit.env, claims, set, cache, "https://panel.example.com", false, "SMP");
MapService service = new MapService(kit.env, claims, set.mapSource(), cache, "https://panel.example.com", false, "SMP");
service.tick();
MapData data = service.snapshot();
assertEquals(1, data.claims().size());