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:
@@ -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.ReconciliationScheduler;
|
||||
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.PlayerPosition;
|
||||
import com.octoturge.mcmapper.common.protocol.SectionData;
|
||||
@@ -102,6 +104,21 @@ public class MCMapperMod {
|
||||
|
||||
mcServer = event.getServer();
|
||||
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);
|
||||
LOGGER.info("MCMapper (1.12.2 leaf) connecting to " + config.backendUrl);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user