From 35852bdcdca240c942408322d1fb3a12c18546fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Mon, 29 Jun 2026 17:54:57 +0200 Subject: [PATCH 01/26] First batch of changes --- .../stationapi/api/effect/EntityEffect.java | 57 +++++++++++-------- .../api/effect/EntityEffectType.java | 24 ++++++++ .../impl/effect/EffectsRenderer.java | 8 +-- .../api/util/math/StationBlockPos.java | 46 ++++++++++++--- .../{TilePosMixin.java => BlockPosMixin.java} | 8 +-- .../resources/station-maths-v0.mixins.json | 2 +- .../UnbakedModelLoadingFinishedEvent.java | 29 ++++++++++ .../render/model/BakedModelManager.java | 2 +- .../api/client/render/model/BakedQuad.java | 4 ++ .../api/client/render/model/ModelLoader.java | 9 ++- .../render/model/WeightedBakedModel.java | 22 ++++++- .../render/model/json/JsonUnbakedModel.java | 13 ++++- 12 files changed, 181 insertions(+), 43 deletions(-) rename station-maths-v0/src/main/java/net/modificationstation/stationapi/mixin/maths/{TilePosMixin.java => BlockPosMixin.java} (92%) create mode 100644 station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/UnbakedModelLoadingFinishedEvent.java diff --git a/station-effects-api-v0/src/main/java/net/modificationstation/stationapi/api/effect/EntityEffect.java b/station-effects-api-v0/src/main/java/net/modificationstation/stationapi/api/effect/EntityEffect.java index d2e2b4381..d87815e8a 100644 --- a/station-effects-api-v0/src/main/java/net/modificationstation/stationapi/api/effect/EntityEffect.java +++ b/station-effects-api-v0/src/main/java/net/modificationstation/stationapi/api/effect/EntityEffect.java @@ -1,7 +1,5 @@ package net.modificationstation.stationapi.api.effect; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.client.resource.language.I18n; import net.minecraft.entity.Entity; import net.minecraft.nbt.NbtCompound; @@ -10,16 +8,15 @@ public abstract class EntityEffect> { public static final int INFINITY_TICKS = -1; protected Entity entity; private int ticks; - + private final String nameTranslationKey; private final String descriptionTranslationKey; - + protected EntityEffect(Entity entity, int ticks) { this.entity = entity; this.ticks = ticks; - var effectId = getType().registryEntry.registryKey().getValue(); - nameTranslationKey = "gui.stationapi.effect." + effectId.namespace + "." + effectId.path + ".name"; - descriptionTranslationKey = nameTranslationKey.substring(0, nameTranslationKey.length() - 4) + "desc"; + this.nameTranslationKey = getType().getTranslationKey(); + this.descriptionTranslationKey = getType().getDescriptionTranslationKey(); } /** @@ -29,58 +26,72 @@ protected EntityEffect(Entity entity, int ticks) { * or synchronized from the server later. */ public abstract void onAdded(boolean appliedNow); - + /** * This method is called on each entity tick. */ public abstract void onTick(); - + /** * This method is called immediately when the effect is removed. */ public abstract void onRemoved(); - + /** * Allows to write any custom data to the tag storage. + * * @param tag effect data root tag */ protected abstract void writeNbt(NbtCompound tag); - + /** * Allows to read any custom data from the tag storage. + * * @param tag effect data root tag */ protected abstract void readNbt(NbtCompound tag); public abstract EntityEffectType getType(); - + /** * Get remaining effect ticks. */ public final int getTicks() { return ticks; } - + /** * Check if effect is infinite. */ public final boolean isInfinite() { return ticks == INFINITY_TICKS; } - + /** - * Get translated effect name, client side only. + * Get the translation key for the name of the effect. */ - @Environment(EnvType.CLIENT) - public final String getName() { + public final String getTranslationKey() { + return this.nameTranslationKey; + } + + /** + * Get the translation key for the description of the effect. + */ + public final String getDescriptionTranslationKey() { + return this.descriptionTranslationKey; + } + + /** + * Get translated effect name. + */ + public final String getTranslatedName() { return I18n.getTranslation(nameTranslationKey, nameTranslationKey); } - + /** - * Get translated effect description, client side only. + * Get translated effect description. */ - @Environment(EnvType.CLIENT) - public final String getDescription() { + public final String getTranslatedDescription() { return I18n.getTranslation(descriptionTranslationKey, descriptionTranslationKey); } @@ -93,12 +104,12 @@ public final void tick() { } } } - + public final void write(NbtCompound nbt) { nbt.putInt("ticks", ticks); writeNbt(nbt); } - + public final void read(NbtCompound nbt) { ticks = nbt.getInt("ticks"); readNbt(nbt); diff --git a/station-effects-api-v0/src/main/java/net/modificationstation/stationapi/api/effect/EntityEffectType.java b/station-effects-api-v0/src/main/java/net/modificationstation/stationapi/api/effect/EntityEffectType.java index c14e43662..cca825174 100644 --- a/station-effects-api-v0/src/main/java/net/modificationstation/stationapi/api/effect/EntityEffectType.java +++ b/station-effects-api-v0/src/main/java/net/modificationstation/stationapi/api/effect/EntityEffectType.java @@ -1,6 +1,7 @@ package net.modificationstation.stationapi.api.effect; import net.modificationstation.stationapi.api.registry.RegistryEntry; +import net.modificationstation.stationapi.api.util.Identifier; /** * Static reference of an effect. Since {@link EntityEffect} is initialized per-entity, a separate class is required @@ -24,6 +25,29 @@ private EntityEffectType(Builder builder) { factory = builder.factory; } + /** + * Returns the identifier of the effect + */ + public Identifier getIdentifier() { + return registryEntry.registryKey().getValue(); + } + + /** + * Returns the translation key of the effect's name. + */ + public String getTranslationKey() { + Identifier identifier = registryEntry.registryKey().getValue(); + return "gui.stationapi.effect." + identifier.namespace + "." + identifier.path + ".name"; + } + + /** + * Returns the translation key of the effect's description. + */ + public String getDescriptionTranslationKey() { + Identifier identifier = registryEntry.registryKey().getValue(); + return "gui.stationapi.effect." + identifier.namespace + "." + identifier.path + ".desc"; + } + /** * @param factory the effect instance's factory which takes an {@link net.minecraft.entity.Entity} argument * of the entity the effect's been applied to, and an int argument defining the duration diff --git a/station-effects-api-v0/src/main/java/net/modificationstation/stationapi/impl/effect/EffectsRenderer.java b/station-effects-api-v0/src/main/java/net/modificationstation/stationapi/impl/effect/EffectsRenderer.java index dd27dc80f..beee1e492 100644 --- a/station-effects-api-v0/src/main/java/net/modificationstation/stationapi/impl/effect/EffectsRenderer.java +++ b/station-effects-api-v0/src/main/java/net/modificationstation/stationapi/impl/effect/EffectsRenderer.java @@ -40,8 +40,8 @@ public void renderEffects(Minecraft minecraft, float delta, boolean extended) { int py = 2; for (EntityEffect effect : renderEffects) { if (extended) { - String name = effect.getName(); - String desc = effect.getDescription(); + String name = effect.getTranslatedName(); + String desc = effect.getTranslatedDescription(); int width = Math.max( textRenderer.getWidth(name), textRenderer.getWidth(desc) @@ -127,8 +127,8 @@ private void renderEffectBack(Minecraft minecraft, int y, int width) { private int getEffectWidth(EntityEffect effect) { if (effect.isInfinite()) return 26; - String name = effect.getName(); - String desc = effect.getDescription(); + String name = effect.getTranslatedName(); + String desc = effect.getTranslatedDescription(); return Math.max( textRenderer.getWidth(name), textRenderer.getWidth(desc) diff --git a/station-maths-v0/src/main/java/net/modificationstation/stationapi/api/util/math/StationBlockPos.java b/station-maths-v0/src/main/java/net/modificationstation/stationapi/api/util/math/StationBlockPos.java index 3c4aff045..2c463d1fc 100644 --- a/station-maths-v0/src/main/java/net/modificationstation/stationapi/api/util/math/StationBlockPos.java +++ b/station-maths-v0/src/main/java/net/modificationstation/stationapi/api/util/math/StationBlockPos.java @@ -41,14 +41,14 @@ static BlockPos create(double x, double y, double z) { return new BlockPos(floor(x), floor(y), floor(z)); } - static BlockPos create(Vec3d pos) { - return create(pos.x, pos.y, pos.z); - } - static BlockPos create(Position pos) { return create(pos.getX(), pos.getY(), pos.getZ()); } + static BlockPos create(Vec3d pos) { + return create(pos.x, pos.y, pos.z); + } + static BlockPos create(Vec3i pos) { return new BlockPos(pos.getX(), pos.getY(), pos.getZ()); } @@ -336,23 +336,55 @@ default long asLong() { return asLong(getX(), getY(), getZ()); } - default BlockPos add(double d, double e, double f) { + default BlockPos add(double x, double y, double z) { return Util.assertImpl(); } - default BlockPos add(int i, int j, int k) { + default BlockPos add(int x, int y, int z) { return Util.assertImpl(); } + default BlockPos add(BlockPos blockPos) { + return add(blockPos.x, blockPos.y, blockPos.z); + } + + default BlockPos add(Position position) { + return add(position.getX(), position.getY(), position.getZ()); + } + + default BlockPos add(Vec3d vec3d) { + return add(vec3d.x, vec3d.y, vec3d.z); + } + default BlockPos add(Vec3i vec3i) { return add(vec3i.getX(), vec3i.getY(), vec3i.getZ()); } + default BlockPos subtract(double x, double y, double z) { + return add(-x, -y, -z); + } + + default BlockPos subtract(int x, int y, int z) { + return add(-x, -y, -z); + } + + default BlockPos subtract(BlockPos blockPos) { + return add(-blockPos.x, -blockPos.y, -blockPos.z); + } + + default BlockPos subtract(Position position) { + return add(-position.getX(), -position.getY(), -position.getZ()); + } + + default BlockPos subtract(Vec3d vec3d) { + return add(-vec3d.x, -vec3d.y, -vec3d.z); + } + default BlockPos subtract(Vec3i vec3i) { return add(-vec3i.getX(), -vec3i.getY(), -vec3i.getZ()); } - default BlockPos multiply(int i) { + default BlockPos multiply(int multiplier) { return Util.assertImpl(); } diff --git a/station-maths-v0/src/main/java/net/modificationstation/stationapi/mixin/maths/TilePosMixin.java b/station-maths-v0/src/main/java/net/modificationstation/stationapi/mixin/maths/BlockPosMixin.java similarity index 92% rename from station-maths-v0/src/main/java/net/modificationstation/stationapi/mixin/maths/TilePosMixin.java rename to station-maths-v0/src/main/java/net/modificationstation/stationapi/mixin/maths/BlockPosMixin.java index 7aadd2cf5..b2675068c 100644 --- a/station-maths-v0/src/main/java/net/modificationstation/stationapi/mixin/maths/TilePosMixin.java +++ b/station-maths-v0/src/main/java/net/modificationstation/stationapi/mixin/maths/BlockPosMixin.java @@ -10,7 +10,7 @@ import org.spongepowered.asm.mixin.Shadow; @Mixin(BlockPos.class) -public class TilePosMixin implements StationBlockPos { +public class BlockPosMixin implements StationBlockPos { @Shadow @Final public int x; @Shadow @Final public int y; @@ -56,12 +56,12 @@ public BlockPos subtract(Vec3i vec3i) { } @Override - public BlockPos multiply(int i) { - return switch (i) { + public BlockPos multiply(int multiplier) { + return switch (multiplier) { case 1 -> //noinspection DataFlowIssue (BlockPos) (Object) this; case 0 -> ORIGIN; - default -> new BlockPos(getX() * i, getY() * i, getZ() * i); + default -> new BlockPos(getX() * multiplier, getY() * multiplier, getZ() * multiplier); }; } diff --git a/station-maths-v0/src/main/resources/station-maths-v0.mixins.json b/station-maths-v0/src/main/resources/station-maths-v0.mixins.json index 153251cdd..dd396faed 100644 --- a/station-maths-v0/src/main/resources/station-maths-v0.mixins.json +++ b/station-maths-v0/src/main/resources/station-maths-v0.mixins.json @@ -5,7 +5,7 @@ "compatibilityLevel": "JAVA_17", "mixins": [ "TilePosAccessor", - "TilePosMixin" + "BlockPosMixin" ], "injectors": { "defaultRequire": 1 diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/UnbakedModelLoadingFinishedEvent.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/UnbakedModelLoadingFinishedEvent.java new file mode 100644 index 000000000..fada8e564 --- /dev/null +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/UnbakedModelLoadingFinishedEvent.java @@ -0,0 +1,29 @@ +package net.modificationstation.stationapi.api.client.event.render.model; + +import net.mine_diver.unsafeevents.Event; +import net.mine_diver.unsafeevents.event.EventPhases; +import net.modificationstation.stationapi.api.StationAPI; +import net.modificationstation.stationapi.api.client.color.block.BlockColors; +import net.modificationstation.stationapi.api.client.render.model.ModelLoader; +import net.modificationstation.stationapi.api.client.render.model.UnbakedModel; +import net.modificationstation.stationapi.api.util.Identifier; + +import java.util.List; +import java.util.Map; + +@EventPhases(StationAPI.INTERNAL_PHASE) +public class UnbakedModelLoadingFinishedEvent extends Event { + public ModelLoader modelLoader; + public Map unbakedModels; + public Map modelsToBake; + public Map> blockStates; + public BlockColors blockColors; + + public UnbakedModelLoadingFinishedEvent(ModelLoader modelLoader, Map unbakedModels, Map modelsToBake, Map> blockStates, BlockColors blockColors) { + this.modelLoader = modelLoader; + this.unbakedModels = unbakedModels; + this.modelsToBake = modelsToBake; + this.blockStates = blockStates; + this.blockColors = blockColors; + } +} diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java index aea0398ca..d6348cacc 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java @@ -187,7 +187,7 @@ private void upload(BakingResult bakingResult, Profiler profiler) { profiler.endTick(); } - record BakingResult(ModelLoader modelLoader, BakedModel missingModel, Map modelCache, Map atlasPreparations, CompletableFuture readyForUpload) {} + public record BakingResult(ModelLoader modelLoader, BakedModel missingModel, Map modelCache, Map atlasPreparations, CompletableFuture readyForUpload) {} @Override public Identifier getId() { diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedQuad.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedQuad.java index 74818cef4..2dd45d222 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedQuad.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedQuad.java @@ -43,6 +43,10 @@ public Direction getFace() { return this.face; } + public Sprite getSprite() { + return sprite; + } + public boolean hasShade() { return this.shade; } diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/ModelLoader.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/ModelLoader.java index 23ab77d35..3833f1fa9 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/ModelLoader.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/ModelLoader.java @@ -17,6 +17,7 @@ import net.modificationstation.stationapi.api.block.BlockStateHolder; import net.modificationstation.stationapi.api.client.color.block.BlockColors; import net.modificationstation.stationapi.api.client.event.render.model.LoadUnbakedModelEvent; +import net.modificationstation.stationapi.api.client.event.render.model.UnbakedModelLoadingFinishedEvent; import net.modificationstation.stationapi.api.client.event.render.model.PreLoadUnbakedModelEvent; import net.modificationstation.stationapi.api.client.render.block.BlockModels; import net.modificationstation.stationapi.api.client.render.model.json.JsonUnbakedModel; @@ -55,7 +56,6 @@ @Environment(EnvType.CLIENT) public class ModelLoader { - public static final List BLOCK_DESTRUCTION_STAGES = IntStream.range(0, 10).mapToObj(stage -> Identifier.of("block/destroy_stage_" + stage)).collect(Collectors.toList()); public static final List BLOCK_DESTRUCTION_STAGE_TEXTURES = BLOCK_DESTRUCTION_STAGES.stream().map(id -> Identifier.of(NAMESPACE + "/textures/" + id.path + ".png")).collect(Collectors.toList()); public static final ModelIdentifier MISSING_ID; @@ -87,6 +87,7 @@ public ModelLoader(BlockColors blockColors, Profiler profiler, Map stateManager.getStates().forEach(state -> this.addModel(BlockModels.getModelId(id, state).asIdentifier()))); + profiler.swap("blocks"); for (Block block : BlockRegistry.INSTANCE) block.getStateManager().getStates().forEach(state -> this.addModel(BlockModels.getModelId(state).asIdentifier())); + profiler.swap("items"); for (Identifier identifier : ItemRegistry.INSTANCE.getIds()) this.addModel(ModelIdentifier.of(identifier, "inventory").asIdentifier()); + profiler.swap("special"); this.modelsToBake.values().forEach(model -> model.setParents(this::getOrLoadModel)); + profiler.pop(); + StationAPI.EVENT_BUS.post(new UnbakedModelLoadingFinishedEvent(this, this.unbakedModels, this.modelsToBake, this.blockStates, this.blockColors)); } public void bake(BiFunction spriteLoader) { diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/WeightedBakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/WeightedBakedModel.java index a9059606f..c77c57a33 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/WeightedBakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/WeightedBakedModel.java @@ -32,6 +32,10 @@ public WeightedBakedModel(List models) { public ImmutableList getQuads(@Nullable BlockState state, @Nullable Direction face, Random random) { return Objects.requireNonNull(WeightedPicker.getAt(this.models, Math.abs((int) random.nextLong()) % this.totalWeight)).model.getQuads(state, face, random); } + + public Entry pickModel(Random random) { + return WeightedPicker.getAt(this.models, Math.abs((int) random.nextLong()) % this.totalWeight); + } public boolean useAmbientOcclusion() { return this.defaultModel.useAmbientOcclusion(); @@ -61,14 +65,30 @@ public ModelOverrideList getOverrides() { return this.defaultModel.getOverrides(); } + public int getTotalWeight() { + return totalWeight; + } + + public List getModels() { + return models; + } + + public BakedModel getDefaultModel() { + return defaultModel; + } + @Environment(EnvType.CLIENT) - static class Entry extends WeightedPicker.Entry { + public static class Entry extends WeightedPicker.Entry { protected final BakedModel model; public Entry(BakedModel model, int weight) { super(weight); this.model = model; } + + public BakedModel getModel() { + return model; + } } @Environment(EnvType.CLIENT) diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java index ebe10b9d7..16669ca54 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java @@ -29,7 +29,6 @@ import static net.modificationstation.stationapi.impl.client.texture.StationRenderImpl.LOGGER; public final class JsonUnbakedModel implements UnbakedModel { - public static final Identifier BUILTIN_GENERATED = Identifier.of("builtin/generated"); private static final BakedQuadFactory QUAD_FACTORY = new BakedQuadFactory(); @@ -94,6 +93,18 @@ private ModelOverrideList compileOverrides(Baker baker, JsonUnbakedModel parent) return this.overrides.isEmpty() ? ModelOverrideList.EMPTY : new ModelOverrideList(baker, parent, baker::getOrLoadModel, this.overrides); } + public @Nullable JsonUnbakedModel getParent() { + return parent; + } + + public @Nullable Identifier getParentId() { + return parentId; + } + + public Map> getTextureMap() { + return textureMap; + } + public Collection getModelDependencies() { ReferenceSet set = new ReferenceOpenHashSet<>(); for (ModelOverride modelOverride : this.overrides) set.add(modelOverride.getModelId()); From 4c58d72626d529c3a671a66113737a7bb9929fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Mon, 29 Jun 2026 18:25:10 +0200 Subject: [PATCH 02/26] Fix a Dany L Moment --- .../stationapi/mixin/StationRecipesMixinPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/station-recipes-v0/src/main/java/net/modificationstation/stationapi/mixin/StationRecipesMixinPlugin.java b/station-recipes-v0/src/main/java/net/modificationstation/stationapi/mixin/StationRecipesMixinPlugin.java index 481fa44e1..0611ec5c7 100644 --- a/station-recipes-v0/src/main/java/net/modificationstation/stationapi/mixin/StationRecipesMixinPlugin.java +++ b/station-recipes-v0/src/main/java/net/modificationstation/stationapi/mixin/StationRecipesMixinPlugin.java @@ -37,7 +37,7 @@ public List getMixins() { return null; } - if (FabricLoader.getInstance().isModLoaded("alwaysmoreitems")) { + if (!FabricLoader.getInstance().isModLoaded("alwaysmoreitems")) { return null; } From 869ac437549c05412194c2c6a332423d06d6be7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Mon, 29 Jun 2026 18:50:15 +0200 Subject: [PATCH 03/26] BlockStateReloadEvent --- .../sltest/render/ModelLoadingListener.java | 27 ++++++ src/test/resources/fabric.mod.json | 3 +- .../render/model/BlockStateReloadEvent.java | 97 +++++++++++++++++++ .../render/model/BakedModelManager.java | 6 +- 4 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java create mode 100644 station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BlockStateReloadEvent.java diff --git a/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java b/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java new file mode 100644 index 000000000..168b3cc26 --- /dev/null +++ b/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java @@ -0,0 +1,27 @@ +package net.modificationstation.sltest.render; + +import net.mine_diver.unsafeevents.listener.EventListener; +import net.modificationstation.sltest.SLTest; +import net.modificationstation.stationapi.api.client.event.render.model.BlockStateReloadEvent; +import net.modificationstation.stationapi.api.client.event.render.model.UnbakedModelLoadingFinishedEvent; +import net.modificationstation.stationapi.api.util.Identifier; + +public class ModelLoadingListener { + @EventListener + public void modelsLodaded(UnbakedModelLoadingFinishedEvent event) { + SLTest.LOGGER.info(event.unbakedModels.size() + " models loaded"); + } + + String blockstateJson = (""" + { + "variants": { + "": {"model": "sltest:block/test_block"} + } + }""" + ); + + @EventListener + public void injectBlockState(BlockStateReloadEvent event) { + event.addBlockstateJsonString(Identifier.of("minecraft:sponge"), blockstateJson); + } +} diff --git a/src/test/resources/fabric.mod.json b/src/test/resources/fabric.mod.json index f77d035c0..12056434e 100644 --- a/src/test/resources/fabric.mod.json +++ b/src/test/resources/fabric.mod.json @@ -41,7 +41,8 @@ "net.modificationstation.sltest.option.OptionListener", "net.modificationstation.sltest.keyboard.KeyboardListener", "net.modificationstation.sltest.texture.TextureListener", - "net.modificationstation.sltest.render.entity.EntityRendererListener" + "net.modificationstation.sltest.render.entity.EntityRendererListener", + "net.modificationstation.sltest.render.ModelLoadingListener" ], "main": [ "net.modificationstation.sltest.MainTest" diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BlockStateReloadEvent.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BlockStateReloadEvent.java new file mode 100644 index 000000000..5ba22657d --- /dev/null +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BlockStateReloadEvent.java @@ -0,0 +1,97 @@ +package net.modificationstation.stationapi.api.client.event.render.model; + +import com.mojang.datafixers.util.Pair; +import net.mine_diver.unsafeevents.Event; +import net.mine_diver.unsafeevents.event.EventPhases; +import net.modificationstation.stationapi.api.StationAPI; +import net.modificationstation.stationapi.api.client.render.model.ModelLoader; +import net.modificationstation.stationapi.api.resource.Resource; +import net.modificationstation.stationapi.api.resource.ResourceManager; +import net.modificationstation.stationapi.api.util.Identifier; +import net.modificationstation.stationapi.api.util.JsonHelper; + +import java.io.BufferedReader; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import static net.modificationstation.stationapi.impl.client.texture.StationRenderImpl.LOGGER; + +/** + * An event which is fired when block states are being reloaded + */ +@EventPhases(StationAPI.INTERNAL_PHASE) +public class BlockStateReloadEvent extends Event { + public final ResourceManager resourceManager; + public final Executor executor; + public final List>>> states; + + public BlockStateReloadEvent(ResourceManager resourceManager, Executor executor, List>>> states) { + this.resourceManager = resourceManager; + this.executor = executor; + this.states = states; + } + + /** + * Adds a blockstate json to the list of blockstates to be loaded + * + * @param blockIdentifier The {@link Identifier} of the block + * @param jsons A string representation of the blockstate json to be deserialized + */ + public void addBlockstateJsonString(Identifier blockIdentifier, ArrayList jsons) { + Identifier id = Identifier.of(blockIdentifier.namespace + ":stationapi/blockstates/" + blockIdentifier.path + ".json"); + + states.add(CompletableFuture.supplyAsync(() -> { + List statesList = new ArrayList<>(jsons.size()); + for (String jsonString : jsons) + try { + statesList.add(new ModelLoader.SourceTrackedData("Default", JsonHelper.deserialize(jsonString))); + } catch (Exception exception) { + LOGGER.error("Failed to load blockstate {} from pack {}", blockIdentifier, "Default", exception); + } + return Pair.of(id, statesList); + }, executor)); + } + + /** + * Adds a blockstate json to the list of blockstates to be loaded + * + * @param blockIdentifier The {@link Identifier} of the block + * @param json Resource to be read and deserialized + */ + public void addBlockstateJsonString(Identifier blockIdentifier, String json) { + addBlockstateJsonString(blockIdentifier, new ArrayList<>(List.of(json))); + } + + /** + * Adds a resource to the list of blockstates to be loaded + * + * @param blockIdentifier The {@link Identifier} of the block + * @param resources Resources to be read and deserialized + */ + public void addBlockstateJsonResource(Identifier blockIdentifier, ArrayList resources) { + Identifier id = Identifier.of(blockIdentifier.namespace + ":stationapi/blockstates/" + blockIdentifier.path + ".json"); + + states.add(CompletableFuture.supplyAsync(() -> { + List list2 = new ArrayList<>(resources.size()); + for (Resource resource : resources) + try (BufferedReader reader = resource.getReader()) { + list2.add(new ModelLoader.SourceTrackedData("Default", JsonHelper.deserialize(reader))); + } catch (Exception exception) { + LOGGER.error("Failed to load blockstate {} from pack {}", blockIdentifier, "Default", exception); + } + return Pair.of(id, list2); + }, executor)); + } + + /** + * Adds a resource to the list of blockstates to be loaded + * + * @param blockIdentifier The {@link Identifier} of the block + * @param resource Resource to be read and deserialized + */ + public void addBlockStateJsonResource(Identifier blockIdentifier, Resource resource) { + addBlockstateJsonResource(blockIdentifier, new ArrayList<>(List.of(resource))); + } +} diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java index d6348cacc..a9abc82e4 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java @@ -12,8 +12,10 @@ import net.minecraft.block.Block; import net.minecraft.client.Minecraft; import net.minecraft.client.texture.TextureManager; +import net.modificationstation.stationapi.api.StationAPI; import net.modificationstation.stationapi.api.block.BlockState; import net.modificationstation.stationapi.api.client.color.block.BlockColors; +import net.modificationstation.stationapi.api.client.event.render.model.BlockStateReloadEvent; import net.modificationstation.stationapi.api.client.render.block.BlockModels; import net.modificationstation.stationapi.api.client.render.model.json.JsonUnbakedModel; import net.modificationstation.stationapi.api.client.texture.Sprite; @@ -129,7 +131,8 @@ private static CompletableFuture> reloadModels private static CompletableFuture>> reloadBlockStates(ResourceManager resourceManager, Executor executor) { return CompletableFuture.supplyAsync(() -> ModelLoader.BLOCK_STATES_FINDER.findAllResources(resourceManager), executor).thenCompose(blockStates2 -> { List>>> list = new ArrayList<>(blockStates2.size()); - for (Map.Entry> entry : blockStates2.entrySet()) + StationAPI.EVENT_BUS.post(new BlockStateReloadEvent(resourceManager, executor, list)); + for (Map.Entry> entry : blockStates2.entrySet()) { list.add(CompletableFuture.supplyAsync(() -> { List resources = entry.getValue(); List list2 = new ArrayList<>(resources.size()); @@ -141,6 +144,7 @@ private static CompletableFuture blockStates.stream().filter(Objects::nonNull).collect(Collectors.toUnmodifiableMap(Pair::getFirst, Pair::getSecond))); }); } From 0c23b00ab27cd006ccf94faae912ec802b85c0b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Mon, 29 Jun 2026 18:50:24 +0200 Subject: [PATCH 04/26] Cant run without this --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index bfc3c0ad7..0605addce 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -147,6 +147,7 @@ allprojects { // this fixes some edge cases with special characters not displaying correctly // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html tasks.withType { + options.release = 17 options.encoding = "UTF-8" } From 70633749c86c48892e6c5f9a56b0a36df8013811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Mon, 29 Jun 2026 20:01:14 +0200 Subject: [PATCH 05/26] oops --- .../modificationstation/sltest/render/ModelLoadingListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java b/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java index 168b3cc26..82d808214 100644 --- a/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java +++ b/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java @@ -8,7 +8,7 @@ public class ModelLoadingListener { @EventListener - public void modelsLodaded(UnbakedModelLoadingFinishedEvent event) { + public void modelsLoaded(UnbakedModelLoadingFinishedEvent event) { SLTest.LOGGER.info(event.unbakedModels.size() + " models loaded"); } From b4705e9f7173d024520c3433474625a5af31b460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Mon, 29 Jun 2026 21:03:47 +0200 Subject: [PATCH 06/26] Brought back from the dead --- .../api/client/render/model/UnbakedModel.java | 4 ++ .../render/model/json/JsonUnbakedModel.java | 56 +++++++++++++++++++ .../model/json/MultipartUnbakedModel.java | 6 ++ .../model/json/WeightedUnbakedModel.java | 7 +++ 4 files changed, 73 insertions(+) diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/UnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/UnbakedModel.java index 768599d93..7bf06a0cb 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/UnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/UnbakedModel.java @@ -1,5 +1,6 @@ package net.modificationstation.stationapi.api.client.render.model; +import com.mojang.datafixers.util.Pair; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.modificationstation.stationapi.api.client.texture.Sprite; @@ -8,6 +9,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; +import java.util.Set; import java.util.function.Function; @Environment(EnvType.CLIENT) @@ -16,6 +18,8 @@ public interface UnbakedModel { void setParents(Function parents); + Collection getTextureDependencies(Function unbakedModelGetter, Set> unresolvedTextureReferences); + @Nullable BakedModel bake(Baker baker, Function textureGetter, ModelBakeSettings rotationContainer, Identifier modelId); } diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java index 16669ca54..1511ba4ca 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java @@ -3,8 +3,10 @@ import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import com.google.gson.*; import com.mojang.datafixers.util.Either; +import com.mojang.datafixers.util.Pair; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import it.unimi.dsi.fastutil.objects.ReferenceSet; import net.fabricmc.api.EnvType; @@ -141,6 +143,60 @@ public void setParents(Function modelLoader) { }); } + @Override + public Collection getTextureDependencies(Function unbakedModelGetter, Set> unresolvedTextureReferences) { + Set set = Sets.newLinkedHashSet(); + + for(JsonUnbakedModel JsonModel = this; JsonModel.parentId != null && JsonModel.parent == null; JsonModel = JsonModel.parent) { + set.add(JsonModel); + UnbakedModel unbakedModel = unbakedModelGetter.apply(JsonModel.parentId); + if (unbakedModel == null) { + LOGGER.warn("No parent '{}' while loading model '{}'", this.parentId, JsonModel); + } + + if (set.contains(unbakedModel)) { + LOGGER.warn("Found 'parent' loop while loading model '{}' in chain: {} -> {}", JsonModel, set.stream().map(Object::toString).collect(Collectors.joining(" -> ")), this.parentId); + unbakedModel = null; + } + + if (unbakedModel == null) { + JsonModel.parentId = ModelLoader.MISSING_ID.asIdentifier(); + unbakedModel = unbakedModelGetter.apply(JsonModel.parentId); + } + + if (!(unbakedModel instanceof JsonUnbakedModel jsonModel)) { + throw new IllegalStateException("BlockModel parent has to be a block model."); + } + + JsonModel.parent = jsonModel; + } + + Set set2 = Sets.newHashSet(this.resolveSprite("particle")); + + for (ModelElement modelElement : this.getElements()) { + SpriteIdentifier spriteIdentifier; + for (ModelElementFace modelElementFace : modelElement.faces.values()) { + spriteIdentifier = this.resolveSprite(modelElementFace.textureId); + if (spriteIdentifier.texture == MissingSprite.getMissingSpriteId()) { + unresolvedTextureReferences.add(Pair.of(modelElementFace.textureId, this.id)); + } + set2.add(spriteIdentifier); + } + } + + this.overrides.forEach((modelOverride) -> { + UnbakedModel unbakedModel = unbakedModelGetter.apply(modelOverride.getModelId()); + if (!Objects.equals(unbakedModel, this)) { + set2.addAll(unbakedModel.getTextureDependencies(unbakedModelGetter, unresolvedTextureReferences)); + } + }); + if (this.getRootModel() == ModelLoader.GENERATION_MARKER) { + ItemModelGenerator.LAYERS.forEach((string) -> set2.add(this.resolveSprite(string))); + } + + return set2; + } + @Override public BakedModel bake(Baker baker, Function textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { return this.bake(baker, this, textureGetter, rotationContainer, modelId, true); diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/MultipartUnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/MultipartUnbakedModel.java index 2ea1a48da..ff143898c 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/MultipartUnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/MultipartUnbakedModel.java @@ -3,6 +3,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.gson.*; +import com.mojang.datafixers.util.Pair; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.block.Block; @@ -69,6 +70,11 @@ public void setParents(Function modelLoader) { this.getComponents().forEach(component -> component.getModel().setParents(modelLoader)); } + @Override + public Collection getTextureDependencies(Function unbakedModelGetter, Set> unresolvedTextureReferences) { + return this.getComponents().stream().flatMap((multipartModelComponent) -> multipartModelComponent.getModel().getTextureDependencies(unbakedModelGetter, unresolvedTextureReferences).stream()).collect(Collectors.toSet()); + } + @Nullable public BakedModel bake(Baker baker, Function textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { MultipartBakedModel.Builder builder = new MultipartBakedModel.Builder(); diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/WeightedUnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/WeightedUnbakedModel.java index 0d83ecd0b..bc0d63a80 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/WeightedUnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/WeightedUnbakedModel.java @@ -2,6 +2,7 @@ import com.google.common.collect.Lists; import com.google.gson.*; +import com.mojang.datafixers.util.Pair; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.modificationstation.stationapi.api.client.render.model.*; @@ -13,6 +14,7 @@ import java.lang.reflect.Type; import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -52,6 +54,11 @@ public void setParents(Function modelLoader) { this.getVariants().stream().map(ModelVariant::getLocation).distinct().forEach(id -> modelLoader.apply(id).setParents(modelLoader)); } + @Override + public Collection getTextureDependencies(Function unbakedModelGetter, Set> unresolvedTextureReferences) { + return this.getVariants().stream().map(ModelVariant::getLocation).distinct().flatMap((identifier) -> unbakedModelGetter.apply(identifier).getTextureDependencies(unbakedModelGetter, unresolvedTextureReferences).stream()).collect(Collectors.toSet()); + } + @Nullable public BakedModel bake(Baker baker, Function textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { if (this.getVariants().isEmpty()) { From 558b603145a36805dc7e2a5b16e7aafdc387019c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Mon, 29 Jun 2026 21:11:02 +0200 Subject: [PATCH 07/26] Code stealing moment --- .../api/client/render/model/json/JsonUnbakedModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java index 1511ba4ca..8a303ae8e 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java @@ -144,7 +144,7 @@ public void setParents(Function modelLoader) { } @Override - public Collection getTextureDependencies(Function unbakedModelGetter, Set> unresolvedTextureReferences) { + public Collection getTextureDependencies(Function unbakedModelGetter, Set> unresolvedTextureReferences) { Set set = Sets.newLinkedHashSet(); for(JsonUnbakedModel JsonModel = this; JsonModel.parentId != null && JsonModel.parent == null; JsonModel = JsonModel.parent) { From 2bc5b1037ad19f18136c32e857a37dd5e34920b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Mon, 29 Jun 2026 21:28:55 +0200 Subject: [PATCH 08/26] Fix getTextureDependencies --- .../render/model/json/JsonUnbakedModel.java | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java index 8a303ae8e..01cb3bd27 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java @@ -144,57 +144,59 @@ public void setParents(Function modelLoader) { } @Override - public Collection getTextureDependencies(Function unbakedModelGetter, Set> unresolvedTextureReferences) { + public Collection getTextureDependencies(Function modelLoader, Set> unresolvedTextureReferences) { Set set = Sets.newLinkedHashSet(); - for(JsonUnbakedModel JsonModel = this; JsonModel.parentId != null && JsonModel.parent == null; JsonModel = JsonModel.parent) { - set.add(JsonModel); - UnbakedModel unbakedModel = unbakedModelGetter.apply(JsonModel.parentId); - if (unbakedModel == null) { - LOGGER.warn("No parent '{}' while loading model '{}'", this.parentId, JsonModel); - } - + JsonUnbakedModel jsonUnbakedModel = this; + while (jsonUnbakedModel.parentId != null && jsonUnbakedModel.parent == null) { + set.add(jsonUnbakedModel); + + UnbakedModel unbakedModel = modelLoader.apply(jsonUnbakedModel.parentId); + if (unbakedModel == null) + LOGGER.warn("No parent '{}' while loading model '{}'", this.parentId, jsonUnbakedModel); if (set.contains(unbakedModel)) { - LOGGER.warn("Found 'parent' loop while loading model '{}' in chain: {} -> {}", JsonModel, set.stream().map(Object::toString).collect(Collectors.joining(" -> ")), this.parentId); + LOGGER.warn("Found 'parent' loop while loading model '{}' in chain: {} -> {}", jsonUnbakedModel, set.stream().map(Object::toString).collect(Collectors.joining(" -> ")), this.parentId); unbakedModel = null; } - + if (unbakedModel == null) { - JsonModel.parentId = ModelLoader.MISSING_ID.asIdentifier(); - unbakedModel = unbakedModelGetter.apply(JsonModel.parentId); + unbakedModel = modelLoader.apply(ModelLoader.MISSING_ID.asIdentifier()); } - - if (!(unbakedModel instanceof JsonUnbakedModel jsonModel)) { + + if (!(unbakedModel instanceof JsonUnbakedModel)) { throw new IllegalStateException("BlockModel parent has to be a block model."); } - - JsonModel.parent = jsonModel; + + jsonUnbakedModel = jsonUnbakedModel.parent; } - Set set2 = Sets.newHashSet(this.resolveSprite("particle")); + Set dependencies = Sets.newHashSet(this.resolveSprite("particle")); for (ModelElement modelElement : this.getElements()) { SpriteIdentifier spriteIdentifier; for (ModelElementFace modelElementFace : modelElement.faces.values()) { spriteIdentifier = this.resolveSprite(modelElementFace.textureId); + if (spriteIdentifier.texture == MissingSprite.getMissingSpriteId()) { unresolvedTextureReferences.add(Pair.of(modelElementFace.textureId, this.id)); } - set2.add(spriteIdentifier); + + dependencies.add(spriteIdentifier); } } this.overrides.forEach((modelOverride) -> { - UnbakedModel unbakedModel = unbakedModelGetter.apply(modelOverride.getModelId()); - if (!Objects.equals(unbakedModel, this)) { - set2.addAll(unbakedModel.getTextureDependencies(unbakedModelGetter, unresolvedTextureReferences)); + UnbakedModel overrideModel = modelLoader.apply(modelOverride.getModelId()); + if (!Objects.equals(overrideModel, this)) { + dependencies.addAll(overrideModel.getTextureDependencies(modelLoader, unresolvedTextureReferences)); } }); + if (this.getRootModel() == ModelLoader.GENERATION_MARKER) { - ItemModelGenerator.LAYERS.forEach((string) -> set2.add(this.resolveSprite(string))); + ItemModelGenerator.LAYERS.forEach((string) -> dependencies.add(this.resolveSprite(string))); } - return set2; + return dependencies; } @Override From 71e405941857d663d61013557a471c945a1506f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Mon, 29 Jun 2026 21:29:47 +0200 Subject: [PATCH 09/26] proper names --- .../stationapi/api/client/render/model/UnbakedModel.java | 2 +- .../api/client/render/model/json/MultipartUnbakedModel.java | 4 ++-- .../api/client/render/model/json/WeightedUnbakedModel.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/UnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/UnbakedModel.java index 7bf06a0cb..066da58d9 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/UnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/UnbakedModel.java @@ -18,7 +18,7 @@ public interface UnbakedModel { void setParents(Function parents); - Collection getTextureDependencies(Function unbakedModelGetter, Set> unresolvedTextureReferences); + Collection getTextureDependencies(Function modelLoader, Set> unresolvedTextureReferences); @Nullable BakedModel bake(Baker baker, Function textureGetter, ModelBakeSettings rotationContainer, Identifier modelId); diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/MultipartUnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/MultipartUnbakedModel.java index ff143898c..b136f7129 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/MultipartUnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/MultipartUnbakedModel.java @@ -71,8 +71,8 @@ public void setParents(Function modelLoader) { } @Override - public Collection getTextureDependencies(Function unbakedModelGetter, Set> unresolvedTextureReferences) { - return this.getComponents().stream().flatMap((multipartModelComponent) -> multipartModelComponent.getModel().getTextureDependencies(unbakedModelGetter, unresolvedTextureReferences).stream()).collect(Collectors.toSet()); + public Collection getTextureDependencies(Function modelLoader, Set> unresolvedTextureReferences) { + return this.getComponents().stream().flatMap((multipartModelComponent) -> multipartModelComponent.getModel().getTextureDependencies(modelLoader, unresolvedTextureReferences).stream()).collect(Collectors.toSet()); } @Nullable diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/WeightedUnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/WeightedUnbakedModel.java index bc0d63a80..6fa4f5aad 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/WeightedUnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/WeightedUnbakedModel.java @@ -55,8 +55,8 @@ public void setParents(Function modelLoader) { } @Override - public Collection getTextureDependencies(Function unbakedModelGetter, Set> unresolvedTextureReferences) { - return this.getVariants().stream().map(ModelVariant::getLocation).distinct().flatMap((identifier) -> unbakedModelGetter.apply(identifier).getTextureDependencies(unbakedModelGetter, unresolvedTextureReferences).stream()).collect(Collectors.toSet()); + public Collection getTextureDependencies(Function modelLoader, Set> unresolvedTextureReferences) { + return this.getVariants().stream().map(ModelVariant::getLocation).distinct().flatMap((identifier) -> modelLoader.apply(identifier).getTextureDependencies(modelLoader, unresolvedTextureReferences).stream()).collect(Collectors.toSet()); } @Nullable From ca0c7ae341e90d361ad5a8b6f25a778b3fc22c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Mon, 29 Jun 2026 22:09:15 +0200 Subject: [PATCH 10/26] Reduced to a helper method --- .../api/client/render/model/UnbakedModel.java | 2 +- .../render/model/json/JsonUnbakedModel.java | 42 +++---------------- .../model/json/MultipartUnbakedModel.java | 4 +- .../model/json/WeightedUnbakedModel.java | 4 +- 4 files changed, 10 insertions(+), 42 deletions(-) diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/UnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/UnbakedModel.java index 066da58d9..0101d4bf3 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/UnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/UnbakedModel.java @@ -18,7 +18,7 @@ public interface UnbakedModel { void setParents(Function parents); - Collection getTextureDependencies(Function modelLoader, Set> unresolvedTextureReferences); + Collection getTextures(Function modelLoader, Set> unresolvedTextureReferences); @Nullable BakedModel bake(Baker baker, Function textureGetter, ModelBakeSettings rotationContainer, Identifier modelId); diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java index 01cb3bd27..31ed4846c 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java @@ -144,33 +144,8 @@ public void setParents(Function modelLoader) { } @Override - public Collection getTextureDependencies(Function modelLoader, Set> unresolvedTextureReferences) { - Set set = Sets.newLinkedHashSet(); - - JsonUnbakedModel jsonUnbakedModel = this; - while (jsonUnbakedModel.parentId != null && jsonUnbakedModel.parent == null) { - set.add(jsonUnbakedModel); - - UnbakedModel unbakedModel = modelLoader.apply(jsonUnbakedModel.parentId); - if (unbakedModel == null) - LOGGER.warn("No parent '{}' while loading model '{}'", this.parentId, jsonUnbakedModel); - if (set.contains(unbakedModel)) { - LOGGER.warn("Found 'parent' loop while loading model '{}' in chain: {} -> {}", jsonUnbakedModel, set.stream().map(Object::toString).collect(Collectors.joining(" -> ")), this.parentId); - unbakedModel = null; - } - - if (unbakedModel == null) { - unbakedModel = modelLoader.apply(ModelLoader.MISSING_ID.asIdentifier()); - } - - if (!(unbakedModel instanceof JsonUnbakedModel)) { - throw new IllegalStateException("BlockModel parent has to be a block model."); - } - - jsonUnbakedModel = jsonUnbakedModel.parent; - } - - Set dependencies = Sets.newHashSet(this.resolveSprite("particle")); + public Collection getTextures(Function modelLoader, Set> unresolvedTextureReferences) { + Set textures = Sets.newHashSet(this.resolveSprite("particle")); for (ModelElement modelElement : this.getElements()) { SpriteIdentifier spriteIdentifier; @@ -181,22 +156,15 @@ public Collection getTextureDependencies(Function { - UnbakedModel overrideModel = modelLoader.apply(modelOverride.getModelId()); - if (!Objects.equals(overrideModel, this)) { - dependencies.addAll(overrideModel.getTextureDependencies(modelLoader, unresolvedTextureReferences)); - } - }); - if (this.getRootModel() == ModelLoader.GENERATION_MARKER) { - ItemModelGenerator.LAYERS.forEach((string) -> dependencies.add(this.resolveSprite(string))); + ItemModelGenerator.LAYERS.forEach((string) -> textures.add(this.resolveSprite(string))); } - return dependencies; + return textures; } @Override diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/MultipartUnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/MultipartUnbakedModel.java index b136f7129..50a1aac50 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/MultipartUnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/MultipartUnbakedModel.java @@ -71,8 +71,8 @@ public void setParents(Function modelLoader) { } @Override - public Collection getTextureDependencies(Function modelLoader, Set> unresolvedTextureReferences) { - return this.getComponents().stream().flatMap((multipartModelComponent) -> multipartModelComponent.getModel().getTextureDependencies(modelLoader, unresolvedTextureReferences).stream()).collect(Collectors.toSet()); + public Collection getTextures(Function modelLoader, Set> unresolvedTextureReferences) { + return this.getComponents().stream().flatMap((multipartModelComponent) -> multipartModelComponent.getModel().getTextures(modelLoader, unresolvedTextureReferences).stream()).collect(Collectors.toSet()); } @Nullable diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/WeightedUnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/WeightedUnbakedModel.java index 6fa4f5aad..43561b3b4 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/WeightedUnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/WeightedUnbakedModel.java @@ -55,8 +55,8 @@ public void setParents(Function modelLoader) { } @Override - public Collection getTextureDependencies(Function modelLoader, Set> unresolvedTextureReferences) { - return this.getVariants().stream().map(ModelVariant::getLocation).distinct().flatMap((identifier) -> modelLoader.apply(identifier).getTextureDependencies(modelLoader, unresolvedTextureReferences).stream()).collect(Collectors.toSet()); + public Collection getTextures(Function modelLoader, Set> unresolvedTextureReferences) { + return this.getVariants().stream().map(ModelVariant::getLocation).distinct().flatMap((identifier) -> modelLoader.apply(identifier).getTextures(modelLoader, unresolvedTextureReferences).stream()).collect(Collectors.toSet()); } @Nullable From d1b10b30bd16ed6146531e1e8d33fc8c635a4d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Tue, 30 Jun 2026 21:25:09 +0200 Subject: [PATCH 11/26] More changes, more events and mine's beloved SuperBuilder --- .../sltest/render/ModelLoadingListener.java | 17 ++++++++ .../model/BeforeModelLoaderInitEvent.java | 20 +++++++++ .../render/model/BlockStateReloadEvent.java | 9 +--- .../model/ModelVariantMapOverrideEvent.java | 42 +++++++++++++++++++ .../UnbakedModelLoadingFinishedEvent.java | 25 ++++------- .../render/model/BakedModelManager.java | 2 +- .../api/client/render/model/ModelLoader.java | 32 ++++++++------ .../render/model/json/JsonUnbakedModel.java | 2 +- 8 files changed, 110 insertions(+), 39 deletions(-) create mode 100644 station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BeforeModelLoaderInitEvent.java create mode 100644 station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/ModelVariantMapOverrideEvent.java diff --git a/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java b/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java index 82d808214..261b1fde0 100644 --- a/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java +++ b/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java @@ -1,9 +1,13 @@ package net.modificationstation.sltest.render; +import com.mojang.datafixers.util.Pair; import net.mine_diver.unsafeevents.listener.EventListener; import net.modificationstation.sltest.SLTest; +import net.modificationstation.stationapi.api.client.event.render.model.BeforeModelLoaderInitEvent; import net.modificationstation.stationapi.api.client.event.render.model.BlockStateReloadEvent; +import net.modificationstation.stationapi.api.client.event.render.model.ModelVariantMapOverrideEvent; import net.modificationstation.stationapi.api.client.event.render.model.UnbakedModelLoadingFinishedEvent; +import net.modificationstation.stationapi.api.client.render.model.json.ModelVariantMap; import net.modificationstation.stationapi.api.util.Identifier; public class ModelLoadingListener { @@ -24,4 +28,17 @@ public void modelsLoaded(UnbakedModelLoadingFinishedEvent event) { public void injectBlockState(BlockStateReloadEvent event) { event.addBlockstateJsonString(Identifier.of("minecraft:sponge"), blockstateJson); } + + @EventListener + public void beforeLoader(BeforeModelLoaderInitEvent event) { + SLTest.LOGGER.info("Model Loader Init"); + } + + @EventListener + public void mineLdiver(ModelVariantMapOverrideEvent event) { + System.err.println(event.id); + for (Pair variantMap : event.variantMaps) { + System.err.println(" - " + variantMap.getFirst() + " | " + variantMap.getSecond().getVariantMap().toString()); + } + } } diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BeforeModelLoaderInitEvent.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BeforeModelLoaderInitEvent.java new file mode 100644 index 000000000..89e87f242 --- /dev/null +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BeforeModelLoaderInitEvent.java @@ -0,0 +1,20 @@ +package net.modificationstation.stationapi.api.client.event.render.model; + +import lombok.experimental.SuperBuilder; +import net.mine_diver.unsafeevents.Event; +import net.modificationstation.stationapi.api.client.color.block.BlockColors; +import net.modificationstation.stationapi.api.client.render.model.ModelLoader; +import net.modificationstation.stationapi.api.client.render.model.UnbakedModel; +import net.modificationstation.stationapi.api.util.Identifier; + +import java.util.List; +import java.util.Map; + +@SuperBuilder +public final class BeforeModelLoaderInitEvent extends Event { + public final ModelLoader modelLoader; + public final Map unbakedModels; + public final Map modelsToBake; + public final Map> blockStates; + public final BlockColors blockColors; +} diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BlockStateReloadEvent.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BlockStateReloadEvent.java index 5ba22657d..28a779424 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BlockStateReloadEvent.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BlockStateReloadEvent.java @@ -1,6 +1,7 @@ package net.modificationstation.stationapi.api.client.event.render.model; import com.mojang.datafixers.util.Pair; +import lombok.experimental.SuperBuilder; import net.mine_diver.unsafeevents.Event; import net.mine_diver.unsafeevents.event.EventPhases; import net.modificationstation.stationapi.api.StationAPI; @@ -21,18 +22,12 @@ /** * An event which is fired when block states are being reloaded */ -@EventPhases(StationAPI.INTERNAL_PHASE) +@SuperBuilder public class BlockStateReloadEvent extends Event { public final ResourceManager resourceManager; public final Executor executor; public final List>>> states; - public BlockStateReloadEvent(ResourceManager resourceManager, Executor executor, List>>> states) { - this.resourceManager = resourceManager; - this.executor = executor; - this.states = states; - } - /** * Adds a blockstate json to the list of blockstates to be loaded * diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/ModelVariantMapOverrideEvent.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/ModelVariantMapOverrideEvent.java new file mode 100644 index 000000000..a2e6a624e --- /dev/null +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/ModelVariantMapOverrideEvent.java @@ -0,0 +1,42 @@ +package net.modificationstation.stationapi.api.client.event.render.model; + +import com.mojang.datafixers.util.Pair; +import lombok.experimental.SuperBuilder; +import net.mine_diver.unsafeevents.Event; +import net.minecraft.block.Block; +import net.modificationstation.stationapi.api.block.BlockState; +import net.modificationstation.stationapi.api.client.render.model.ModelLoader; +import net.modificationstation.stationapi.api.client.render.model.json.ModelVariantMap; +import net.modificationstation.stationapi.api.util.Identifier; +import net.modificationstation.stationapi.api.util.Namespace; + +import java.util.List; + +/** + * Fired after the {@link ModelVariantMap} is deserialized, allowing to add custom overrides + */ +@SuperBuilder +public final class ModelVariantMapOverrideEvent extends Event { + /** + * The model loader that is loading this blockstate + */ + public final ModelLoader modelLoader; + /** + * The {@link Identifier} of the {@link Block} for which the {@link BlockState} is being loaded + */ + public final Identifier id; + /** + * The map of the variants of the {@link BlockState} + */ + public final List> variantMaps; + + /** + * Adds a custom {@link ModelVariantMap} to the list of overrides for the given {@link BlockState} + * + * @param namespace The {@link Namespace} of your mod + * @param variantMap The {@link ModelVariantMap} to add + */ + public void addModelVariantMap(Namespace namespace, ModelVariantMap variantMap) { + this.variantMaps.add(new Pair<>(namespace.getName() + " Model Variant Event Override", variantMap)); + } +} diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/UnbakedModelLoadingFinishedEvent.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/UnbakedModelLoadingFinishedEvent.java index fada8e564..a5c6871b0 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/UnbakedModelLoadingFinishedEvent.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/UnbakedModelLoadingFinishedEvent.java @@ -1,8 +1,7 @@ package net.modificationstation.stationapi.api.client.event.render.model; +import lombok.experimental.SuperBuilder; import net.mine_diver.unsafeevents.Event; -import net.mine_diver.unsafeevents.event.EventPhases; -import net.modificationstation.stationapi.api.StationAPI; import net.modificationstation.stationapi.api.client.color.block.BlockColors; import net.modificationstation.stationapi.api.client.render.model.ModelLoader; import net.modificationstation.stationapi.api.client.render.model.UnbakedModel; @@ -11,19 +10,11 @@ import java.util.List; import java.util.Map; -@EventPhases(StationAPI.INTERNAL_PHASE) -public class UnbakedModelLoadingFinishedEvent extends Event { - public ModelLoader modelLoader; - public Map unbakedModels; - public Map modelsToBake; - public Map> blockStates; - public BlockColors blockColors; - - public UnbakedModelLoadingFinishedEvent(ModelLoader modelLoader, Map unbakedModels, Map modelsToBake, Map> blockStates, BlockColors blockColors) { - this.modelLoader = modelLoader; - this.unbakedModels = unbakedModels; - this.modelsToBake = modelsToBake; - this.blockStates = blockStates; - this.blockColors = blockColors; - } +@SuperBuilder +public final class UnbakedModelLoadingFinishedEvent extends Event { + public final ModelLoader modelLoader; + public final Map unbakedModels; + public final Map modelsToBake; + public final Map> blockStates; + public final BlockColors blockColors; } diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java index a9abc82e4..df026a5d1 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java @@ -131,7 +131,7 @@ private static CompletableFuture> reloadModels private static CompletableFuture>> reloadBlockStates(ResourceManager resourceManager, Executor executor) { return CompletableFuture.supplyAsync(() -> ModelLoader.BLOCK_STATES_FINDER.findAllResources(resourceManager), executor).thenCompose(blockStates2 -> { List>>> list = new ArrayList<>(blockStates2.size()); - StationAPI.EVENT_BUS.post(new BlockStateReloadEvent(resourceManager, executor, list)); + StationAPI.EVENT_BUS.post(BlockStateReloadEvent.builder().resourceManager(resourceManager).executor(executor).states(list).build()); for (Map.Entry> entry : blockStates2.entrySet()) { list.add(CompletableFuture.supplyAsync(() -> { List resources = entry.getValue(); diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/ModelLoader.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/ModelLoader.java index 3833f1fa9..ee7898a72 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/ModelLoader.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/ModelLoader.java @@ -16,9 +16,7 @@ import net.modificationstation.stationapi.api.block.BlockState; import net.modificationstation.stationapi.api.block.BlockStateHolder; import net.modificationstation.stationapi.api.client.color.block.BlockColors; -import net.modificationstation.stationapi.api.client.event.render.model.LoadUnbakedModelEvent; -import net.modificationstation.stationapi.api.client.event.render.model.UnbakedModelLoadingFinishedEvent; -import net.modificationstation.stationapi.api.client.event.render.model.PreLoadUnbakedModelEvent; +import net.modificationstation.stationapi.api.client.event.render.model.*; import net.modificationstation.stationapi.api.client.render.block.BlockModels; import net.modificationstation.stationapi.api.client.render.model.json.JsonUnbakedModel; import net.modificationstation.stationapi.api.client.render.model.json.ModelVariantMap; @@ -88,7 +86,10 @@ public ModelLoader(BlockColors blockColors, Profiler profiler, Map model.setParents(this::getOrLoadModel)); + profiler.swap("after_init_event"); + StationAPI.EVENT_BUS.post(UnbakedModelLoadingFinishedEvent.builder().modelLoader(this).unbakedModels(this.unbakedModels).modelsToBake(this.modelsToBake).blockStates(this.blockStates).blockColors(this.blockColors).build()); + profiler.pop(); - StationAPI.EVENT_BUS.post(new UnbakedModelLoadingFinishedEvent(this, this.unbakedModels, this.modelsToBake, this.blockStates, this.blockColors)); } public void bake(BiFunction spriteLoader) { @@ -222,10 +225,10 @@ private void loadModel(Identifier id) throws Exception { } else { StateManager stateManager = /*Optional.ofNullable(STATIC_DEFINITIONS.get(identifier)).orElseGet(() -> */((BlockStateHolder) Objects.requireNonNull(BlockRegistry.INSTANCE.get(modelIdentifier.id))).getStateManager()/*)*/; variantMapDeserializationContext.setStateFactory(stateManager); - ImmutableList> list = ImmutableList.copyOf(this.blockColors.getProperties(stateManager.getOwner())); - ImmutableList immutableList = stateManager.getStates(); - Map map = new IdentityHashMap<>(); - immutableList.forEach(state -> map.put(BlockModels.getModelId(modelIdentifier.id, state), state)); + ImmutableList> blockColors = ImmutableList.copyOf(this.blockColors.getProperties(stateManager.getOwner())); + ImmutableList states = stateManager.getStates(); + Map idToStateMap = new IdentityHashMap<>(); + states.forEach(state -> idToStateMap.put(BlockModels.getModelId(modelIdentifier.id, state), state)); Map>> map2 = new HashMap<>(); Identifier identifier2 = BLOCK_STATES_FINDER.toResourcePath(modelIdentifier.id); UnbakedModel unbakedModel = VANILLA_MARKER; @@ -239,18 +242,21 @@ private void loadModel(Identifier id) throws Exception { throw new ModelLoaderException(String.format(Locale.ROOT, "Exception loading blockstate definition: '%s' in texturepack: '%s': %s", identifier2, blockState.source, var4.getMessage())); } }).toList(); + + StationAPI.EVENT_BUS.post(ModelVariantMapOverrideEvent.builder().modelLoader(this).id(modelIdentifier.id).variantMaps(list2).build()); + for (Pair pair2 : list2) { MultipartUnbakedModel multipartUnbakedModel; ModelVariantMap modelVariantMap = pair2.getSecond(); Map>> map4 = new IdentityHashMap<>(); if (modelVariantMap.hasMultipartModel()) { multipartUnbakedModel = modelVariantMap.getMultipartModel(); - immutableList.forEach(blockState -> map4.put(blockState, Pair.of(multipartUnbakedModel, () -> ModelDefinition.create(blockState, multipartUnbakedModel, list)))); + states.forEach(blockState -> map4.put(blockState, Pair.of(multipartUnbakedModel, () -> ModelDefinition.create(blockState, multipartUnbakedModel, blockColors)))); } else multipartUnbakedModel = null; modelVariantMap.getVariantMap().forEach((string, weightedUnbakedModel) -> { try { - immutableList.stream().filter(ModelLoader.stateKeyToPredicate(stateManager, string)).forEach(blockState -> { - Pair> pair3 = map4.put(blockState, Pair.of(weightedUnbakedModel, () -> ModelDefinition.create(blockState, weightedUnbakedModel, list))); + states.stream().filter(ModelLoader.stateKeyToPredicate(stateManager, string)).forEach(blockState -> { + Pair> pair3 = map4.put(blockState, Pair.of(weightedUnbakedModel, () -> ModelDefinition.create(blockState, weightedUnbakedModel, blockColors))); if (pair3 != null && pair3.getFirst() != multipartUnbakedModel) { map4.put(blockState, pair); throw new RuntimeException("Overlapping definition with: " + modelVariantMap.getVariantMap().entrySet().stream().filter(entry -> entry.getValue() == pair3.getFirst()).findFirst().orElseThrow(NullPointerException::new).getKey()); @@ -268,7 +274,7 @@ private void loadModel(Identifier id) throws Exception { throw new ModelLoaderException(String.format("Exception loading blockstate definition: '%s': %s", identifier2, exception)); } finally { Map> map6 = new HashMap<>(); - map.forEach((id1, blockState) -> { + idToStateMap.forEach((id1, blockState) -> { Pair> pair2 = map2.get(blockState); // Vanilla blocks don't have JSON models, and we don't want to enforce them // LOGGER.warn("Exception loading blockstate definition: '{}' missing model for variant: '{}'", identifier2, id1); diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java index 31ed4846c..2c1b74d36 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/json/JsonUnbakedModel.java @@ -30,7 +30,7 @@ import static net.modificationstation.stationapi.impl.client.texture.StationRenderImpl.LOGGER; -public final class JsonUnbakedModel implements UnbakedModel { +public class JsonUnbakedModel implements UnbakedModel { public static final Identifier BUILTIN_GENERATED = Identifier.of("builtin/generated"); private static final BakedQuadFactory QUAD_FACTORY = new BakedQuadFactory(); From c97849c5da1ca880437c31eecc4f529042eadb29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Tue, 30 Jun 2026 21:56:16 +0200 Subject: [PATCH 12/26] Fix AMI yelling --- .../net/modificationstation/sltest/item/ItemListener.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/net/modificationstation/sltest/item/ItemListener.java b/src/test/java/net/modificationstation/sltest/item/ItemListener.java index 4a0fb4446..54c78c67e 100644 --- a/src/test/java/net/modificationstation/sltest/item/ItemListener.java +++ b/src/test/java/net/modificationstation/sltest/item/ItemListener.java @@ -35,8 +35,8 @@ public void registerItems(ItemRegistryEvent event) { testPickaxe = new ModdedPickaxeItem(NAMESPACE.id("test_pickaxe"), testMaterial); //8476 testNBTItem = new NBTItem(NAMESPACE.id("nbt_item")); //8477 testModelItem = new ModelItem(NAMESPACE.id("model_item")).setMaxCount(1); - ironOre = event.register(NAMESPACE.id("iron_ore"), new Item(ItemRegistry.AUTO_ID)); - generatedItem = event.register(NAMESPACE.id("generated_item"), new Item(ItemRegistry.AUTO_ID)); + ironOre = event.register(NAMESPACE.id("iron_ore"), new Item(ItemRegistry.AUTO_ID).setTranslationKey(NAMESPACE.id("iron_ore"))); + generatedItem = event.register(NAMESPACE.id("generated_item"), new Item(ItemRegistry.AUTO_ID).setTranslationKey(NAMESPACE.id("generated_item"))); variationBlockIdle = new BlockStateItem(NAMESPACE.id("variation_block_idle"), Blocks.VARIATION_BLOCK.get().getDefaultState()); variationBlockPassive = new BlockStateItem(NAMESPACE.id("variation_block_passive"), Blocks.VARIATION_BLOCK.get().getDefaultState().with(VariationBlock.VARIANT, VariationBlock.Variant.PASSIVE)); variationBlockActive = new BlockStateItem(NAMESPACE.id("variation_block_active"), Blocks.VARIATION_BLOCK.get().getDefaultState().with(VariationBlock.VARIANT, VariationBlock.Variant.ACTIVE)); From 317389d5d9e82bf6bb35b9cbbc18b412015099e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Tue, 30 Jun 2026 22:03:20 +0200 Subject: [PATCH 13/26] Finish ModelVariantMapOverrideEvent --- .../sltest/render/ModelLoadingListener.java | 18 +++++++++++++++++- .../model/ModelVariantMapOverrideEvent.java | 11 +++++++++++ .../api/client/render/model/ModelLoader.java | 6 +++--- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java b/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java index 261b1fde0..c418cfa54 100644 --- a/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java +++ b/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java @@ -10,6 +10,9 @@ import net.modificationstation.stationapi.api.client.render.model.json.ModelVariantMap; import net.modificationstation.stationapi.api.util.Identifier; +import java.io.BufferedReader; +import java.io.StringReader; + public class ModelLoadingListener { @EventListener public void modelsLoaded(UnbakedModelLoadingFinishedEvent event) { @@ -32,10 +35,23 @@ public void injectBlockState(BlockStateReloadEvent event) { @EventListener public void beforeLoader(BeforeModelLoaderInitEvent event) { SLTest.LOGGER.info("Model Loader Init"); - } + } + + String blockstateVariantJson = (""" + { + "variants": { + "": {"model": "sltest:block/test_block"} + } + }""" + ); @EventListener public void mineLdiver(ModelVariantMapOverrideEvent event) { + if (event.id.equals(Identifier.of("minecraft:note_block"))) { + BufferedReader reader = new BufferedReader(new StringReader(blockstateVariantJson)); + event.addModelVariantMap(SLTest.NAMESPACE, ModelVariantMap.fromJson(event.deserializationContext, reader)); + } + System.err.println(event.id); for (Pair variantMap : event.variantMaps) { System.err.println(" - " + variantMap.getFirst() + " | " + variantMap.getSecond().getVariantMap().toString()); diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/ModelVariantMapOverrideEvent.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/ModelVariantMapOverrideEvent.java index a2e6a624e..e51046b00 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/ModelVariantMapOverrideEvent.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/ModelVariantMapOverrideEvent.java @@ -7,11 +7,14 @@ import net.modificationstation.stationapi.api.block.BlockState; import net.modificationstation.stationapi.api.client.render.model.ModelLoader; import net.modificationstation.stationapi.api.client.render.model.json.ModelVariantMap; +import net.modificationstation.stationapi.api.state.StateManager; import net.modificationstation.stationapi.api.util.Identifier; import net.modificationstation.stationapi.api.util.Namespace; import java.util.List; +import static net.modificationstation.stationapi.api.client.render.model.json.ModelVariantMap.*; + /** * Fired after the {@link ModelVariantMap} is deserialized, allowing to add custom overrides */ @@ -29,6 +32,14 @@ public final class ModelVariantMapOverrideEvent extends Event { * The map of the variants of the {@link BlockState} */ public final List> variantMaps; + /** + * The {@link StateManager} of the {@link Block} for which the {@link BlockState} is being loaded. + */ + public final StateManager stateManager; + /** + * The {@link ModelVariantMap.DeserializationContext} for deserializing the {@link ModelVariantMap} + */ + public final DeserializationContext deserializationContext; /** * Adds a custom {@link ModelVariantMap} to the list of overrides for the given {@link BlockState} diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/ModelLoader.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/ModelLoader.java index ee7898a72..aa49ad4b9 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/ModelLoader.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/ModelLoader.java @@ -235,15 +235,15 @@ private void loadModel(Identifier id) throws Exception { ModelDefinition modelDefinition2 = new ModelDefinition(ImmutableList.of(unbakedModel), ImmutableList.of()); Pair> pair = Pair.of(unbakedModel, () -> modelDefinition2); try { - List> list2 = this.blockStates.getOrDefault(identifier2, List.of()).stream().map((blockState) -> { + List> list2 = new ArrayList<>(this.blockStates.getOrDefault(identifier2, List.of()).stream().map((blockState) -> { try { return Pair.of(blockState.source, ModelVariantMap.fromJson(variantMapDeserializationContext, blockState.data)); } catch (Exception var4) { throw new ModelLoaderException(String.format(Locale.ROOT, "Exception loading blockstate definition: '%s' in texturepack: '%s': %s", identifier2, blockState.source, var4.getMessage())); } - }).toList(); + }).toList()); - StationAPI.EVENT_BUS.post(ModelVariantMapOverrideEvent.builder().modelLoader(this).id(modelIdentifier.id).variantMaps(list2).build()); + StationAPI.EVENT_BUS.post(ModelVariantMapOverrideEvent.builder().modelLoader(this).id(modelIdentifier.id).variantMaps(list2).stateManager(stateManager).deserializationContext(variantMapDeserializationContext).build()); for (Pair pair2 : list2) { MultipartUnbakedModel multipartUnbakedModel; From d842080a9c9001c29179f839c0c057861dfad135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Fri, 10 Jul 2026 00:41:39 +0200 Subject: [PATCH 14/26] Optimize PackedIntegerArray to 1.4% of runtime cost when looking up index --- .../util/collection/PackedIntegerArray.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/util/collection/PackedIntegerArray.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/util/collection/PackedIntegerArray.java index b1ce8a278..90d0d0d18 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/util/collection/PackedIntegerArray.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/util/collection/PackedIntegerArray.java @@ -1,5 +1,7 @@ package net.modificationstation.stationapi.api.util.collection; +import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; import net.modificationstation.stationapi.api.world.chunk.CompactingPackedIntegerArray; import net.modificationstation.stationapi.impl.world.chunk.Palette; import org.jetbrains.annotations.Nullable; @@ -30,6 +32,8 @@ public class PackedIntegerArray private final int indexScale; private final int indexOffset; private final int indexShift; + private final int[] indexLookup; + private static final Long2ReferenceOpenHashMap indexLookups = new Long2ReferenceOpenHashMap<>(); public PackedIntegerArray(int i, int j, int[] is) { this(i, j); @@ -77,9 +81,24 @@ public PackedIntegerArray(int elementBits, int size, @SuppressWarnings("Nullable } else { this.data = new long[j]; } + + long lookupKey = (long)elementBits << 32 | (long)size; + if (!indexLookups.containsKey(lookupKey)) { + int[] lookup = new int[size]; + for (int k = 0; k < size; k++) { + lookup[k] = computeStorageIndex(k); + } + indexLookups.put(lookupKey, lookup); + } + + this.indexLookup = indexLookups.get(lookupKey); } private int getStorageIndex(int index) { + return this.indexLookup[index]; + } + + private int computeStorageIndex(int index) { long l = Integer.toUnsignedLong(this.indexScale); long m = Integer.toUnsignedLong(this.indexOffset); return (int)((long)index * l + m >> 32 >> this.indexShift); From 4836c33958ca049e2ab13efd6d1384b20f8355f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Sat, 11 Jul 2026 21:22:16 +0200 Subject: [PATCH 15/26] Use Objects.checkIndex in EmptyPaletteStorage --- .../util/collection/EmptyPaletteStorage.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/util/collection/EmptyPaletteStorage.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/util/collection/EmptyPaletteStorage.java index 9fe82e64b..4a34a7315 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/util/collection/EmptyPaletteStorage.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/util/collection/EmptyPaletteStorage.java @@ -1,8 +1,7 @@ package net.modificationstation.stationapi.api.util.collection; -import org.apache.commons.lang3.Validate; - import java.util.Arrays; +import java.util.Objects; import java.util.function.IntConsumer; /** @@ -18,20 +17,27 @@ public EmptyPaletteStorage(int size) { @Override public int swap(int index, int value) { - Validate.inclusiveBetween(0L, this.size - 1, index); - Validate.inclusiveBetween(0L, 0L, value); + Objects.checkIndex(index, this.size); + + if (value != 0) { + throw new IllegalArgumentException("Invalid value, cannot set a non-zero value into an EmptyPaletteStorage: " + value); + } + return 0; } @Override public void set(int index, int value) { - Validate.inclusiveBetween(0L, this.size - 1, index); - Validate.inclusiveBetween(0L, 0L, value); + Objects.checkIndex(index, this.size); + + if (value != 0) { + throw new IllegalArgumentException("Invalid value, cannot set a non-zero value into an EmptyPaletteStorage: " + value); + } } @Override public int get(int index) { - Validate.inclusiveBetween(0L, this.size - 1, index); + Objects.checkIndex(index, this.size); return 0; } From 3454ed54350de4674e1326fe83d5bf3986630566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Sat, 11 Jul 2026 21:23:42 +0200 Subject: [PATCH 16/26] This helps a bit.. for some reason --- .../impl/world/chunk/PalettedContainer.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/PalettedContainer.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/PalettedContainer.java index 4ab8977d8..ee3e9516a 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/PalettedContainer.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/PalettedContainer.java @@ -39,6 +39,8 @@ public class PalettedContainer private final PaletteResizeListener dummyListener = (newSize, added) -> 0; private final IndexedIterable idList; private volatile Data data; + private Palette dataPalette; + private PaletteStorage dataStorage; private final PaletteProvider paletteProvider; /** @@ -58,21 +60,27 @@ public static Codec> createCodec(IndexedIterable idL public PalettedContainer(IndexedIterable idList, PaletteProvider paletteProvider, DataProvider dataProvider, PaletteStorage storage, List paletteEntries) { this.idList = idList; this.paletteProvider = paletteProvider; - this.data = new Data<>(dataProvider, storage, dataProvider.factory().create(dataProvider.bits(), idList, this, paletteEntries)); + this.setData(new Data<>(dataProvider, storage, dataProvider.factory().create(dataProvider.bits(), idList, this, paletteEntries))); } private PalettedContainer(IndexedIterable idList, PaletteProvider paletteProvider, Data data) { this.idList = idList; this.paletteProvider = paletteProvider; - this.data = data; + this.setData(data); } public PalettedContainer(IndexedIterable idList, T object, PaletteProvider paletteProvider) { this.paletteProvider = paletteProvider; this.idList = idList; - this.data = this.getCompatibleData(null, 0); + this.setData(this.getCompatibleData(null, 0)); this.data.palette.index(object); } + + void setData(Data data) { + this.data = data; + this.dataPalette = data.palette; + this.dataStorage = data.storage; + } /** * {@return a compatible data object for the given entry {@code bits} size} @@ -93,7 +101,7 @@ public int onResize(int i, T object) { Data data = this.data; Data data2 = this.getCompatibleData(data, i); data2.importFrom(data.palette, data.storage); - this.data = data2; + this.setData(data2); return data2.palette.index(object); } @@ -121,8 +129,8 @@ public T get(int x, int y, int z) { } protected T get(int index) { - Data data = this.data; - return data.palette.get(data.storage.get(index)); + int storageValue = dataStorage.get(index); + return dataPalette.get(storageValue); } public void forEachValue(Consumer consumer) { @@ -150,7 +158,7 @@ public void readPacket(ByteBuffer buf) { .get(data.storage.getData()) .position() * Long.BYTES ); - this.data = data; + this.setData(data); } /** From 4864b41be50ea01abb89a4b85cb6cda7b70ef1c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Sun, 12 Jul 2026 00:52:52 +0200 Subject: [PATCH 17/26] CachedFlattenedChunk, reduced block id lookup by around 12s over a 60s testing period --- .../impl/world/FlattenedWorldManager.java | 3 +- .../world/chunk/CachedFlattenedChunk.java | 46 +++++++++++++++++++ .../impl/world/chunk/ChunkSection.java | 5 ++ .../impl/world/chunk/FlattenedChunk.java | 2 +- .../flattening/NetherChunkGeneratorMixin.java | 3 +- .../OverworldChunkGeneratorMixin.java | 3 +- .../SkylandsChunkGeneratorMixin.java | 3 +- .../client/MultiplayerChunkCacheMixin.java | 6 +-- 8 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/CachedFlattenedChunk.java diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/FlattenedWorldManager.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/FlattenedWorldManager.java index 308a9d91c..b4e4623b6 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/FlattenedWorldManager.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/FlattenedWorldManager.java @@ -13,6 +13,7 @@ import net.modificationstation.stationapi.api.block.BlockState; import net.modificationstation.stationapi.api.block.States; import net.modificationstation.stationapi.api.nbt.NbtOps; +import net.modificationstation.stationapi.impl.world.chunk.CachedFlattenedChunk; import net.modificationstation.stationapi.impl.world.chunk.ChunkSection; import net.modificationstation.stationapi.impl.world.chunk.FlattenedChunk; import net.modificationstation.stationapi.impl.world.chunk.PalettedContainer; @@ -78,7 +79,7 @@ public static void saveChunk(FlattenedChunk chunk, World world, NbtCompound chun public static Chunk loadChunk(World world, NbtCompound chunkTag) { int xPos = chunkTag.getInt("xPos"); int zPos = chunkTag.getInt("zPos"); - FlattenedChunk chunk = new FlattenedChunk(world, xPos, zPos); + FlattenedChunk chunk = new CachedFlattenedChunk(world, xPos, zPos); ChunkSection[] sections = chunk.sections; if (chunkTag.contains(SECTIONS)) { NbtList sectionTags = chunkTag.getList(SECTIONS); diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/CachedFlattenedChunk.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/CachedFlattenedChunk.java new file mode 100644 index 000000000..e4b3f8fca --- /dev/null +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/CachedFlattenedChunk.java @@ -0,0 +1,46 @@ +package net.modificationstation.stationapi.impl.world.chunk; + +import net.minecraft.world.World; + +import java.util.Arrays; + +public class CachedFlattenedChunk extends FlattenedChunk { + public final int[] blockIdCache; + + public CachedFlattenedChunk(World world, int xPos, int zPos) { + super(world, xPos, zPos); + blockIdCache = new int[16 * 16 * this.world.getHeight()]; + Arrays.fill(blockIdCache, -1); + } + + @Override + public ChunkSection getOrCreateSection(int y, boolean fillSkyLight) { + ChunkSection section = super.getOrCreateSection(y, fillSkyLight); + section.blockIdCache = blockIdCache; + return section; + } + + @Override + protected ChunkSection getSection(int y) { + ChunkSection section = super.getSection(y); + + // This never happens +// if (section != null && section.blockIdCache == null) { +// section.blockIdCache = blockIdCache; +// System.err.println("BLOCKIDCACHE NULL"); +// } + + return section; + } + + @Override + public int getBlockId(int x, int y, int z) { + int key = x | (z << 4) | (y << 8); + + if (blockIdCache[key] == -1) { + blockIdCache[key] = super.getBlockId(x, y, z); + } + + return blockIdCache[key]; + } +} diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/ChunkSection.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/ChunkSection.java index efa244a69..a2a1a677c 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/ChunkSection.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/ChunkSection.java @@ -25,6 +25,7 @@ public class ChunkSection { private final NibbleArray metadataArray = new NibbleArray(4096); private final NibbleArray skyLightArray = new NibbleArray(4096); private final NibbleArray blockLightArray = new NibbleArray(4096); + public int[] blockIdCache = null; public ChunkSection(int chunkPos, PalettedContainer blockStateContainer) { this.yOffset = (short) ChunkSection.blockCoordFromChunkCoord(chunkPos); @@ -50,6 +51,10 @@ public BlockState getBlockState(int x, int y, int z) { // } public BlockState setBlockState(int x, int y, int z, BlockState state) { + if (blockIdCache != null) { + blockIdCache[x | (z << 4) | (y + this.yOffset << 8)] = -1; + } + BlockState blockState = this.blockStateContainer.swap(x, y, z, state); // FluidState fluidState = blockState2.getFluidState(); diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/FlattenedChunk.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/FlattenedChunk.java index 1f54408a4..25e948c0b 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/FlattenedChunk.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/FlattenedChunk.java @@ -99,7 +99,7 @@ public boolean isAboveMaxHeight(int relX, int y, int relZ) { return y >= getShortHeight(relX, relZ); } - private ChunkSection getSection(int y) { + protected ChunkSection getSection(int y) { if (y < firstBlock || y > lastBlock) { return null; } diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/NetherChunkGeneratorMixin.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/NetherChunkGeneratorMixin.java index 85924a7f2..4b3a93a9c 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/NetherChunkGeneratorMixin.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/NetherChunkGeneratorMixin.java @@ -7,6 +7,7 @@ import net.minecraft.world.gen.carver.NetherCaveCarver; import net.minecraft.world.gen.chunk.NetherChunkGenerator; import net.modificationstation.stationapi.impl.world.CaveGenBaseImpl; +import net.modificationstation.stationapi.impl.world.chunk.CachedFlattenedChunk; import net.modificationstation.stationapi.impl.world.chunk.FlattenedChunk; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -38,7 +39,7 @@ private NetherCaveCarver stationapi_setWorldForCaveGen(Operation original, ) ) private Chunk stationapi_redirectChunk(World world, byte[] tiles, int xPos, int zPos) { - return new FlattenedChunk(world, xPos, zPos); + return new CachedFlattenedChunk(world, xPos, zPos); } @Inject( diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/client/MultiplayerChunkCacheMixin.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/client/MultiplayerChunkCacheMixin.java index 1b03c6a0a..c371f131d 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/client/MultiplayerChunkCacheMixin.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/client/MultiplayerChunkCacheMixin.java @@ -1,13 +1,11 @@ package net.modificationstation.stationapi.mixin.flattening.client; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.Minecraft; import net.minecraft.client.world.chunk.MultiplayerChunkCache; import net.minecraft.util.math.ChunkPos; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; -import net.modificationstation.stationapi.api.network.ModdedPacketHandler; import net.modificationstation.stationapi.impl.world.StationClientWorld; +import net.modificationstation.stationapi.impl.world.chunk.CachedFlattenedChunk; import net.modificationstation.stationapi.impl.world.chunk.FlattenedChunk; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -32,7 +30,7 @@ public void stationapi_loadChunk(int i, int j, CallbackInfoReturnable cir if (!((StationClientWorld) world).stationAPI$isModded()) return; ChunkPos vec2i = new ChunkPos(i, j); - FlattenedChunk chunk = new FlattenedChunk(this.world, i, j); + FlattenedChunk chunk = new CachedFlattenedChunk(this.world, i, j); this.chunksByPos.put(vec2i, chunk); chunk.loaded = true; cir.setReturnValue(chunk); From 7c78b05dd56c63045d6f84a08971387cb42ab8b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Sun, 26 Jul 2026 00:50:52 +0200 Subject: [PATCH 18/26] Try using shorts in CachedFlattenedChunk and fix crash if bottomY is below 0 --- .../world/chunk/CachedFlattenedChunk.java | 30 +++++++------------ .../impl/world/chunk/ChunkSection.java | 7 +++-- .../api/registry/SimpleRegistry.java | 4 +++ 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/CachedFlattenedChunk.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/CachedFlattenedChunk.java index e4b3f8fca..f2cef1968 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/CachedFlattenedChunk.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/CachedFlattenedChunk.java @@ -5,40 +5,32 @@ import java.util.Arrays; public class CachedFlattenedChunk extends FlattenedChunk { - public final int[] blockIdCache; + public final short[] blockIdCache; + public final int bottomY; + // This can be used to figure out if we need an short or int cache + //private static final int blockRegistrySize = BlockRegistry.INSTANCE.getNextId(); public CachedFlattenedChunk(World world, int xPos, int zPos) { super(world, xPos, zPos); - blockIdCache = new int[16 * 16 * this.world.getHeight()]; - Arrays.fill(blockIdCache, -1); + blockIdCache = new short[16 * 16 * world.getHeight()]; + bottomY = world.getBottomY(); + Arrays.fill(blockIdCache, (short) -1); } @Override public ChunkSection getOrCreateSection(int y, boolean fillSkyLight) { - ChunkSection section = super.getOrCreateSection(y, fillSkyLight); + ChunkSection section = super.getOrCreateSection(y, fillSkyLight); section.blockIdCache = blockIdCache; - return section; - } - - @Override - protected ChunkSection getSection(int y) { - ChunkSection section = super.getSection(y); - - // This never happens -// if (section != null && section.blockIdCache == null) { -// section.blockIdCache = blockIdCache; -// System.err.println("BLOCKIDCACHE NULL"); -// } - + section.worldBottomY = bottomY; return section; } @Override public int getBlockId(int x, int y, int z) { - int key = x | (z << 4) | (y << 8); + int key = x | (z << 4) | ((y - bottomY) << 8); if (blockIdCache[key] == -1) { - blockIdCache[key] = super.getBlockId(x, y, z); + blockIdCache[key] = (short) super.getBlockId(x, y, z); } return blockIdCache[key]; diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/ChunkSection.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/ChunkSection.java index a2a1a677c..483254fb7 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/ChunkSection.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/ChunkSection.java @@ -25,7 +25,10 @@ public class ChunkSection { private final NibbleArray metadataArray = new NibbleArray(4096); private final NibbleArray skyLightArray = new NibbleArray(4096); private final NibbleArray blockLightArray = new NibbleArray(4096); - public int[] blockIdCache = null; + + // Used for CachedFlattenedChunk + public int worldBottomY = 0; + public short[] blockIdCache = null; public ChunkSection(int chunkPos, PalettedContainer blockStateContainer) { this.yOffset = (short) ChunkSection.blockCoordFromChunkCoord(chunkPos); @@ -52,7 +55,7 @@ public BlockState getBlockState(int x, int y, int z) { public BlockState setBlockState(int x, int y, int z, BlockState state) { if (blockIdCache != null) { - blockIdCache[x | (z << 4) | (y + this.yOffset << 8)] = -1; + blockIdCache[x | (z << 4) | ((y - worldBottomY) + this.yOffset << 8)] = -1; } BlockState blockState = this.blockStateContainer.swap(x, y, z, state); diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java index c10e67742..f62109a31 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java @@ -303,6 +303,10 @@ Reference getOrCreateEntry(RegistryKey key) { public int size() { return this.keyToEntry.size(); } + + public int getNextId() { + return this.nextId; + } @Override public Lifecycle getEntryLifecycle(T entry) { From aacf2b62d1417a3e8db46a827089a2a13bb5a2bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Sun, 26 Jul 2026 19:45:26 +0200 Subject: [PATCH 19/26] Bye bye items --- .../modificationstation/stationapi/api/item/Items.java | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/Items.java diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/Items.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/Items.java deleted file mode 100644 index 495fefaab..000000000 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/Items.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.modificationstation.stationapi.api.item; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class Items { - -} From 7112672cd610449523a61156c98e36922a43f6ab Mon Sep 17 00:00:00 2001 From: mine_diver Date: Sun, 26 Jul 2026 22:55:52 +0500 Subject: [PATCH 20/26] whoops --- .../stationapi/mixin/flattening/WorldMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/WorldMixin.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/WorldMixin.java index 2f43365b2..ee65ddf6d 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/WorldMixin.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/WorldMixin.java @@ -75,7 +75,7 @@ public BlockState setBlockState(int x, int y, int z, BlockState blockState, int index = 9 ) private int stationapi_changeCaveSoundY(int y) { - return y + getBottomY(); + return (y % getHeight()) + getBottomY(); } @ModifyExpressionValue( From efd03970fa6786616289c40a408efb9a6c38b1a9 Mon Sep 17 00:00:00 2001 From: mine_diver Date: Sun, 26 Jul 2026 23:10:25 +0500 Subject: [PATCH 21/26] BlockStateReloadEvent bad --- .../sltest/render/ModelLoadingListener.java | 37 ++- .../render/model/BlockStateReloadEvent.java | 92 -------- .../render/model/BakedModelManager.java | 3 - .../event/resource/RuntimeResourcesEvent.java | 127 +++++++++++ .../api/resource/RuntimeResourcePack.java | 212 ++++++++++++++++++ .../impl/resource/GroupResourcePack.java | 13 +- .../impl/resource/ModResourcePackUtil.java | 5 + .../loader/ResourceManagerHelperImpl.java | 29 +++ 8 files changed, 410 insertions(+), 108 deletions(-) delete mode 100644 station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BlockStateReloadEvent.java create mode 100644 station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/api/event/resource/RuntimeResourcesEvent.java create mode 100644 station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/api/resource/RuntimeResourcePack.java diff --git a/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java b/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java index c418cfa54..1e3ceb720 100644 --- a/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java +++ b/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java @@ -4,21 +4,23 @@ import net.mine_diver.unsafeevents.listener.EventListener; import net.modificationstation.sltest.SLTest; import net.modificationstation.stationapi.api.client.event.render.model.BeforeModelLoaderInitEvent; -import net.modificationstation.stationapi.api.client.event.render.model.BlockStateReloadEvent; import net.modificationstation.stationapi.api.client.event.render.model.ModelVariantMapOverrideEvent; import net.modificationstation.stationapi.api.client.event.render.model.UnbakedModelLoadingFinishedEvent; +import net.modificationstation.stationapi.api.client.render.model.ModelLoader; import net.modificationstation.stationapi.api.client.render.model.json.ModelVariantMap; +import net.modificationstation.stationapi.api.event.resource.RuntimeResourcesEvent; import net.modificationstation.stationapi.api.util.Identifier; import java.io.BufferedReader; import java.io.StringReader; +import java.util.List; public class ModelLoadingListener { @EventListener public void modelsLoaded(UnbakedModelLoadingFinishedEvent event) { SLTest.LOGGER.info(event.unbakedModels.size() + " models loaded"); } - + String blockstateJson = (""" { "variants": { @@ -26,15 +28,34 @@ public void modelsLoaded(UnbakedModelLoadingFinishedEvent event) { } }""" ); - + + String dirtBlockstateJson = (""" + { + "variants": { + "": {"model": "sltest:block/dirt"} + } + }""" + ); + @EventListener - public void injectBlockState(BlockStateReloadEvent event) { - event.addBlockstateJsonString(Identifier.of("minecraft:sponge"), blockstateJson); + public void injectBlockStates(RuntimeResourcesEvent.Assets event) { + event.with(ModelLoader.BLOCK_STATES_FINDER) + .add(Identifier.of("minecraft:sponge"), blockstateJson) + .add(Identifier.of("minecraft:dirt"), dirtBlockstateJson); } - + @EventListener public void beforeLoader(BeforeModelLoaderInitEvent event) { SLTest.LOGGER.info("Model Loader Init"); + logBlockStateSources(event, Identifier.of("minecraft:sponge")); + logBlockStateSources(event, Identifier.of("minecraft:dirt")); + } + + private static void logBlockStateSources(BeforeModelLoaderInitEvent event, Identifier block) { + List sources = event.blockStates.get(ModelLoader.BLOCK_STATES_FINDER.toResourcePath(block)); + SLTest.LOGGER.info("blockstate {} has {} definition(s) layered", block, sources == null ? 0 : sources.size()); + if (sources != null) for (int i = 0; i < sources.size(); i++) + SLTest.LOGGER.info(" layer {}: {}", i, sources.get(i).data()); } String blockstateVariantJson = (""" @@ -44,14 +65,14 @@ public void beforeLoader(BeforeModelLoaderInitEvent event) { } }""" ); - + @EventListener public void mineLdiver(ModelVariantMapOverrideEvent event) { if (event.id.equals(Identifier.of("minecraft:note_block"))) { BufferedReader reader = new BufferedReader(new StringReader(blockstateVariantJson)); event.addModelVariantMap(SLTest.NAMESPACE, ModelVariantMap.fromJson(event.deserializationContext, reader)); } - + System.err.println(event.id); for (Pair variantMap : event.variantMaps) { System.err.println(" - " + variantMap.getFirst() + " | " + variantMap.getSecond().getVariantMap().toString()); diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BlockStateReloadEvent.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BlockStateReloadEvent.java deleted file mode 100644 index 28a779424..000000000 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/BlockStateReloadEvent.java +++ /dev/null @@ -1,92 +0,0 @@ -package net.modificationstation.stationapi.api.client.event.render.model; - -import com.mojang.datafixers.util.Pair; -import lombok.experimental.SuperBuilder; -import net.mine_diver.unsafeevents.Event; -import net.mine_diver.unsafeevents.event.EventPhases; -import net.modificationstation.stationapi.api.StationAPI; -import net.modificationstation.stationapi.api.client.render.model.ModelLoader; -import net.modificationstation.stationapi.api.resource.Resource; -import net.modificationstation.stationapi.api.resource.ResourceManager; -import net.modificationstation.stationapi.api.util.Identifier; -import net.modificationstation.stationapi.api.util.JsonHelper; - -import java.io.BufferedReader; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; - -import static net.modificationstation.stationapi.impl.client.texture.StationRenderImpl.LOGGER; - -/** - * An event which is fired when block states are being reloaded - */ -@SuperBuilder -public class BlockStateReloadEvent extends Event { - public final ResourceManager resourceManager; - public final Executor executor; - public final List>>> states; - - /** - * Adds a blockstate json to the list of blockstates to be loaded - * - * @param blockIdentifier The {@link Identifier} of the block - * @param jsons A string representation of the blockstate json to be deserialized - */ - public void addBlockstateJsonString(Identifier blockIdentifier, ArrayList jsons) { - Identifier id = Identifier.of(blockIdentifier.namespace + ":stationapi/blockstates/" + blockIdentifier.path + ".json"); - - states.add(CompletableFuture.supplyAsync(() -> { - List statesList = new ArrayList<>(jsons.size()); - for (String jsonString : jsons) - try { - statesList.add(new ModelLoader.SourceTrackedData("Default", JsonHelper.deserialize(jsonString))); - } catch (Exception exception) { - LOGGER.error("Failed to load blockstate {} from pack {}", blockIdentifier, "Default", exception); - } - return Pair.of(id, statesList); - }, executor)); - } - - /** - * Adds a blockstate json to the list of blockstates to be loaded - * - * @param blockIdentifier The {@link Identifier} of the block - * @param json Resource to be read and deserialized - */ - public void addBlockstateJsonString(Identifier blockIdentifier, String json) { - addBlockstateJsonString(blockIdentifier, new ArrayList<>(List.of(json))); - } - - /** - * Adds a resource to the list of blockstates to be loaded - * - * @param blockIdentifier The {@link Identifier} of the block - * @param resources Resources to be read and deserialized - */ - public void addBlockstateJsonResource(Identifier blockIdentifier, ArrayList resources) { - Identifier id = Identifier.of(blockIdentifier.namespace + ":stationapi/blockstates/" + blockIdentifier.path + ".json"); - - states.add(CompletableFuture.supplyAsync(() -> { - List list2 = new ArrayList<>(resources.size()); - for (Resource resource : resources) - try (BufferedReader reader = resource.getReader()) { - list2.add(new ModelLoader.SourceTrackedData("Default", JsonHelper.deserialize(reader))); - } catch (Exception exception) { - LOGGER.error("Failed to load blockstate {} from pack {}", blockIdentifier, "Default", exception); - } - return Pair.of(id, list2); - }, executor)); - } - - /** - * Adds a resource to the list of blockstates to be loaded - * - * @param blockIdentifier The {@link Identifier} of the block - * @param resource Resource to be read and deserialized - */ - public void addBlockStateJsonResource(Identifier blockIdentifier, Resource resource) { - addBlockstateJsonResource(blockIdentifier, new ArrayList<>(List.of(resource))); - } -} diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java index df026a5d1..e401b2861 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/BakedModelManager.java @@ -12,10 +12,8 @@ import net.minecraft.block.Block; import net.minecraft.client.Minecraft; import net.minecraft.client.texture.TextureManager; -import net.modificationstation.stationapi.api.StationAPI; import net.modificationstation.stationapi.api.block.BlockState; import net.modificationstation.stationapi.api.client.color.block.BlockColors; -import net.modificationstation.stationapi.api.client.event.render.model.BlockStateReloadEvent; import net.modificationstation.stationapi.api.client.render.block.BlockModels; import net.modificationstation.stationapi.api.client.render.model.json.JsonUnbakedModel; import net.modificationstation.stationapi.api.client.texture.Sprite; @@ -131,7 +129,6 @@ private static CompletableFuture> reloadModels private static CompletableFuture>> reloadBlockStates(ResourceManager resourceManager, Executor executor) { return CompletableFuture.supplyAsync(() -> ModelLoader.BLOCK_STATES_FINDER.findAllResources(resourceManager), executor).thenCompose(blockStates2 -> { List>>> list = new ArrayList<>(blockStates2.size()); - StationAPI.EVENT_BUS.post(BlockStateReloadEvent.builder().resourceManager(resourceManager).executor(executor).states(list).build()); for (Map.Entry> entry : blockStates2.entrySet()) { list.add(CompletableFuture.supplyAsync(() -> { List resources = entry.getValue(); diff --git a/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/api/event/resource/RuntimeResourcesEvent.java b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/api/event/resource/RuntimeResourcesEvent.java new file mode 100644 index 000000000..38abe0f5c --- /dev/null +++ b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/api/event/resource/RuntimeResourcesEvent.java @@ -0,0 +1,127 @@ +package net.modificationstation.stationapi.api.event.resource; + +import com.google.gson.JsonElement; +import lombok.experimental.SuperBuilder; +import net.mine_diver.unsafeevents.Event; +import net.modificationstation.stationapi.api.resource.InputSupplier; +import net.modificationstation.stationapi.api.resource.ResourceFinder; +import net.modificationstation.stationapi.api.resource.ResourceType; +import net.modificationstation.stationapi.api.resource.RuntimeResourcePack; +import net.modificationstation.stationapi.api.util.Identifier; +import net.modificationstation.stationapi.api.util.Namespace; + +import java.io.InputStream; +import java.util.Map; + +/** + * Fired once per reload, before packs are handed to the resource manager, to collect resources that + * mods supply from code rather than from files. + * + *

Resources added here behave exactly like files shipped in the adding mod's JAR - see + * {@link RuntimeResourcePack} for what that buys you. Because the event is fired per reload, + * listeners may consult registry state freely and their output is regenerated when the player + * reloads resources. + * + *

{@link #with(ResourceFinder)} names the loader that should pick the resources up, and no path + * is spelled out by hand: + * + *

{@code
+ * @EventListener
+ * public void assets(RuntimeResourcesEvent.Assets event) {
+ *     event.with(BLOCK_STATES_FINDER)
+ *             .add(Identifier.of("minecraft:sponge"), spongeState)
+ *             .add(Identifier.of("minecraft:dirt"), dirtState);
+ * }
+ * }
+ * + *

The receiving pack is resolved from the calling class's mod. Callers generating resources from + * a shared utility class, where that would resolve to the wrong mod, should name their pack + * explicitly via {@link #pack(Namespace)} and populate it directly. + */ +@SuperBuilder +public abstract class RuntimeResourcesEvent extends Event { + + /** + * The kind of resources being collected. Matches the concrete event type, and is the type the + * packs handed out by this event serve. + */ + public final ResourceType type; + + private final Map packs; + + /** + * {@return the runtime pack owned by {@code namespace}, creating it if this is its first use} + * + *

The namespace identifies the mod supplying the resources, which is unrelated to + * the namespaces of the resources themselves - a mod's pack may serve {@code minecraft} paths. + * It determines pack ordering against other mods' packs and the pack name shown in load errors. + */ + public RuntimeResourcePack pack(Namespace namespace) { + return packs.computeIfAbsent(namespace, ns -> new RuntimeResourcePack(ns, type)); + } + + /** + * {@return the runtime pack owned by the mod that called this method} + */ + public RuntimeResourcePack pack() { + return pack(callerNamespace()); + } + + /** + * {@return the calling mod's pack, scoped to the resources {@code finder} looks for} + * + * @see RuntimeResourcePack#with(ResourceFinder) + */ + public RuntimeResourcePack.Scope with(ResourceFinder finder) { + return pack(callerNamespace()).with(finder); + } + + /** + * Adds a resource at an explicit resource path, to the calling mod's pack. + * + * @see RuntimeResourcePack#add(Identifier, InputSupplier) + */ + public RuntimeResourcePack add(Identifier path, InputSupplier content) { + return pack(callerNamespace()).add(path, content); + } + + public RuntimeResourcePack add(Identifier path, byte[] content) { + return pack(callerNamespace()).add(path, content); + } + + public RuntimeResourcePack add(Identifier path, String content) { + return pack(callerNamespace()).add(path, content); + } + + public RuntimeResourcePack add(Identifier path, JsonElement content) { + return pack(callerNamespace()).add(path, content); + } + + /** + * Resolves the namespace of the first class on the stack that isn't part of this event, so that + * callers don't have to restate their own namespace. Chaining from {@link #with(ResourceFinder)} + * resolves once for the whole chain. + */ + private static Namespace callerNamespace() { + return Namespace.resolve( + StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk(frames -> frames + .map(StackWalker.StackFrame::getDeclaringClass) + .filter(caller -> !RuntimeResourcesEvent.class.isAssignableFrom(caller)) + .findFirst() + .orElseThrow() + ) + ); + } + + /** + * Collects {@link ResourceType#CLIENT_RESOURCES} - models, blockstates, textures, sounds. + */ + @SuperBuilder + public static final class Assets extends RuntimeResourcesEvent {} + + /** + * Collects {@link ResourceType#SERVER_DATA} - recipes, tags. + */ + @SuperBuilder + public static final class Data extends RuntimeResourcesEvent {} +} diff --git a/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/api/resource/RuntimeResourcePack.java b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/api/resource/RuntimeResourcePack.java new file mode 100644 index 000000000..a88fb5fe3 --- /dev/null +++ b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/api/resource/RuntimeResourcePack.java @@ -0,0 +1,212 @@ +package net.modificationstation.stationapi.api.resource; + +import com.google.gson.JsonElement; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import net.fabricmc.loader.api.metadata.ModMetadata; +import net.modificationstation.stationapi.api.event.resource.RuntimeResourcesEvent; +import net.modificationstation.stationapi.api.resource.metadata.ResourceMetadataReader; +import net.modificationstation.stationapi.api.util.Identifier; +import net.modificationstation.stationapi.api.util.Namespace; +import net.modificationstation.stationapi.impl.resource.ModResourcePack; +import org.jetbrains.annotations.Nullable; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Set; + +/** + * A resource pack whose contents are supplied by code instead of by files in a mod JAR. + * + *

Runtime packs are indistinguishable from file-backed packs to everything downstream of + * {@link ResourceManager}: they take part in pack layering, can be overridden by resource packs the + * player enables, honor the {@code filter} section of other packs' {@code pack.mcmeta}, and are + * ordered against other mods' packs by the usual {@code station-resource-loader-v0:_priority} + * custom values. Consequently there is no need for a loader to grow its own injection hook - if a + * loader reads a resource, a runtime pack can supply it. + * + *

Packs are populated per reload, through {@link RuntimeResourcesEvent}, + * so generated content is free to depend on registry state and is regenerated when the player + * reloads resources. + * + * @see RuntimeResourcesEvent + */ +public class RuntimeResourcePack implements ModResourcePack { + + private final Namespace owner; + private final ResourceType type; + private final Reference2ReferenceMap> resources = new Reference2ReferenceOpenHashMap<>(); + private final ReferenceSet namespaces = new ReferenceOpenHashSet<>(); + + public RuntimeResourcePack(Namespace owner, ResourceType type) { + this.owner = owner; + this.type = type; + } + + /** + * {@return this pack, scoped to the resources {@code finder} looks for} + * + *

Within a scope, resources are named by the IDs their loader knows them by, and the + * directory and extension are left to the finder: + * + *

{@code
+     * pack.with(BLOCK_STATES_FINDER)
+     *         .add(Identifier.of("minecraft:sponge"), spongeState)
+     *         .add(Identifier.of("minecraft:dirt"), dirtState);
+     * }
+ * + * @param finder the finder of the loader that should pick these resources up + */ + public Scope with(ResourceFinder finder) { + return new Scope(finder); + } + + /** + * Adds a resource at an explicit resource path. + * + *

The path is the full in-pack path, i.e. what a file under + * {@code assets//} would resolve to. Prefer {@link #with(ResourceFinder)} when + * the target loader exposes its finder, so that the directory and extension aren't restated + * here. + * + * @param path the resource path + * @param content supplier of the resource's bytes, invoked every time the resource is read + */ + public RuntimeResourcePack add(Identifier path, InputSupplier content) { + resources.put(path, content); + namespaces.add(path.namespace); + return this; + } + + public RuntimeResourcePack add(Identifier path, byte[] content) { + return add(path, () -> new ByteArrayInputStream(content)); + } + + public RuntimeResourcePack add(Identifier path, String content) { + return add(path, content.getBytes(StandardCharsets.UTF_8)); + } + + public RuntimeResourcePack add(Identifier path, JsonElement content) { + return add(path, content.toString()); + } + + public boolean isEmpty() { + return resources.isEmpty(); + } + + @Nullable + @Override + public InputSupplier openRoot(String... segments) { + // Root files (pack.mcmeta, pack.png) are supplied by the enclosing mod pack. + return null; + } + + @Nullable + @Override + public InputSupplier open(ResourceType type, Identifier id) { + return this.type == type ? resources.get(id) : null; + } + + @Override + public void findResources(ResourceType type, Namespace namespace, String prefix, ResultConsumer consumer) { + if (this.type != type || prefix.startsWith("/")) return; + resources.forEach((id, content) -> { + if (id.namespace == namespace && isUnder(id.path, prefix)) consumer.accept(id, content); + }); + } + + /** + * Matches whole path segments only, so that a search for {@code stationapi/block} doesn't + * report resources living in {@code stationapi/blockstates}. + */ + private static boolean isUnder(String path, String prefix) { + return path.startsWith(prefix) && (path.length() == prefix.length() || path.charAt(prefix.length()) == '/'); + } + + @Override + public Set getNamespaces(ResourceType type) { + return this.type == type ? namespaces : Collections.emptySet(); + } + + @Nullable + @Override + public T parseMetadata(ResourceMetadataReader metaReader) { + return null; + } + + @Override + public String getName() { + return owner.getName() + " (generated)"; + } + + @Override + public void close() {} + + @Override + public ModMetadata getFabricModMetadata() { + return owner.getMetadata(); + } + + /** + * A {@link RuntimeResourcePack} bound to one {@link ResourceFinder}, so that a run of related + * resources can be added without restating where they go. + * + * @see RuntimeResourcePack#with(ResourceFinder) + */ + public final class Scope { + + private final ResourceFinder finder; + + private Scope(ResourceFinder finder) { + this.finder = finder; + } + + /** + * Adds a resource for the given ID, deriving its path from this scope's finder. + * + * @param id the ID the loader knows the resource by + * @param content supplier of the resource's bytes, invoked every time the resource is read + */ + public Scope add(Identifier id, InputSupplier content) { + RuntimeResourcePack.this.add(finder.toResourcePath(id), content); + return this; + } + + public Scope add(Identifier id, byte[] content) { + RuntimeResourcePack.this.add(finder.toResourcePath(id), content); + return this; + } + + public Scope add(Identifier id, String content) { + RuntimeResourcePack.this.add(finder.toResourcePath(id), content); + return this; + } + + public Scope add(Identifier id, JsonElement content) { + RuntimeResourcePack.this.add(finder.toResourcePath(id), content); + return this; + } + + /** + * {@return the same pack, scoped to {@code finder} instead} + * + *

Lets one chain cover several kinds of resource. + */ + public Scope with(ResourceFinder finder) { + return RuntimeResourcePack.this.with(finder); + } + + /** + * {@return the pack this scope adds to} + * + *

For leaving a chain to add a resource by explicit path. + */ + public RuntimeResourcePack pack() { + return RuntimeResourcePack.this; + } + } +} diff --git a/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/GroupResourcePack.java b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/GroupResourcePack.java index 0d7fbf495..952ae4dc7 100644 --- a/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/GroupResourcePack.java +++ b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/GroupResourcePack.java @@ -67,11 +67,10 @@ public void findResources(ResourceType type, Namespace namespace, String prefix, if (packs == null) return; - for (int i = packs.size() - 1; i >= 0; i--) { - ResourcePack pack = packs.get(i); - + // Ascending priority, unlike open() above: consumers of findResources keep the *last* + // result they're handed for an ID, so the highest priority pack has to be visited last. + for (ModResourcePack pack : packs) pack.findResources(type, namespace, prefix, consumer); - } } @Override @@ -86,7 +85,11 @@ public void appendResources(ResourceType type, Identifier id, List res Identifier metadataId = NamespaceResourceManager.getMetadataPath(id); - for (ModResourcePack pack : packs) { + // Descending priority: getAllResources walks its pack list from the top down and reverses + // the result at the end, so appending ascending here would leave this group's entries + // inverted relative to every other pack once that reverse happens. + for (int i = packs.size() - 1; i >= 0; i--) { + ModResourcePack pack = packs.get(i); InputSupplier supplier = pack.open(type, id); if (supplier != null) { diff --git a/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/ModResourcePackUtil.java b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/ModResourcePackUtil.java index ae3fbf603..5872e1126 100644 --- a/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/ModResourcePackUtil.java +++ b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/ModResourcePackUtil.java @@ -13,6 +13,7 @@ import net.fabricmc.loader.api.metadata.ModMetadata; import net.modificationstation.stationapi.api.resource.ResourcePackActivationType; import net.modificationstation.stationapi.api.resource.ResourceType; +import net.modificationstation.stationapi.impl.resource.loader.ResourceManagerHelperImpl; import org.apache.commons.io.IOUtils; import org.jetbrains.annotations.Nullable; @@ -59,6 +60,10 @@ public static void appendModResourcePacks(List packs, ResourceT packs.add(pack); } } + + // Added before the sort so that code-supplied packs are ordered by the same priority rules. + ResourceManagerHelperImpl.appendRuntimeResourcePacks(packs, type); + packs.sort((pack1, pack2) -> { String id1 = pack1.getFabricModMetadata().getId(); String id2 = pack2.getFabricModMetadata().getId(); diff --git a/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/loader/ResourceManagerHelperImpl.java b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/loader/ResourceManagerHelperImpl.java index d69d1639c..4bf051144 100644 --- a/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/loader/ResourceManagerHelperImpl.java +++ b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/loader/ResourceManagerHelperImpl.java @@ -2,10 +2,15 @@ import com.google.common.collect.Lists; import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap; import net.fabricmc.loader.api.ModContainer; +import net.modificationstation.stationapi.api.StationAPI; +import net.modificationstation.stationapi.api.event.resource.RuntimeResourcesEvent; import net.modificationstation.stationapi.api.resource.*; import net.modificationstation.stationapi.api.util.Identifier; +import net.modificationstation.stationapi.api.util.Namespace; import net.modificationstation.stationapi.impl.resource.ModNioResourcePack; +import net.modificationstation.stationapi.impl.resource.ModResourcePack; import net.modificationstation.stationapi.impl.resource.ResourcePackProfile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,6 +74,30 @@ public static boolean registerBuiltinResourcePack(Identifier id, String subPath, return registerBuiltinResourcePack(id, subPath, container, id.namespace + "/" + id.path, activationType); } + /** + * Fires {@link RuntimeResourcesEvent} for the given resource type and appends every non-empty + * pack it collected to {@code packs}. + * + *

Called before mod packs are sorted, so runtime packs take part in the usual + * {@code station-resource-loader-v0:_priority} ordering. A mod supplying both files and + * runtime resources ends up with its runtime pack after its file pack, letting generated + * content override the mod's own files. + * + * @param packs the mod resource pack list to append to + * @param type the type of resource being collected + */ + public static void appendRuntimeResourcePacks(List packs, ResourceType type) { + Map runtimePacks = new Reference2ReferenceLinkedOpenHashMap<>(); + StationAPI.EVENT_BUS.post(switch (type) { + case CLIENT_RESOURCES -> RuntimeResourcesEvent.Assets.builder().type(type).packs(runtimePacks).build(); + case SERVER_DATA -> RuntimeResourcesEvent.Data.builder().type(type).packs(runtimePacks).build(); + }); + + for (RuntimeResourcePack pack : runtimePacks.values()) + if (pack.isEmpty()) LOGGER.debug("Discarding empty runtime resource pack {}", pack.getName()); + else packs.add(pack); + } + public static void registerBuiltinResourcePacks(ResourceType resourceType, Consumer consumer) { // Loop through each registered built-in resource packs and add them if valid. for (Pair entry : builtinResourcePacks) { From a145f38eb144512891e364db25959dca853e7fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Sun, 26 Jul 2026 21:03:28 +0200 Subject: [PATCH 22/26] getBlock() -> owner = performance --- .../api/block/AbstractBlockState.java | 18 +++++++++--------- .../stationapi/api/state/State.java | 4 +++- .../impl/world/chunk/FlattenedChunk.java | 14 +++++++------- .../mixin/flattening/BlockMixin.java | 2 +- .../mixin/flattening/ChunkMixin.java | 4 ++-- .../mixin/flattening/WorldMixin.java | 8 ++++---- .../render/client/BlockRenderManagerMixin.java | 2 +- 7 files changed, 27 insertions(+), 25 deletions(-) diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java index 76e3d3d6f..4a4b2ff69 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java @@ -65,11 +65,11 @@ public MapColor getTopMaterialColor(BlockView world, BlockPos pos) { } public float getHardness(BlockView world, BlockPos pos) { - return getBlock().getHardness(asBlockState(), world, pos); + return owner.getHardness(asBlockState(), world, pos); } public float calcBlockBreakingDelta(PlayerEntity player, BlockView world, BlockPos pos) { - return getBlock().calcBlockBreakingDelta(asBlockState(), player, world, pos); + return owner.calcBlockBreakingDelta(asBlockState(), player, world, pos); } public boolean isOpaque() { @@ -77,15 +77,15 @@ public boolean isOpaque() { } public void onStateReplaced(World world, BlockPos pos, BlockState state) { - this.getBlock().onStateReplaced(this.asBlockState(), world, pos, state); + this.owner.onStateReplaced(this.asBlockState(), world, pos, state); } public boolean canReplace(ItemPlacementContext context) { - return this.getBlock().canReplace(this.asBlockState(), context); + return this.owner.canReplace(this.asBlockState(), context); } public boolean isIn(TagKey tag) { - return getBlock().getRegistryEntry().isIn(tag); + return owner.getRegistryEntry().isIn(tag); } public boolean isIn(TagKey tag, Predicate predicate) { @@ -93,19 +93,19 @@ public boolean isIn(TagKey tag, Predicate predicate) } public boolean isIn(RegistryEntryList blocks) { - return blocks.contains(getBlock().getRegistryEntry()); + return blocks.contains(owner.getRegistryEntry()); } public Stream> streamTags() { - return getBlock().getRegistryEntry().streamTags(); + return owner.getRegistryEntry().streamTags(); } public boolean isOf(Block block) { - return this.getBlock() == block; + return this.owner == block; } public boolean hasRandomTicks() { - return Block.BLOCKS_RANDOM_TICK[getBlock().id]; + return Block.BLOCKS_RANDOM_TICK[owner.id]; } @Environment(EnvType.CLIENT) diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/state/State.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/state/State.java index bccd458fa..d4c668b9f 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/state/State.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/state/State.java @@ -6,6 +6,7 @@ import net.modificationstation.stationapi.api.state.property.Property; import net.modificationstation.stationapi.api.util.collection.FastImmutableTable; import net.modificationstation.stationapi.api.util.collection.FastImmutableTableCache; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -29,7 +30,8 @@ private > String nameValue(Property property, Compara return property.name((T) value); } }; - protected final O owner; + @ApiStatus.Internal + public final O owner; private final ImmutableMap, Comparable> entries; private Table, Comparable, S> withTable; protected final MapCodec codec; diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/FlattenedChunk.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/FlattenedChunk.java index 25e948c0b..904451cd5 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/FlattenedChunk.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/FlattenedChunk.java @@ -313,7 +313,7 @@ private void updateHeightMap(int x, int y, int z) { @Override public int getBlockId(int x, int y, int z) { - return getBlockState(x, y, z).getBlock().id; + return getBlockState(x, y, z).owner.id; } @Override @@ -380,7 +380,7 @@ public BlockState setBlockState(int x, int y, int z, BlockState state, int meta) BlockState oldState = section.getBlockState(x, y & 15, z); if (oldState == state && sameMeta) return null; - Block oldBlock = oldState.getBlock(); + Block oldBlock = oldState.owner; if ( StationAPI.EVENT_BUS.post(BlockEvent.BeforeRemoved.builder() .block(oldBlock) @@ -395,7 +395,7 @@ public BlockState setBlockState(int x, int y, int z, BlockState state, int meta) section.setMeta(x, y & 15, z, meta); if (!this.world.dimension.hasCeiling) { - if (Block.BLOCKS_LIGHT_OPACITY[state.getBlock().id] != 0) { + if (Block.BLOCKS_LIGHT_OPACITY[state.owner.id] != 0) { if (y >= var6) this.updateHeightMap(x, y + 1, z); } else if (y == var6 - 1) @@ -407,7 +407,7 @@ public BlockState setBlockState(int x, int y, int z, BlockState state, int meta) this.world.queueLightUpdate(LightType.BLOCK, worldX, y, worldZ, worldX, y, worldZ); ((ChunkAccessor) this).invokeLightGaps(x, z); section.setMeta(x, y & 15, z, meta); - state.getBlock().onBlockPlaced(this.world, worldX, y, worldZ, oldState); + state.owner.onBlockPlaced(this.world, worldX, y, worldZ, oldState); this.dirty = true; return oldState; @@ -432,7 +432,7 @@ public BlockState setBlockState(int x, int y, int z, BlockState state) { if (oldState == state) return null; short topY = getShortHeight(x, z); - Block oldBlock = oldState.getBlock(); + Block oldBlock = oldState.owner; if ( StationAPI.EVENT_BUS.post(BlockEvent.BeforeRemoved.builder() .block(oldBlock) @@ -444,7 +444,7 @@ public BlockState setBlockState(int x, int y, int z, BlockState state) { section.setBlockState(x, y & 15, z, state); oldState.onStateReplaced(world, CACHED_BLOCK_POS.get().set(worldX, y, worldZ), state); section.setMeta(x, y & 15, z, 0); - if (Block.BLOCKS_LIGHT_OPACITY[state.getBlock().id] != 0) { + if (Block.BLOCKS_LIGHT_OPACITY[state.owner.id] != 0) { if (y >= topY) this.updateHeightMap(x, y + 1, z); } else if (y == topY - 1) @@ -453,7 +453,7 @@ public BlockState setBlockState(int x, int y, int z, BlockState state) { this.world.queueLightUpdate(LightType.BLOCK, worldX, y, worldZ, worldX, y, worldZ); ((ChunkAccessor) this).invokeLightGaps(x, z); if (!this.world.isRemote) { - state.getBlock().onBlockPlaced(this.world, worldX, y, worldZ, oldState); + state.owner.onBlockPlaced(this.world, worldX, y, worldZ, oldState); } this.dirty = true; diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/BlockMixin.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/BlockMixin.java index 9c42a64c0..d38629be6 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/BlockMixin.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/BlockMixin.java @@ -338,7 +338,7 @@ private static Item stationapi_onlyUnderShift(Item[] array, int index) { return index < ItemRegistry.ID_SHIFT ? array[index] : null; } - @Unique private ToIntFunction stationapi_luminance = state -> Block.BLOCKS_LIGHT_LUMINANCE[state.getBlock().id]; + @Unique private ToIntFunction stationapi_luminance = state -> Block.BLOCKS_LIGHT_LUMINANCE[state.owner.id]; @Override @Unique diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/ChunkMixin.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/ChunkMixin.java index 9c4d9cdf9..ffb1345f0 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/ChunkMixin.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/ChunkMixin.java @@ -26,13 +26,13 @@ public BlockState getBlockState(int x, int y, int z) { @Unique public BlockState setBlockState(int x, int y, int z, BlockState blockState) { BlockState oldState = getBlockState(x, y, z); - return setBlock(x, y, z, blockState.getBlock().id) ? oldState : null; + return setBlock(x, y, z, blockState.owner.id) ? oldState : null; } @Override @Unique public BlockState setBlockState(int x, int y, int z, BlockState blockState, int meta) { BlockState oldState = getBlockState(x, y, z); - return setBlock(x, y, z, blockState.getBlock().id, meta) ? oldState : null; + return setBlock(x, y, z, blockState.owner.id, meta) ? oldState : null; } } diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/WorldMixin.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/WorldMixin.java index ee65ddf6d..794fabe58 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/WorldMixin.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/WorldMixin.java @@ -43,7 +43,7 @@ public BlockState setBlockStateWithoutNotifyingNeighbors(int x, int y, int z, Bl public BlockState setBlockState(int x, int y, int z, BlockState blockState) { BlockState oldBlockState = setBlockStateWithoutNotifyingNeighbors(x, y, z, blockState); if (oldBlockState != null) { - blockUpdate(x, y, z, blockState.getBlock().id); + blockUpdate(x, y, z, blockState.owner.id); return oldBlockState; } return null; @@ -60,7 +60,7 @@ public BlockState setBlockStateWithoutNotifyingNeighbors(int x, int y, int z, Bl public BlockState setBlockState(int x, int y, int z, BlockState blockState, int meta) { BlockState oldBlockState = setBlockStateWithoutNotifyingNeighbors(x, y, z, blockState, meta); if (oldBlockState != null) { - blockUpdate(x, y, z, blockState.getBlock().id); + blockUpdate(x, y, z, blockState.owner.id); return oldBlockState; } return null; @@ -137,7 +137,7 @@ private int stationapi_changeTickBlockId( @Local(index = 5) Chunk chunk, @Local(index = 8) int x, @Local(index = 10) int y, @Local(index = 9) int z ) { - return chunk.getBlockState(x, y, z).getBlock().id; + return chunk.getBlockState(x, y, z).owner.id; } @ModifyConstant(method = { @@ -248,7 +248,7 @@ public int getBottomY() { ) ) private Block stationapi_accountForAirBlock(Block value) { - return value == States.AIR.get().getBlock() ? null : value; + return value == States.AIR.get().owner ? null : value; } @ModifyVariable( diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/mixin/render/client/BlockRenderManagerMixin.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/mixin/render/client/BlockRenderManagerMixin.java index 7f93d621d..32836ae4d 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/mixin/render/client/BlockRenderManagerMixin.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/mixin/render/client/BlockRenderManagerMixin.java @@ -31,7 +31,7 @@ abstract class BlockRenderManagerMixin implements StationRendererBlockRenderMana @Unique public void renderAllSides(BlockState state, int x, int y, int z) { if (StationRenderAPI.getBakedModelManager().getBlockModels().getModel(state) instanceof VanillaBakedModel) - renderWithoutCulling(state.getBlock(), x, y, z); + renderWithoutCulling(state.owner, x, y, z); else if (RendererAccess.INSTANCE.hasRenderer()) RendererAccess.INSTANCE.getRenderer().bakedModelRenderer().renderBlock(state, stationapi_pos.set(x, y, z), blockView, false, stationapi_random); } From fd44dc08d7858a18af55ef85ada662ff1b2289b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Sun, 26 Jul 2026 21:47:12 +0200 Subject: [PATCH 23/26] owner -> block --- .../api/block/AbstractBlockState.java | 24 ++++++++++--------- .../stationapi/api/state/State.java | 3 +-- .../world/chunk/CachedFlattenedChunk.java | 7 +++++- .../impl/world/chunk/FlattenedChunk.java | 14 +++++------ .../mixin/flattening/BlockMixin.java | 2 +- .../mixin/flattening/ChunkMixin.java | 4 ++-- .../mixin/flattening/WorldMixin.java | 8 +++---- .../client/BlockRenderManagerMixin.java | 2 +- 8 files changed, 35 insertions(+), 29 deletions(-) diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java index 4a4b2ff69..cd9eb174d 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java @@ -23,6 +23,7 @@ import java.util.stream.Stream; public abstract class AbstractBlockState extends State { + public final Block block; private final boolean isAir; private final Material material; private final MapColor materialColor; @@ -32,6 +33,7 @@ public abstract class AbstractBlockState extends State { protected AbstractBlockState(Block block, ImmutableMap, Comparable> propertyMap, MapCodec mapCodec) { super(block, propertyMap, mapCodec); + this.block = block; this.isAir = block.material == Material.AIR; this.material = block.material; this.materialColor = block.material.mapColor; @@ -40,7 +42,7 @@ protected AbstractBlockState(Block block, ImmutableMap, Comparable tag) { - return owner.getRegistryEntry().isIn(tag); + return block.getRegistryEntry().isIn(tag); } public boolean isIn(TagKey tag, Predicate predicate) { @@ -93,19 +95,19 @@ public boolean isIn(TagKey tag, Predicate predicate) } public boolean isIn(RegistryEntryList blocks) { - return blocks.contains(owner.getRegistryEntry()); + return blocks.contains(block.getRegistryEntry()); } public Stream> streamTags() { - return owner.getRegistryEntry().streamTags(); + return block.getRegistryEntry().streamTags(); } public boolean isOf(Block block) { - return this.owner == block; + return this.block == block; } public boolean hasRandomTicks() { - return Block.BLOCKS_RANDOM_TICK[owner.id]; + return Block.BLOCKS_RANDOM_TICK[block.id]; } @Environment(EnvType.CLIENT) diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/state/State.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/state/State.java index d4c668b9f..e31447fdc 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/state/State.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/state/State.java @@ -30,8 +30,7 @@ private > String nameValue(Property property, Compara return property.name((T) value); } }; - @ApiStatus.Internal - public final O owner; + protected final O owner; private final ImmutableMap, Comparable> entries; private Table, Comparable, S> withTable; protected final MapCodec codec; diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/CachedFlattenedChunk.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/CachedFlattenedChunk.java index f2cef1968..f1e08a40d 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/CachedFlattenedChunk.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/CachedFlattenedChunk.java @@ -9,7 +9,7 @@ public class CachedFlattenedChunk extends FlattenedChunk { public final int bottomY; // This can be used to figure out if we need an short or int cache //private static final int blockRegistrySize = BlockRegistry.INSTANCE.getNextId(); - + public CachedFlattenedChunk(World world, int xPos, int zPos) { super(world, xPos, zPos); blockIdCache = new short[16 * 16 * world.getHeight()]; @@ -29,6 +29,11 @@ public ChunkSection getOrCreateSection(int y, boolean fillSkyLight) { public int getBlockId(int x, int y, int z) { int key = x | (z << 4) | ((y - bottomY) << 8); + if (key < 0 || key >= blockIdCache.length) { + System.err.println("x = " + x + ", y = " + y + ", z = " + z); + return 0; + } + if (blockIdCache[key] == -1) { blockIdCache[key] = (short) super.getBlockId(x, y, z); } diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/FlattenedChunk.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/FlattenedChunk.java index 904451cd5..46c11caf3 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/FlattenedChunk.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/FlattenedChunk.java @@ -313,7 +313,7 @@ private void updateHeightMap(int x, int y, int z) { @Override public int getBlockId(int x, int y, int z) { - return getBlockState(x, y, z).owner.id; + return getBlockState(x, y, z).block.id; } @Override @@ -380,7 +380,7 @@ public BlockState setBlockState(int x, int y, int z, BlockState state, int meta) BlockState oldState = section.getBlockState(x, y & 15, z); if (oldState == state && sameMeta) return null; - Block oldBlock = oldState.owner; + Block oldBlock = oldState.block; if ( StationAPI.EVENT_BUS.post(BlockEvent.BeforeRemoved.builder() .block(oldBlock) @@ -395,7 +395,7 @@ public BlockState setBlockState(int x, int y, int z, BlockState state, int meta) section.setMeta(x, y & 15, z, meta); if (!this.world.dimension.hasCeiling) { - if (Block.BLOCKS_LIGHT_OPACITY[state.owner.id] != 0) { + if (Block.BLOCKS_LIGHT_OPACITY[state.block.id] != 0) { if (y >= var6) this.updateHeightMap(x, y + 1, z); } else if (y == var6 - 1) @@ -407,7 +407,7 @@ public BlockState setBlockState(int x, int y, int z, BlockState state, int meta) this.world.queueLightUpdate(LightType.BLOCK, worldX, y, worldZ, worldX, y, worldZ); ((ChunkAccessor) this).invokeLightGaps(x, z); section.setMeta(x, y & 15, z, meta); - state.owner.onBlockPlaced(this.world, worldX, y, worldZ, oldState); + state.block.onBlockPlaced(this.world, worldX, y, worldZ, oldState); this.dirty = true; return oldState; @@ -432,7 +432,7 @@ public BlockState setBlockState(int x, int y, int z, BlockState state) { if (oldState == state) return null; short topY = getShortHeight(x, z); - Block oldBlock = oldState.owner; + Block oldBlock = oldState.block; if ( StationAPI.EVENT_BUS.post(BlockEvent.BeforeRemoved.builder() .block(oldBlock) @@ -444,7 +444,7 @@ public BlockState setBlockState(int x, int y, int z, BlockState state) { section.setBlockState(x, y & 15, z, state); oldState.onStateReplaced(world, CACHED_BLOCK_POS.get().set(worldX, y, worldZ), state); section.setMeta(x, y & 15, z, 0); - if (Block.BLOCKS_LIGHT_OPACITY[state.owner.id] != 0) { + if (Block.BLOCKS_LIGHT_OPACITY[state.block.id] != 0) { if (y >= topY) this.updateHeightMap(x, y + 1, z); } else if (y == topY - 1) @@ -453,7 +453,7 @@ public BlockState setBlockState(int x, int y, int z, BlockState state) { this.world.queueLightUpdate(LightType.BLOCK, worldX, y, worldZ, worldX, y, worldZ); ((ChunkAccessor) this).invokeLightGaps(x, z); if (!this.world.isRemote) { - state.owner.onBlockPlaced(this.world, worldX, y, worldZ, oldState); + state.block.onBlockPlaced(this.world, worldX, y, worldZ, oldState); } this.dirty = true; diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/BlockMixin.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/BlockMixin.java index d38629be6..a3fbfaf74 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/BlockMixin.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/BlockMixin.java @@ -338,7 +338,7 @@ private static Item stationapi_onlyUnderShift(Item[] array, int index) { return index < ItemRegistry.ID_SHIFT ? array[index] : null; } - @Unique private ToIntFunction stationapi_luminance = state -> Block.BLOCKS_LIGHT_LUMINANCE[state.owner.id]; + @Unique private ToIntFunction stationapi_luminance = state -> Block.BLOCKS_LIGHT_LUMINANCE[state.block.id]; @Override @Unique diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/ChunkMixin.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/ChunkMixin.java index ffb1345f0..c7e1db508 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/ChunkMixin.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/ChunkMixin.java @@ -26,13 +26,13 @@ public BlockState getBlockState(int x, int y, int z) { @Unique public BlockState setBlockState(int x, int y, int z, BlockState blockState) { BlockState oldState = getBlockState(x, y, z); - return setBlock(x, y, z, blockState.owner.id) ? oldState : null; + return setBlock(x, y, z, blockState.block.id) ? oldState : null; } @Override @Unique public BlockState setBlockState(int x, int y, int z, BlockState blockState, int meta) { BlockState oldState = getBlockState(x, y, z); - return setBlock(x, y, z, blockState.owner.id, meta) ? oldState : null; + return setBlock(x, y, z, blockState.block.id, meta) ? oldState : null; } } diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/WorldMixin.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/WorldMixin.java index 794fabe58..269cb52f9 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/WorldMixin.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/WorldMixin.java @@ -43,7 +43,7 @@ public BlockState setBlockStateWithoutNotifyingNeighbors(int x, int y, int z, Bl public BlockState setBlockState(int x, int y, int z, BlockState blockState) { BlockState oldBlockState = setBlockStateWithoutNotifyingNeighbors(x, y, z, blockState); if (oldBlockState != null) { - blockUpdate(x, y, z, blockState.owner.id); + blockUpdate(x, y, z, blockState.block.id); return oldBlockState; } return null; @@ -60,7 +60,7 @@ public BlockState setBlockStateWithoutNotifyingNeighbors(int x, int y, int z, Bl public BlockState setBlockState(int x, int y, int z, BlockState blockState, int meta) { BlockState oldBlockState = setBlockStateWithoutNotifyingNeighbors(x, y, z, blockState, meta); if (oldBlockState != null) { - blockUpdate(x, y, z, blockState.owner.id); + blockUpdate(x, y, z, blockState.block.id); return oldBlockState; } return null; @@ -137,7 +137,7 @@ private int stationapi_changeTickBlockId( @Local(index = 5) Chunk chunk, @Local(index = 8) int x, @Local(index = 10) int y, @Local(index = 9) int z ) { - return chunk.getBlockState(x, y, z).owner.id; + return chunk.getBlockState(x, y, z).block.id; } @ModifyConstant(method = { @@ -248,7 +248,7 @@ public int getBottomY() { ) ) private Block stationapi_accountForAirBlock(Block value) { - return value == States.AIR.get().owner ? null : value; + return value == States.AIR.get().block ? null : value; } @ModifyVariable( diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/mixin/render/client/BlockRenderManagerMixin.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/mixin/render/client/BlockRenderManagerMixin.java index 32836ae4d..b9b8cb3c4 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/mixin/render/client/BlockRenderManagerMixin.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/mixin/render/client/BlockRenderManagerMixin.java @@ -31,7 +31,7 @@ abstract class BlockRenderManagerMixin implements StationRendererBlockRenderMana @Unique public void renderAllSides(BlockState state, int x, int y, int z) { if (StationRenderAPI.getBakedModelManager().getBlockModels().getModel(state) instanceof VanillaBakedModel) - renderWithoutCulling(state.owner, x, y, z); + renderWithoutCulling(state.block, x, y, z); else if (RendererAccess.INSTANCE.hasRenderer()) RendererAccess.INSTANCE.getRenderer().bakedModelRenderer().renderBlock(state, stationapi_pos.set(x, y, z), blockView, false, stationapi_random); } From 1fb6dcf072586b5ec50179f4dc7c08d988018a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Sun, 26 Jul 2026 21:48:42 +0200 Subject: [PATCH 24/26] deimport the imported import --- .../java/net/modificationstation/stationapi/api/state/State.java | 1 - 1 file changed, 1 deletion(-) diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/state/State.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/state/State.java index e31447fdc..bccd458fa 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/state/State.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/state/State.java @@ -6,7 +6,6 @@ import net.modificationstation.stationapi.api.state.property.Property; import net.modificationstation.stationapi.api.util.collection.FastImmutableTable; import net.modificationstation.stationapi.api.util.collection.FastImmutableTableCache; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.util.*; From 351aaac5816709d378a873dc2e184573f2ed5915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Sun, 26 Jul 2026 22:17:25 +0200 Subject: [PATCH 25/26] make this immutable --- .../api/client/render/model/WeightedBakedModel.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/WeightedBakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/WeightedBakedModel.java index c77c57a33..b39530098 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/WeightedBakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/WeightedBakedModel.java @@ -19,11 +19,13 @@ @Environment(EnvType.CLIENT) public class WeightedBakedModel implements BakedModel { private final int totalWeight; - private final List models; + private final List models; + private final ImmutableList immutableModels; private final BakedModel defaultModel; - public WeightedBakedModel(List models) { + public WeightedBakedModel(List models) { this.models = models; + this.immutableModels = ImmutableList.copyOf(models); this.totalWeight = WeightedPicker.getWeightSum(models); this.defaultModel = models.get(0).model; } @@ -69,8 +71,8 @@ public int getTotalWeight() { return totalWeight; } - public List getModels() { - return models; + public ImmutableList getModels() { + return immutableModels; } public BakedModel getDefaultModel() { From 281d11c0e2c8718c2c4ae94c7a183957d5559d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fild=C3=A1n?= Date: Sun, 26 Jul 2026 22:22:04 +0200 Subject: [PATCH 26/26] Literally saved 50% of memory on models --- .../api/client/render/model/WeightedBakedModel.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/WeightedBakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/WeightedBakedModel.java index b39530098..05a892397 100644 --- a/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/WeightedBakedModel.java +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/WeightedBakedModel.java @@ -20,12 +20,10 @@ public class WeightedBakedModel implements BakedModel { private final int totalWeight; private final List models; - private final ImmutableList immutableModels; private final BakedModel defaultModel; public WeightedBakedModel(List models) { - this.models = models; - this.immutableModels = ImmutableList.copyOf(models); + this.models = List.copyOf(models); this.totalWeight = WeightedPicker.getWeightSum(models); this.defaultModel = models.get(0).model; } @@ -71,8 +69,8 @@ public int getTotalWeight() { return totalWeight; } - public ImmutableList getModels() { - return immutableModels; + public List getModels() { + return models; } public BakedModel getDefaultModel() {