Phase 13: forge-1_12_2 ships blockstate/model JSON for non-cube models
BlockAssetExtractor#extractModels walks every active mod's own jar/directory (Loader#getActiveModList's ModContainer#getSource, not the classloader, since directory listing isn't a classloader operation) for every blockstates/*.json and models/block/**/*.json file, shipping all of them over a new batched block_models WS message alongside the existing Phase 11 block_registry/block_textures dump. Resolution of this data into real non-cube geometry is entirely backend-side (see MCMapper-Backend's worker/src/models.rs). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015tKdPZt78zbPUZMXWzKEKt
This commit is contained in:
@@ -129,6 +129,31 @@ matching this phase's Enigmatica-2-focused scope. See MCMapper-Backend's README
|
|||||||
backend currently does with this data (short version: stores it; per-server render-time
|
backend currently does with this data (short version: stores it; per-server render-time
|
||||||
resolution is explicitly deferred, documented there).
|
resolution is explicitly deferred, documented there).
|
||||||
|
|
||||||
|
### Real (non-cube) block models (Phase 13)
|
||||||
|
|
||||||
|
`BlockAssetExtractor#extractModels` ships every `assets/<modid>/blockstates/*.json` (flat, matching
|
||||||
|
vanilla's own layout — no subfolders expected) and `assets/<modid>/models/block/**/*.json`
|
||||||
|
(recursive — mods are free to nest these, e.g. a shared `models/block/base/` folder) file for every
|
||||||
|
currently-active mod, keyed the way MCMapper-Backend's `worker/src/models.rs` expects. Unlike
|
||||||
|
`extractTextures`, this can't use the classloader (there's no `ClassLoader` API to list a
|
||||||
|
directory's contents, only to read one known file by path) — it walks each mod's own source
|
||||||
|
instead, via `net.minecraftforge.fml.common.Loader#getActiveModList`'s `ModContainer#getSource`,
|
||||||
|
handling both shapes that can show up there: a packed jar (opened as a `ZipFile`) in production, or
|
||||||
|
a raw exploded directory when running from an IDE/dev environment.
|
||||||
|
|
||||||
|
The mod deliberately ships **every** model file it finds under a mod's `models/block/` tree rather
|
||||||
|
than trying to work out which ones a given blockstate actually references — a blockstate's
|
||||||
|
`"model"` field (or another model's `"parent"`) can point at a path that doesn't match the
|
||||||
|
referencing block's own registry path 1:1 (e.g. several blocks sharing one base model), and the
|
||||||
|
mod has no JSON parser to resolve that itself. That resolution — parent-chain walking, texture-
|
||||||
|
variable substitution, picking a representative variant — is entirely backend-side (see
|
||||||
|
MCMapper-Backend's README's own Phase 13 section for what it does and doesn't handle, e.g. no
|
||||||
|
`multipart` blockstate support, no property-based variant selection).
|
||||||
|
|
||||||
|
Sent as a new `block_models` WS message, batched 50/message like `block_textures`, from the same
|
||||||
|
ready-listener callback as the Phase 11 registry/texture dump. Verified via a real
|
||||||
|
`./gradlew :forge-1_12_2:compileJava` against the pinned legacy ForgeGradle toolchain.
|
||||||
|
|
||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
See `THIRD_PARTY_NOTICES.md`.
|
See `THIRD_PARTY_NOTICES.md`.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.octoturge.mcmapper.common;
|
package com.octoturge.mcmapper.common;
|
||||||
|
|
||||||
|
import com.octoturge.mcmapper.common.protocol.BlockModelFile;
|
||||||
import com.octoturge.mcmapper.common.protocol.BlockRegistryEntry;
|
import com.octoturge.mcmapper.common.protocol.BlockRegistryEntry;
|
||||||
import com.octoturge.mcmapper.common.protocol.BlockTexture;
|
import com.octoturge.mcmapper.common.protocol.BlockTexture;
|
||||||
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
||||||
@@ -54,6 +55,13 @@ public interface BackendConnection {
|
|||||||
*/
|
*/
|
||||||
void sendBlockTextures(List<BlockTexture> textures);
|
void sendBlockTextures(List<BlockTexture> textures);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends this leaf's raw blockstate/model JSON dump (Phase 13) — see {@link
|
||||||
|
* BlockModelFile}'s javadoc. Implementations should batch calls the same way {@link
|
||||||
|
* #sendBlockTextures} does, for the same heavily-modded-pack reason.
|
||||||
|
*/
|
||||||
|
void sendBlockModels(List<BlockModelFile> files);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a callback fired every time the connection successfully authenticates (including
|
* Registers a callback fired every time the connection successfully authenticates (including
|
||||||
* after an automatic reconnect) — {@code connect()} itself is async (the real handshake
|
* after an automatic reconnect) — {@code connect()} itself is async (the real handshake
|
||||||
@@ -112,6 +120,10 @@ public interface BackendConnection {
|
|||||||
public void sendBlockTextures(List<BlockTexture> textures) {
|
public void sendBlockTextures(List<BlockTexture> textures) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendBlockModels(List<BlockModelFile> files) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setReadyListener(Runnable onReady) {
|
public void setReadyListener(Runnable onReady) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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.BlockModelFile;
|
||||||
import com.octoturge.mcmapper.common.protocol.BlockRegistryEntry;
|
import com.octoturge.mcmapper.common.protocol.BlockRegistryEntry;
|
||||||
import com.octoturge.mcmapper.common.protocol.BlockTexture;
|
import com.octoturge.mcmapper.common.protocol.BlockTexture;
|
||||||
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
||||||
@@ -40,6 +41,7 @@ import java.util.function.Consumer;
|
|||||||
*
|
*
|
||||||
* mod -> api {"type":"block_registry","entries":[{"id":4000,"name":"botania:manapool"}]}
|
* mod -> api {"type":"block_registry","entries":[{"id":4000,"name":"botania:manapool"}]}
|
||||||
* mod -> api {"type":"block_textures","textures":[{"name":"botania:manapool","dataBase64":"..."}]}
|
* mod -> api {"type":"block_textures","textures":[{"name":"botania:manapool","dataBase64":"..."}]}
|
||||||
|
* mod -> api {"type":"block_models","entries":[{"kind":"blockstate","name":"botania:manapool","json":"..."},{"kind":"model","name":"botania:block/manapool","json":"..."}]}
|
||||||
* </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
|
||||||
@@ -83,6 +85,13 @@ import java.util.function.Consumer;
|
|||||||
* {@link #sendBlockTextures} batches into multiple messages (see {@code BLOCK_TEXTURE_BATCH_SIZE})
|
* {@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.
|
* rather than one huge frame, since a heavily-modded server can have thousands of block textures.
|
||||||
*
|
*
|
||||||
|
* Phase 13: {@code block_models} is sent the same way, once, alongside {@code block_registry}/
|
||||||
|
* {@code block_textures} — see {@link BlockModelFile}'s javadoc for why the mod ships every model
|
||||||
|
* file it finds rather than trying to resolve blockstate-to-model references itself.
|
||||||
|
* {@link #sendBlockModels} batches the same way {@link #sendBlockTextures} does, for the same
|
||||||
|
* reason (blockstate/model JSON files, while individually small, can number in the thousands on
|
||||||
|
* a heavily-modded pack).
|
||||||
|
*
|
||||||
* 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.
|
||||||
@@ -352,6 +361,28 @@ public class DefaultBackendConnection implements BackendConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final int BLOCK_MODEL_BATCH_SIZE = 50;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendBlockModels(List<BlockModelFile> files) {
|
||||||
|
if (files.isEmpty() || !serverReady) return;
|
||||||
|
for (int start = 0; start < files.size(); start += BLOCK_MODEL_BATCH_SIZE) {
|
||||||
|
int end = Math.min(start + BLOCK_MODEL_BATCH_SIZE, files.size());
|
||||||
|
List<Object> entryList = new ArrayList<>();
|
||||||
|
for (BlockModelFile f : files.subList(start, end)) {
|
||||||
|
Map<String, Object> obj = new LinkedHashMap<>();
|
||||||
|
obj.put("kind", f.kind);
|
||||||
|
obj.put("name", f.name);
|
||||||
|
obj.put("json", f.json);
|
||||||
|
entryList.add(obj);
|
||||||
|
}
|
||||||
|
Map<String, Object> msg = new LinkedHashMap<>();
|
||||||
|
msg.put("type", "block_models");
|
||||||
|
msg.put("entries", entryList);
|
||||||
|
sendRaw("block_models", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setReadyListener(Runnable onReady) {
|
public void setReadyListener(Runnable onReady) {
|
||||||
this.readyListener = onReady;
|
this.readyListener = onReady;
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.octoturge.mcmapper.common.protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One raw blockstate or model JSON file, extracted by a leaf off its own classloader (Phase 13) —
|
||||||
|
* same classloader-is-already-there rationale as {@link BlockTexture}. {@code kind} is either
|
||||||
|
* {@code "blockstate"} (a {@code assets/<modid>/blockstates/<path>.json} file, keyed by the
|
||||||
|
* block's own registry name) or {@code "model"} (a {@code assets/<modid>/models/block/<path>.json}
|
||||||
|
* file, keyed by {@code "<modid>:block/<path>"} — the same reference shape a blockstate's
|
||||||
|
* {@code "model"} field or another model's {@code "parent"} field uses to point at it). The mod
|
||||||
|
* ships every model file it finds under a mod's {@code models/block/} tree rather than trying to
|
||||||
|
* pick out just the ones a given blockstate needs — a blockstate can reference a model at a path
|
||||||
|
* that doesn't match the block's own registry path 1:1 (e.g. a shared base model reused by several
|
||||||
|
* blocks), and the mod has no JSON parser to work that out itself; that resolution is the
|
||||||
|
* backend's job (see MCMapper-Backend's {@code worker/src/models.rs}).
|
||||||
|
*/
|
||||||
|
public final class BlockModelFile {
|
||||||
|
public final String kind;
|
||||||
|
public final String name;
|
||||||
|
public final String json;
|
||||||
|
|
||||||
|
public BlockModelFile(String kind, String name, String json) {
|
||||||
|
this.kind = kind;
|
||||||
|
this.name = name;
|
||||||
|
this.json = json;
|
||||||
|
}
|
||||||
|
}
|
||||||
+125
-4
@@ -1,16 +1,25 @@
|
|||||||
package com.octoturge.mcmapper.forge1122;
|
package com.octoturge.mcmapper.forge1122;
|
||||||
|
|
||||||
|
import com.octoturge.mcmapper.common.protocol.BlockModelFile;
|
||||||
import com.octoturge.mcmapper.common.protocol.BlockRegistryEntry;
|
import com.octoturge.mcmapper.common.protocol.BlockRegistryEntry;
|
||||||
import com.octoturge.mcmapper.common.protocol.BlockTexture;
|
import com.octoturge.mcmapper.common.protocol.BlockTexture;
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.block.Block;
|
||||||
import net.minecraft.util.ResourceLocation;
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
import net.minecraftforge.fml.common.Loader;
|
||||||
|
import net.minecraftforge.fml.common.ModContainer;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Phase 11: best-effort classloader extraction of block textures + the numeric-id -> registry-
|
* Phase 11: best-effort classloader extraction of block textures + the numeric-id -> registry-
|
||||||
@@ -23,10 +32,10 @@ import java.util.List;
|
|||||||
* {@code assets/<modid>/textures/blocks/<path>.png} directly with no jar-file bookkeeping.
|
* {@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
|
* 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
|
* real blockstate/model JSON resolution — {@link #extractModels} (Phase 13) is what feeds that,
|
||||||
* block whose model uses a differently-named/multi-texture layout (most non-cube blocks) simply
|
* on the backend side (see MCMapper-Backend's {@code worker/src/models.rs}). A block whose texture
|
||||||
* isn't found here and is skipped — no worse than pre-Phase-11 behavior for that block, just not
|
* doesn't match this convention (most non-cube blocks) simply isn't found by {@link
|
||||||
* improved yet.
|
* #extractTextures} and is skipped there — no worse than pre-Phase-11 behavior for that block.
|
||||||
*/
|
*/
|
||||||
final class BlockAssetExtractor {
|
final class BlockAssetExtractor {
|
||||||
private BlockAssetExtractor() {
|
private BlockAssetExtractor() {
|
||||||
@@ -85,4 +94,116 @@ final class BlockAssetExtractor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 13: ships every {@code assets/<modid>/blockstates/*.json} and {@code
|
||||||
|
* assets/<modid>/models/block/**\/*.json} file for every currently-active mod, keyed the same
|
||||||
|
* way {@code worker/src/models.rs}'s {@code ModelRegistry::build} expects — see {@link
|
||||||
|
* BlockModelFile}'s javadoc for why this ships every model file rather than trying to resolve
|
||||||
|
* which ones a blockstate actually needs. Walks each mod's source (a jar in production, a raw
|
||||||
|
* exploded directory when running from an IDE/dev environment — {@code ModContainer#getSource}
|
||||||
|
* covers both) directly rather than the classloader, since listing a directory's contents
|
||||||
|
* (unlike reading one known file by path, as {@link #extractTextures} does) isn't something
|
||||||
|
* {@code ClassLoader#getResourceAsStream} can do.
|
||||||
|
*/
|
||||||
|
static List<BlockModelFile> extractModels(Logger logger) {
|
||||||
|
List<BlockModelFile> files = new ArrayList<>();
|
||||||
|
for (ModContainer mod : Loader.instance().getActiveModList()) {
|
||||||
|
File source = mod.getSource();
|
||||||
|
if (source == null) continue;
|
||||||
|
if (source.isFile()) {
|
||||||
|
extractModelsFromJar(source, mod.getModId(), files);
|
||||||
|
} else if (source.isDirectory()) {
|
||||||
|
extractModelsFromDirectory(source, mod.getModId(), files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("MCMapper (1.12.2 leaf) extracted " + files.size()
|
||||||
|
+ " blockstate/model JSON files for Phase 13 non-cube model rendering");
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void extractModelsFromJar(File jarFile, String modid, List<BlockModelFile> out) {
|
||||||
|
try (ZipFile zip = new ZipFile(jarFile)) {
|
||||||
|
Enumeration<? extends ZipEntry> entries = zip.entries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
ZipEntry entry = entries.nextElement();
|
||||||
|
if (entry.isDirectory()) continue;
|
||||||
|
ModelPath parsed = classify(modid, entry.getName());
|
||||||
|
if (parsed == null) continue;
|
||||||
|
byte[] json = readZipEntry(zip, entry);
|
||||||
|
if (json != null) out.add(new BlockModelFile(parsed.kind, parsed.name, new String(json, StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Best-effort, matching readTexture's convention — skip this mod's models entirely.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] readZipEntry(ZipFile zip, ZipEntry entry) {
|
||||||
|
try (InputStream in = zip.getInputStream(entry)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void extractModelsFromDirectory(File modRoot, String modid, List<BlockModelFile> out) {
|
||||||
|
File assetsDir = new File(modRoot, "assets/" + modid);
|
||||||
|
if (assetsDir.isDirectory()) walkDirectory(assetsDir, "assets/" + modid + "/", modid, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void walkDirectory(File dir, String relPrefix, String modid, List<BlockModelFile> out) {
|
||||||
|
File[] children = dir.listFiles();
|
||||||
|
if (children == null) return;
|
||||||
|
for (File child : children) {
|
||||||
|
String rel = relPrefix + child.getName();
|
||||||
|
if (child.isDirectory()) {
|
||||||
|
walkDirectory(child, rel + "/", modid, out);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ModelPath parsed = classify(modid, rel);
|
||||||
|
if (parsed == null) continue;
|
||||||
|
try {
|
||||||
|
byte[] json = Files.readAllBytes(child.toPath());
|
||||||
|
out.add(new BlockModelFile(parsed.kind, parsed.name, new String(json, StandardCharsets.UTF_8)));
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Best-effort — skip this one file.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classifies one archive/directory entry path (e.g. {@code "assets/botania/blockstates/
|
||||||
|
* manapool.json"}) into a wire-ready {@code (kind, name)} pair, or {@code null} if it's not a
|
||||||
|
* blockstate/model JSON file for this mod. Blockstates are matched flat (no subfolders, matching
|
||||||
|
* vanilla's own layout); models are matched recursively under {@code models/block/} since mods
|
||||||
|
* are free to nest them (e.g. a shared {@code models/block/base/} folder).
|
||||||
|
*/
|
||||||
|
private static ModelPath classify(String modid, String entryPath) {
|
||||||
|
String prefix = "assets/" + modid + "/";
|
||||||
|
if (!entryPath.startsWith(prefix) || !entryPath.endsWith(".json")) return null;
|
||||||
|
String rest = entryPath.substring(prefix.length());
|
||||||
|
if (rest.startsWith("blockstates/") && rest.indexOf('/', "blockstates/".length()) < 0) {
|
||||||
|
String path = rest.substring("blockstates/".length(), rest.length() - ".json".length());
|
||||||
|
return new ModelPath("blockstate", modid + ":" + path);
|
||||||
|
}
|
||||||
|
if (rest.startsWith("models/block/")) {
|
||||||
|
String path = rest.substring("models/block/".length(), rest.length() - ".json".length());
|
||||||
|
return new ModelPath("model", modid + ":block/" + path);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ModelPath {
|
||||||
|
final String kind;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
ModelPath(String kind, String name) {
|
||||||
|
this.kind = kind;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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.BlockModelFile;
|
||||||
import com.octoturge.mcmapper.common.protocol.BlockRegistryEntry;
|
import com.octoturge.mcmapper.common.protocol.BlockRegistryEntry;
|
||||||
import com.octoturge.mcmapper.common.protocol.BlockTexture;
|
import com.octoturge.mcmapper.common.protocol.BlockTexture;
|
||||||
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
||||||
@@ -114,9 +115,11 @@ public class MCMapperMod {
|
|||||||
// silently no-op).
|
// silently no-op).
|
||||||
List<BlockRegistryEntry> blockRegistry = BlockAssetExtractor.extractRegistry();
|
List<BlockRegistryEntry> blockRegistry = BlockAssetExtractor.extractRegistry();
|
||||||
List<BlockTexture> blockTextures = BlockAssetExtractor.extractTextures(LOGGER);
|
List<BlockTexture> blockTextures = BlockAssetExtractor.extractTextures(LOGGER);
|
||||||
|
List<BlockModelFile> blockModels = BlockAssetExtractor.extractModels(LOGGER);
|
||||||
connection.setReadyListener(() -> {
|
connection.setReadyListener(() -> {
|
||||||
connection.sendBlockRegistry(blockRegistry);
|
connection.sendBlockRegistry(blockRegistry);
|
||||||
connection.sendBlockTextures(blockTextures);
|
connection.sendBlockTextures(blockTextures);
|
||||||
|
connection.sendBlockModels(blockModels);
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.connect(config.backendUrl, config.serverToken);
|
connection.connect(config.backendUrl, config.serverToken);
|
||||||
|
|||||||
Reference in New Issue
Block a user