Phase 11: extract and ship block textures/registry (forge-1_12_2 leaf)
Forge doesn't split client/server jars, so a modded server's own classpath already has every loaded mod's block textures, unused server-side. BlockAssetExtractor reads them via the classloader (best-effort convention match on registry name -> texture filename) plus the numeric-id -> registry- name mapping, and ships both to the backend once per connection via two new wire messages (block_registry, block_textures), fired through a new BackendConnection#setReadyListener callback so the one-time send can't race the async WS handshake. Only forge-1_12_2 wires it up so far; the other two leaves can adopt it later with no protocol change.
This commit is contained in:
@@ -107,6 +107,28 @@ to web viewers is a separate, independent per-server admin setting on the backen
|
|||||||
MCMapper-Backend's README) — a server operator can track positions server-side without exposing
|
MCMapper-Backend's README) — a server operator can track positions server-side without exposing
|
||||||
them publicly, or vice versa.
|
them publicly, or vice versa.
|
||||||
|
|
||||||
|
### Real block textures (Phase 11)
|
||||||
|
|
||||||
|
Forge doesn't split client/server jars, so a dedicated server's own classpath already has every
|
||||||
|
loaded mod's `assets/<modid>/textures/blocks/*.png` (or `textures/block/` on newer conventions),
|
||||||
|
sitting there unused server-side. `forge-1_12_2` (this project's primary, most-modded target —
|
||||||
|
Enigmatica 2) reads them once at server-starting time via `BlockAssetExtractor`: `Block.REGISTRY`
|
||||||
|
gives every block's numeric id + registry name (same source as the existing column/section wire
|
||||||
|
encoding), and each block's texture is guessed by a best-effort *convention* match — the registry
|
||||||
|
name's path segment tried as a texture filename — not a real blockstate/model JSON resolution
|
||||||
|
(that's Phase 12's job; a block whose model doesn't follow the convention is just skipped, no
|
||||||
|
worse than before this phase). The registry mapping (`block_registry`) and extracted PNGs
|
||||||
|
(`block_textures`, batched 50/message) are sent to the backend once, right after the WS handshake
|
||||||
|
completes — via `BackendConnection#setReadyListener`, since `connect()` is async and a naive send
|
||||||
|
right after calling it would silently no-op before the handshake lands.
|
||||||
|
|
||||||
|
`forge-1_7_10` and `neoforge-26_1` don't implement extraction yet — `sendBlockRegistry`/
|
||||||
|
`sendBlockTextures`/`setReadyListener` live on the shared `BackendConnection` interface (so either
|
||||||
|
leaf can adopt them later with no protocol change) but only `forge-1_12_2` calls them so far,
|
||||||
|
matching this phase's Enigmatica-2-focused scope. See MCMapper-Backend's README for what the
|
||||||
|
backend currently does with this data (short version: stores it; per-server render-time
|
||||||
|
resolution is explicitly deferred, documented there).
|
||||||
|
|
||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
See `THIRD_PARTY_NOTICES.md`.
|
See `THIRD_PARTY_NOTICES.md`.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.octoturge.mcmapper.common;
|
package com.octoturge.mcmapper.common;
|
||||||
|
|
||||||
|
import com.octoturge.mcmapper.common.protocol.BlockRegistryEntry;
|
||||||
|
import com.octoturge.mcmapper.common.protocol.BlockTexture;
|
||||||
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
||||||
import com.octoturge.mcmapper.common.protocol.LinkRequest;
|
import com.octoturge.mcmapper.common.protocol.LinkRequest;
|
||||||
import com.octoturge.mcmapper.common.protocol.PlayerPosition;
|
import com.octoturge.mcmapper.common.protocol.PlayerPosition;
|
||||||
@@ -37,6 +39,30 @@ public interface BackendConnection {
|
|||||||
*/
|
*/
|
||||||
void sendPlayerPositions(int dimension, List<PlayerPosition> players);
|
void sendPlayerPositions(int dimension, List<PlayerPosition> players);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends this leaf's numeric-blockId -> registry-name dump (Phase 11) — see {@link
|
||||||
|
* BlockRegistryEntry}'s javadoc. Leaves that don't implement Phase 11 texture extraction
|
||||||
|
* (see {@link #sendBlockTextures}) can simply never call this; an empty/never-called dump is
|
||||||
|
* harmless, not an error, on the backend side.
|
||||||
|
*/
|
||||||
|
void sendBlockRegistry(List<BlockRegistryEntry> entries);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends this leaf's best-effort classloader-extracted block textures (Phase 11) — see {@link
|
||||||
|
* BlockTexture}'s javadoc. Implementations should batch calls (many small messages rather
|
||||||
|
* than one huge frame) rather than requiring the caller to pre-batch.
|
||||||
|
*/
|
||||||
|
void sendBlockTextures(List<BlockTexture> textures);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a callback fired every time the connection successfully authenticates (including
|
||||||
|
* after an automatic reconnect) — {@code connect()} itself is async (the real handshake
|
||||||
|
* completes on a later WS frame), so callers with a one-time "just after connecting" send
|
||||||
|
* (see Phase 11's registry/texture dump) need this rather than calling right after {@code
|
||||||
|
* connect()}, which would silently no-op (every {@code send*} method is a no-op until ready).
|
||||||
|
*/
|
||||||
|
void setReadyListener(Runnable onReady);
|
||||||
|
|
||||||
/** Registers the callback for web-originated chat messages the backend relays back to us. */
|
/** Registers the callback for web-originated chat messages the backend relays back to us. */
|
||||||
void setChatListener(ChatListener listener);
|
void setChatListener(ChatListener listener);
|
||||||
|
|
||||||
@@ -78,6 +104,18 @@ public interface BackendConnection {
|
|||||||
public void sendPlayerPositions(int dimension, List<PlayerPosition> players) {
|
public void sendPlayerPositions(int dimension, List<PlayerPosition> players) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendBlockRegistry(List<BlockRegistryEntry> entries) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendBlockTextures(List<BlockTexture> textures) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadyListener(Runnable onReady) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setChatListener(ChatListener listener) {
|
public void setChatListener(ChatListener listener) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.octoturge.mcmapper.common;
|
package com.octoturge.mcmapper.common;
|
||||||
|
|
||||||
import com.octoturge.mcmapper.common.json.MiniJson;
|
import com.octoturge.mcmapper.common.json.MiniJson;
|
||||||
|
import com.octoturge.mcmapper.common.protocol.BlockRegistryEntry;
|
||||||
|
import com.octoturge.mcmapper.common.protocol.BlockTexture;
|
||||||
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
||||||
import com.octoturge.mcmapper.common.protocol.LinkRequest;
|
import com.octoturge.mcmapper.common.protocol.LinkRequest;
|
||||||
import com.octoturge.mcmapper.common.protocol.PlayerPosition;
|
import com.octoturge.mcmapper.common.protocol.PlayerPosition;
|
||||||
@@ -35,6 +37,9 @@ import java.util.function.Consumer;
|
|||||||
* api -> mod {"type":"waypoint_share","name":"...","x":..,"y":..,"z":..,"dimension":..,"color":"#RRGGBB","format":"journeymap"|"xaero"}
|
* api -> mod {"type":"waypoint_share","name":"...","x":..,"y":..,"z":..,"dimension":..,"color":"#RRGGBB","format":"journeymap"|"xaero"}
|
||||||
*
|
*
|
||||||
* mod -> api {"type":"player_positions","dimension":0,"players":[{"uuid":"...","username":"...","x":..,"y":..,"z":..}]}
|
* mod -> api {"type":"player_positions","dimension":0,"players":[{"uuid":"...","username":"...","x":..,"y":..,"z":..}]}
|
||||||
|
*
|
||||||
|
* mod -> api {"type":"block_registry","entries":[{"id":4000,"name":"botania:manapool"}]}
|
||||||
|
* mod -> api {"type":"block_textures","textures":[{"name":"botania:manapool","dataBase64":"..."}]}
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* Phase 3: {@code link_request} is sent by {@code /mcmapper link}; {@code chat} both directions
|
* Phase 3: {@code link_request} is sent by {@code /mcmapper link}; {@code chat} both directions
|
||||||
@@ -72,6 +77,12 @@ import java.util.function.Consumer;
|
|||||||
* mod-local {@code playerTrackingEnabled} config, which decides whether the mod computes/sends
|
* mod-local {@code playerTrackingEnabled} config, which decides whether the mod computes/sends
|
||||||
* this at all.
|
* this at all.
|
||||||
*
|
*
|
||||||
|
* Phase 11: {@code block_registry}/{@code block_textures} are sent once, shortly after connecting
|
||||||
|
* (a world's numeric-id assignments and mod-jar contents don't change without a server restart,
|
||||||
|
* which restarts the mod too) — see {@link BlockRegistryEntry}/{@link BlockTexture}'s javadocs.
|
||||||
|
* {@link #sendBlockTextures} batches into multiple messages (see {@code BLOCK_TEXTURE_BATCH_SIZE})
|
||||||
|
* rather than one huge frame, since a heavily-modded server can have thousands of block textures.
|
||||||
|
*
|
||||||
* This class avoids a third-party JSON/WS library entirely (see {@link SimpleWebSocketClient}
|
* This class avoids a third-party JSON/WS library entirely (see {@link SimpleWebSocketClient}
|
||||||
* and {@link MiniJson}'s javadoc) to keep the mod's classpath free of anything that would need
|
* and {@link MiniJson}'s javadoc) to keep the mod's classpath free of anything that would need
|
||||||
* shading through legacy ForgeGradle.
|
* shading through legacy ForgeGradle.
|
||||||
@@ -90,6 +101,7 @@ public class DefaultBackendConnection implements BackendConnection {
|
|||||||
private String serverToken;
|
private String serverToken;
|
||||||
private volatile ChatListener chatListener;
|
private volatile ChatListener chatListener;
|
||||||
private volatile WaypointShareListener waypointShareListener;
|
private volatile WaypointShareListener waypointShareListener;
|
||||||
|
private volatile Runnable readyListener;
|
||||||
|
|
||||||
public DefaultBackendConnection(Consumer<String> logInfo, Consumer<String> logWarn) {
|
public DefaultBackendConnection(Consumer<String> logInfo, Consumer<String> logWarn) {
|
||||||
this.logInfo = logInfo;
|
this.logInfo = logInfo;
|
||||||
@@ -173,6 +185,8 @@ public class DefaultBackendConnection implements BackendConnection {
|
|||||||
if (ok) {
|
if (ok) {
|
||||||
serverReady = true;
|
serverReady = true;
|
||||||
logInfo.accept("authenticated with backend as server " + obj.get("serverId"));
|
logInfo.accept("authenticated with backend as server " + obj.get("serverId"));
|
||||||
|
Runnable listener = readyListener;
|
||||||
|
if (listener != null) listener.run();
|
||||||
} else {
|
} else {
|
||||||
serverReady = false;
|
serverReady = false;
|
||||||
logWarn.accept("backend rejected connection: " + obj.get("error"));
|
logWarn.accept("backend rejected connection: " + obj.get("error"));
|
||||||
@@ -301,6 +315,48 @@ public class DefaultBackendConnection implements BackendConnection {
|
|||||||
sendRaw("player_positions", msg);
|
sendRaw("player_positions", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final int BLOCK_TEXTURE_BATCH_SIZE = 50;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendBlockRegistry(List<BlockRegistryEntry> entries) {
|
||||||
|
if (entries.isEmpty() || !serverReady) return;
|
||||||
|
List<Object> entryList = new ArrayList<>();
|
||||||
|
for (BlockRegistryEntry e : entries) {
|
||||||
|
Map<String, Object> obj = new LinkedHashMap<>();
|
||||||
|
obj.put("id", (double) e.id);
|
||||||
|
obj.put("name", e.name);
|
||||||
|
entryList.add(obj);
|
||||||
|
}
|
||||||
|
Map<String, Object> msg = new LinkedHashMap<>();
|
||||||
|
msg.put("type", "block_registry");
|
||||||
|
msg.put("entries", entryList);
|
||||||
|
sendRaw("block_registry", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendBlockTextures(List<BlockTexture> textures) {
|
||||||
|
if (textures.isEmpty() || !serverReady) return;
|
||||||
|
for (int start = 0; start < textures.size(); start += BLOCK_TEXTURE_BATCH_SIZE) {
|
||||||
|
int end = Math.min(start + BLOCK_TEXTURE_BATCH_SIZE, textures.size());
|
||||||
|
List<Object> textureList = new ArrayList<>();
|
||||||
|
for (BlockTexture t : textures.subList(start, end)) {
|
||||||
|
Map<String, Object> obj = new LinkedHashMap<>();
|
||||||
|
obj.put("name", t.name);
|
||||||
|
obj.put("dataBase64", Base64.getEncoder().encodeToString(t.pngBytes));
|
||||||
|
textureList.add(obj);
|
||||||
|
}
|
||||||
|
Map<String, Object> msg = new LinkedHashMap<>();
|
||||||
|
msg.put("type", "block_textures");
|
||||||
|
msg.put("textures", textureList);
|
||||||
|
sendRaw("block_textures", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadyListener(Runnable onReady) {
|
||||||
|
this.readyListener = onReady;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setChatListener(ChatListener listener) {
|
public void setChatListener(ChatListener listener) {
|
||||||
this.chatListener = listener;
|
this.chatListener = listener;
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.octoturge.mcmapper.common.protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One entry of a leaf's numeric-blockId -> registry-name dump (Phase 11) — e.g.
|
||||||
|
* {@code (4000, "botania:manapool")}. Sent once per connection via {@code
|
||||||
|
* BackendConnection#sendBlockRegistry}, since a world's id assignments (and its mod list) are
|
||||||
|
* stable for the server's lifetime. Lets the backend eventually resolve the numeric
|
||||||
|
* {@code blockId}/{@code blockMeta} already carried by {@link DeltaEvent}/{@link SectionData}
|
||||||
|
* into a texture, for blocks {@link BlockTexture} shipped a texture for.
|
||||||
|
*/
|
||||||
|
public final class BlockRegistryEntry {
|
||||||
|
public final int id;
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
public BlockRegistryEntry(int id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.octoturge.mcmapper.common.protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One block's texture, extracted by a leaf off its own classloader (Phase 11) — Forge doesn't
|
||||||
|
* split client/server jars, so every loaded mod's {@code assets/<modid>/textures/...} is already
|
||||||
|
* sitting on a dedicated server's own classpath, just unused server-side. {@code name} is the
|
||||||
|
* block's registry name (e.g. {@code "botania:manapool"}, matching {@link
|
||||||
|
* BlockRegistryEntry#name}) — a best-effort convention match against the block's own path
|
||||||
|
* segment, not a real blockstate/model JSON resolution (that's Phase 12's job), so not every
|
||||||
|
* block gets one: a leaf skips extraction rather than guessing when no file matches.
|
||||||
|
*/
|
||||||
|
public final class BlockTexture {
|
||||||
|
public final String name;
|
||||||
|
public final byte[] pngBytes;
|
||||||
|
|
||||||
|
public BlockTexture(String name, byte[] pngBytes) {
|
||||||
|
this.name = name;
|
||||||
|
this.pngBytes = pngBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.octoturge.mcmapper.forge1122;
|
||||||
|
|
||||||
|
import com.octoturge.mcmapper.common.protocol.BlockRegistryEntry;
|
||||||
|
import com.octoturge.mcmapper.common.protocol.BlockTexture;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 11: best-effort classloader extraction of block textures + the numeric-id -> registry-
|
||||||
|
* name mapping needed to make sense of them on the backend (see {@link BlockRegistryEntry}/
|
||||||
|
* {@link BlockTexture}'s javadocs). Forge doesn't split client/server jars, so every loaded mod's
|
||||||
|
* texture assets are already sitting on this dedicated server's own classpath — {@code
|
||||||
|
* Block.REGISTRY} gives every registered block's numeric id + registry name (same {@code
|
||||||
|
* Block.getIdFromBlock} used for the existing column/section wire encoding, see
|
||||||
|
* Forge1122ChunkAdapter), and the JVM's own merged mod classloader can load
|
||||||
|
* {@code assets/<modid>/textures/blocks/<path>.png} directly with no jar-file bookkeeping.
|
||||||
|
*
|
||||||
|
* This is a *convention* match (the registry name's path segment as the texture filename), not a
|
||||||
|
* real blockstate/model JSON resolution — that's Phase 12's job (see the plan's phase list). A
|
||||||
|
* block whose model uses a differently-named/multi-texture layout (most non-cube blocks) simply
|
||||||
|
* isn't found here and is skipped — no worse than pre-Phase-11 behavior for that block, just not
|
||||||
|
* improved yet.
|
||||||
|
*/
|
||||||
|
final class BlockAssetExtractor {
|
||||||
|
private BlockAssetExtractor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<BlockRegistryEntry> extractRegistry() {
|
||||||
|
List<BlockRegistryEntry> entries = new ArrayList<>();
|
||||||
|
for (Block block : Block.REGISTRY) {
|
||||||
|
ResourceLocation name = block.getRegistryName();
|
||||||
|
if (name == null) continue;
|
||||||
|
entries.add(new BlockRegistryEntry(Block.getIdFromBlock(block), name.toString()));
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<BlockTexture> extractTextures(Logger logger) {
|
||||||
|
ClassLoader classLoader = BlockAssetExtractor.class.getClassLoader();
|
||||||
|
List<BlockTexture> textures = new ArrayList<>();
|
||||||
|
int attempted = 0;
|
||||||
|
for (Block block : Block.REGISTRY) {
|
||||||
|
ResourceLocation name = block.getRegistryName();
|
||||||
|
if (name == null) continue;
|
||||||
|
attempted++;
|
||||||
|
// ResourceLocation's domain/path accessor name varies by MCP mapping version (e.g.
|
||||||
|
// getResourceDomain/getResourcePath vs. getNamespace/getPath) — toString()'s
|
||||||
|
// "modid:path" shape is stable across all of them, so split on that instead.
|
||||||
|
String full = name.toString();
|
||||||
|
int colon = full.indexOf(':');
|
||||||
|
String modid = colon >= 0 ? full.substring(0, colon) : full;
|
||||||
|
String path = colon >= 0 ? full.substring(colon + 1) : full;
|
||||||
|
byte[] png = readTexture(classLoader, modid, path);
|
||||||
|
if (png != null) textures.add(new BlockTexture(full, png));
|
||||||
|
}
|
||||||
|
logger.info("MCMapper (1.12.2 leaf) extracted " + textures.size() + "/" + attempted
|
||||||
|
+ " block textures for Phase 11 texture-averaged rendering (best-effort convention match)");
|
||||||
|
return textures;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] readTexture(ClassLoader classLoader, String modid, String path) {
|
||||||
|
// 1.12.2 uses textures/blocks/ (plural) — see the backend's block_names.rs doc comment
|
||||||
|
// for the 1.13+ textures/block/ rename, tried second in case a mod ships modern-style paths.
|
||||||
|
byte[] png = readResource(classLoader, "assets/" + modid + "/textures/blocks/" + path + ".png");
|
||||||
|
if (png != null) return png;
|
||||||
|
return readResource(classLoader, "assets/" + modid + "/textures/block/" + path + ".png");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] readResource(ClassLoader classLoader, String resourcePath) {
|
||||||
|
try (InputStream in = classLoader.getResourceAsStream(resourcePath)) {
|
||||||
|
if (in == null) return null;
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
byte[] buf = new byte[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = in.read(buf)) != -1) out.write(buf, 0, n);
|
||||||
|
return out.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import com.octoturge.mcmapper.common.ChunkAdapter;
|
|||||||
import com.octoturge.mcmapper.common.DefaultBackendConnection;
|
import com.octoturge.mcmapper.common.DefaultBackendConnection;
|
||||||
import com.octoturge.mcmapper.common.ReconciliationScheduler;
|
import com.octoturge.mcmapper.common.ReconciliationScheduler;
|
||||||
import com.octoturge.mcmapper.common.config.MapperConfig;
|
import com.octoturge.mcmapper.common.config.MapperConfig;
|
||||||
|
import com.octoturge.mcmapper.common.protocol.BlockRegistryEntry;
|
||||||
|
import com.octoturge.mcmapper.common.protocol.BlockTexture;
|
||||||
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
||||||
import com.octoturge.mcmapper.common.protocol.PlayerPosition;
|
import com.octoturge.mcmapper.common.protocol.PlayerPosition;
|
||||||
import com.octoturge.mcmapper.common.protocol.SectionData;
|
import com.octoturge.mcmapper.common.protocol.SectionData;
|
||||||
@@ -102,6 +104,21 @@ public class MCMapperMod {
|
|||||||
|
|
||||||
mcServer = event.getServer();
|
mcServer = event.getServer();
|
||||||
connection = new DefaultBackendConnection(LOGGER::info, LOGGER::warn);
|
connection = new DefaultBackendConnection(LOGGER::info, LOGGER::warn);
|
||||||
|
|
||||||
|
// Phase 11: extracted once, synchronously, here — Block.REGISTRY is already fully
|
||||||
|
// populated by server-starting time (all mod block registration happens during loading,
|
||||||
|
// well before this event fires), and it's a pure classpath read (no network), so this
|
||||||
|
// doesn't meaningfully delay startup even on a heavily-modded pack. Sent via the ready
|
||||||
|
// listener below rather than right after connect() (which is async — see
|
||||||
|
// BackendConnection#setReadyListener's javadoc for why a naive immediate send would
|
||||||
|
// silently no-op).
|
||||||
|
List<BlockRegistryEntry> blockRegistry = BlockAssetExtractor.extractRegistry();
|
||||||
|
List<BlockTexture> blockTextures = BlockAssetExtractor.extractTextures(LOGGER);
|
||||||
|
connection.setReadyListener(() -> {
|
||||||
|
connection.sendBlockRegistry(blockRegistry);
|
||||||
|
connection.sendBlockTextures(blockTextures);
|
||||||
|
});
|
||||||
|
|
||||||
connection.connect(config.backendUrl, config.serverToken);
|
connection.connect(config.backendUrl, config.serverToken);
|
||||||
LOGGER.info("MCMapper (1.12.2 leaf) connecting to " + config.backendUrl);
|
LOGGER.info("MCMapper (1.12.2 leaf) connecting to " + config.backendUrl);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user