BlueMap docs, command guide entries, early BlueMap hook on Fabric
This commit is contained in:
4 files changed
+95
-7
No files matched your search
@@ -0,0 +1,76 @@
|
|||||||
|
# BlueMap: the 3D map
|
||||||
|
|
||||||
|
SCOPENET uses [BlueMap](https://bluemap.bluecolored.de/) for the 3D world map. BlueMap runs on each Minecraft server as a plugin or mod and serves the map on its own web address. SCOPENET adds to it, and the launcher shows it inside the app exactly as a browser would.
|
||||||
|
|
||||||
|
## What you get
|
||||||
|
|
||||||
|
| On the map | Where it comes from |
|
||||||
|
|---|---|
|
||||||
|
| **Guild claims** as coloured regions with the guild's icon and name (one colour per guild, outlines follow the real shape, holes included) | the claim index |
|
||||||
|
| **Spawn, warps and guild homes** as pins | the server |
|
||||||
|
| **Homes** as pins (off by default, see privacy) | the server |
|
||||||
|
| **Markets and shops** as pins that list what is for sale | shop points + the panel's market |
|
||||||
|
| **Players** with BlueMap's own live markers; **click one** for a side panel | BlueMap + SCOPENET |
|
||||||
|
|
||||||
|
The player panel shows level, guild, rank, title, playtime and server level. Inside the launcher it also offers **Send teleport request**, **Send message** and **Invite to my guild** (you need to be signed in with a SCOPENET account, and the other player must be online on that server; inviting needs a leader or officer role). In a plain browser it shows the info and points you to the launcher for the actions.
|
||||||
|
|
||||||
|
## Set it up
|
||||||
|
|
||||||
|
1. **Install BlueMap** on the server: the Paper plugin, or the Fabric/Forge mod, from <https://bluemap.bluecolored.de/>. Start the server once. BlueMap asks you to accept a download of the Minecraft client files in its config (`core.conf`: `accept-download: true`), then renders the world.
|
||||||
|
2. **Make its web address reachable.** BlueMap serves on port 8100 by default. Put it behind HTTPS with a reverse proxy (recommended; see below) or open the port.
|
||||||
|
3. **Install the SCOPENET plugin or Fabric mod** (Paper, or Fabric 1.20.1). It finds BlueMap by itself and adds its markers and script. Nothing else to configure.
|
||||||
|
4. **Tell the panel the address:** *Servers → your server → Settings → Map address (BlueMap)*, for example `https://map.example.com`. Players then see a **Live Map** button for that server in the launcher.
|
||||||
|
|
||||||
|
`bluemap.enabled` (Paper `config.yml`, Fabric `scopenet.properties`) turns the addon off. `bluemap.show-homes` / `bluemap.show_homes` shows every player's homes.
|
||||||
|
|
||||||
|
## Shop points (physical shops)
|
||||||
|
|
||||||
|
A shop point is a block that opens the market or the server shop when right-clicked. It works with the normal windows (chest window on Paper; the client mod's shop and market windows where installed) and appears as a pin on the map.
|
||||||
|
|
||||||
|
```
|
||||||
|
/market point add <name> [market|shop] (look at a block; needs scopenet.command.market.point, default op)
|
||||||
|
/market point remove <name>
|
||||||
|
/market point list
|
||||||
|
```
|
||||||
|
|
||||||
|
`market` opens the player market and lists recent listings on the map pin; `shop` opens the server shop.
|
||||||
|
|
||||||
|
## Inside the launcher
|
||||||
|
|
||||||
|
The launcher loads your map address in a frame, so everything BlueMap does in a browser works there too. SCOPENET's script on the map page passes player clicks to the launcher, which shows the side panel. A few things make it work or not:
|
||||||
|
|
||||||
|
* **Use HTTPS.** Loading an `http://` map inside an HTTPS page can be blocked as mixed content on some systems.
|
||||||
|
* **Don't forbid framing.** If your reverse proxy sends `X-Frame-Options: DENY` or a `Content-Security-Policy: frame-ancestors` that excludes the launcher, the map won't load in the app. Remove that header for the map address. The launcher's **Browser** button opens the same map in your browser either way.
|
||||||
|
* The map address is checked by the panel (web address only, no credentials).
|
||||||
|
|
||||||
|
## Privacy
|
||||||
|
|
||||||
|
* The map is public to anyone who knows its address, like any BlueMap. **Homes are off by default** for that reason. Claims and guild names are visible by design.
|
||||||
|
* `players.json`, which drives the browser panel, holds only public facts (name, level, title, guild, rank, playtime). Never balances.
|
||||||
|
* Actions from the map go through the panel, are rate-limited, and need a signed-in account. A message or teleport request reaches only players who are online on that server.
|
||||||
|
|
||||||
|
## Reverse proxy example (nginx)
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
server_name map.example.com;
|
||||||
|
listen 443 ssl;
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:8100;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection ""; # BlueMap uses server-sent events for live updates
|
||||||
|
proxy_buffering off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## How it works (for developers)
|
||||||
|
|
||||||
|
* `net.scopenet.core.map` builds the map data without any map software: `ChunkRegions` turns chunks into outlines (tested with an area-conservation check), `MapModel` builds pins and regions and escapes every name, `MapService` gathers it from any thread.
|
||||||
|
* `integrations/bluemap` (`MapAddon`) is the only code that touches BlueMap's API. It copies `scopenet.js`, `scopenet.css` and the icons into BlueMap's web root, registers them, and refreshes the marker sets every 10 seconds.
|
||||||
|
* Map actions are queued in the panel (`server_actions`); the server collects them every 3 seconds (`ActionPoller`) and carries them out with its normal rules. Guild invitations are real invitations (`guild_invites`) that the other player accepts in the launcher or with `/guild accept`.
|
||||||
|
|
||||||
|
## Not verified yet
|
||||||
|
|
||||||
|
The BlueMap addon, the Paper and Fabric wiring, and the launcher's map view have not been compiled or run against a real BlueMap and Minecraft: the build environment cannot download them. The geometry, map model, shop points, action poller and guild-invite commands are unit-tested, and so are the panel endpoints. Build it, and report what BlueMap says in its log and what the map shows.
|
||||||
+10
-3
@@ -10,10 +10,17 @@ import java.util.logging.Logger;
|
|||||||
final class BlueMapHook {
|
final class BlueMapHook {
|
||||||
private BlueMapHook() {}
|
private BlueMapHook() {}
|
||||||
|
|
||||||
static Object start(MapService service, Logger log) {
|
/**
|
||||||
|
* Registered while the mod initialises (BlueMap reads its script list when it starts), so the data comes from
|
||||||
|
* {@code service}, which only exists once the server is running: until then the map is simply empty.
|
||||||
|
*/
|
||||||
|
static Object start(java.util.function.Supplier<MapService> service, Logger log) {
|
||||||
return MapAddon.start(new MapAddon.Feed() {
|
return MapAddon.start(new MapAddon.Feed() {
|
||||||
@Override public MapData snapshot() { return service.snapshot(); }
|
@Override public MapData snapshot() { MapService s = service.get(); return s == null ? MapData.empty() : s.snapshot(); }
|
||||||
@Override public com.google.gson.JsonObject playersJson() { return service.playersJson(); }
|
@Override public com.google.gson.JsonObject playersJson() {
|
||||||
|
MapService s = service.get();
|
||||||
|
return s == null ? net.scopenet.core.map.MapModel.playersJson("", java.util.List.of(), System.currentTimeMillis()) : s.playersJson();
|
||||||
|
}
|
||||||
}, log);
|
}, log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -63,6 +63,10 @@ public final class FabricFeatures implements Runnable {
|
|||||||
CommandSet commandSet() { return commands; }
|
CommandSet commandSet() { return commands; }
|
||||||
|
|
||||||
@Override public void run() {
|
@Override public void run() {
|
||||||
|
// Hook into BlueMap early: it reads the list of web scripts when it starts.
|
||||||
|
if (FabricConfig.load().bool("bluemap.enabled", true) && net.fabricmc.loader.api.FabricLoader.getInstance().isModLoaded("bluemap")) {
|
||||||
|
try { blueMap = BlueMapHook.start(() -> map, LOG); } catch (Throwable t) { LOG.warning("BlueMap addon could not start: " + t); }
|
||||||
|
}
|
||||||
ServerLifecycleEvents.SERVER_STARTED.register(this::started);
|
ServerLifecycleEvents.SERVER_STARTED.register(this::started);
|
||||||
ServerLifecycleEvents.SERVER_STOPPING.register(s -> stopping());
|
ServerLifecycleEvents.SERVER_STOPPING.register(s -> stopping());
|
||||||
ServerTickEvents.END_SERVER_TICK.register(s -> tick());
|
ServerTickEvents.END_SERVER_TICK.register(s -> tick());
|
||||||
@@ -116,10 +120,6 @@ public final class FabricFeatures implements Runnable {
|
|||||||
poller = new ActionPoller(env, (from, to) -> commands.essentials.tpa(from, new String[]{to.name()}));
|
poller = new ActionPoller(env, (from, to) -> commands.essentials.tpa(from, new String[]{to.name()}));
|
||||||
map = new MapService(env, integration.client().claims(), commands.mapSource(), cache, integration.settings().panel().toString(),
|
map = new MapService(env, integration.client().claims(), commands.mapSource(), cache, integration.settings().panel().toString(),
|
||||||
config.bool("bluemap.show_homes", false), s.getMotd());
|
config.bool("bluemap.show_homes", false), s.getMotd());
|
||||||
if (config.bool("bluemap.enabled", true) && net.fabricmc.loader.api.FabricLoader.getInstance().isModLoaded("bluemap")) {
|
|
||||||
try { blueMap = BlueMapHook.start(map, LOG); } catch (Throwable t) { LOG.warning("BlueMap addon could not start: " + t); }
|
|
||||||
}
|
|
||||||
|
|
||||||
registerCommands(s.getCommands().getDispatcher());
|
registerCommands(s.getCommands().getDispatcher());
|
||||||
registerPlaceholders();
|
registerPlaceholders();
|
||||||
ScopenetEconomy.install(new ScopenetEconomy(env.panel, r -> platform.runAsync(r)));
|
ScopenetEconomy.install(new ScopenetEconomy(env.panel, r -> platform.runAsync(r)));
|
||||||
@@ -133,6 +133,7 @@ public final class FabricFeatures implements Runnable {
|
|||||||
if (blueMap != null) { try { BlueMapHook.stop(blueMap); } catch (Throwable ignored) { } blueMap = null; }
|
if (blueMap != null) { try { BlueMapHook.stop(blueMap); } catch (Throwable ignored) { } blueMap = null; }
|
||||||
if (platform != null) platform.shutdown();
|
if (platform != null) platform.shutdown();
|
||||||
commands = null;
|
commands = null;
|
||||||
|
map = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerPlaceholders() {
|
private void registerPlaceholders() {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
{ usage: '/sell', does: 'Open the selling menu.', perm: 'scopenet.command.sell' },
|
{ usage: '/sell', does: 'Open the selling menu.', perm: 'scopenet.command.sell' },
|
||||||
{ usage: '/market', does: 'Browse and buy from the player market.', perm: 'scopenet.command.market', aliases: ['ah', 'auction'] },
|
{ usage: '/market', does: 'Browse and buy from the player market.', perm: 'scopenet.command.market', aliases: ['ah', 'auction'] },
|
||||||
{ usage: '/market sell <price>', does: 'List the item in your hand on the market.', perm: 'scopenet.command.market.sell' },
|
{ usage: '/market sell <price>', does: 'List the item in your hand on the market.', perm: 'scopenet.command.market.sell' },
|
||||||
|
{ usage: '/market point add <name> [market|shop]', does: 'Staff: turn the block you look at into a shop that opens the market or shop when right-clicked. Also shown on the map.', perm: 'scopenet.command.market.point' },
|
||||||
{ usage: '/orders', does: 'View your active market orders.', perm: 'scopenet.command.orders' },
|
{ usage: '/orders', does: 'View your active market orders.', perm: 'scopenet.command.orders' },
|
||||||
{ usage: '/trade <player>', does: 'Start a secure trade with another player.', perm: 'scopenet.command.trade' },
|
{ usage: '/trade <player>', does: 'Start a secure trade with another player.', perm: 'scopenet.command.trade' },
|
||||||
{ usage: '/transactions', does: 'See your recent money movements.', perm: 'scopenet.command.transactions' },
|
{ usage: '/transactions', does: 'See your recent money movements.', perm: 'scopenet.command.transactions' },
|
||||||
@@ -38,6 +39,8 @@
|
|||||||
{ usage: '/guild chat <message>', does: 'Talk to your guild only.', perm: 'scopenet.command.guild.chat', aliases: ['/guild c'] },
|
{ usage: '/guild chat <message>', does: 'Talk to your guild only.', perm: 'scopenet.command.guild.chat', aliases: ['/guild c'] },
|
||||||
{ usage: '/guild sethome · /guild home', does: 'Set or visit the guild home.', perm: 'scopenet.command.guild.sethome' },
|
{ usage: '/guild sethome · /guild home', does: 'Set or visit the guild home.', perm: 'scopenet.command.guild.sethome' },
|
||||||
{ usage: '/guild map', does: 'Show nearby claims on a map.', perm: 'scopenet.command.guild.map' },
|
{ usage: '/guild map', does: 'Show nearby claims on a map.', perm: 'scopenet.command.guild.map' },
|
||||||
|
{ usage: '/guild invite <player>', does: 'Invite a player to your guild (leaders and officers).', perm: 'scopenet.command.guild.invite' },
|
||||||
|
{ usage: '/guild accept [tag] · /guild decline [tag]', does: 'Answer a guild invitation. You can also answer in the launcher.', perm: 'scopenet.command.guild.accept' },
|
||||||
{ usage: '/claim', does: 'Claim the chunk you stand in for your guild.', perm: 'scopenet.command.claim' },
|
{ usage: '/claim', does: 'Claim the chunk you stand in for your guild.', perm: 'scopenet.command.claim' },
|
||||||
{ usage: '/unclaim', does: 'Give up the chunk you stand in.', perm: 'scopenet.command.unclaim' },
|
{ usage: '/unclaim', does: 'Give up the chunk you stand in.', perm: 'scopenet.command.unclaim' },
|
||||||
] },
|
] },
|
||||||
@@ -59,6 +62,7 @@
|
|||||||
|
|
||||||
const tips = [
|
const tips = [
|
||||||
'Placeholders: use %scopenet_level%, %scopenet_guild%, %scopenet_balance% and more in scoreboards, tab and chat.',
|
'Placeholders: use %scopenet_level%, %scopenet_guild%, %scopenet_balance% and more in scoreboards, tab and chat.',
|
||||||
|
'Right-click a shop point (a marked block) to open the market or shop. On a server with the SCOPENET client mod it opens the shop window.',
|
||||||
'Some commands may be switched off or limited by the server. If one says you lack permission, ask an admin.',
|
'Some commands may be switched off or limited by the server. If one says you lack permission, ask an admin.',
|
||||||
'Everything here also works through your rank: servers using LuckPerms can grant each permission node to any group.',
|
'Everything here also works through your rank: servers using LuckPerms can grant each permission node to any group.',
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in new issue
Block a user