diff --git a/build.gradle.kts b/build.gradle.kts index 3b94e16e2..007c1fa24 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -144,6 +144,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" } 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)); 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..1e3ceb720 --- /dev/null +++ b/src/test/java/net/modificationstation/sltest/render/ModelLoadingListener.java @@ -0,0 +1,81 @@ +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.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": { + "": {"model": "sltest:block/test_block"} + } + }""" + ); + + String dirtBlockstateJson = (""" + { + "variants": { + "": {"model": "sltest:block/dirt"} + } + }""" + ); + + @EventListener + 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 = (""" + { + "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/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-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-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..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 getBlock().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(getBlock().getRegistryEntry()); + return blocks.contains(block.getRegistryEntry()); } public Stream> streamTags() { - return getBlock().getRegistryEntry().streamTags(); + return block.getRegistryEntry().streamTags(); } public boolean isOf(Block block) { - return this.getBlock() == block; + return this.block == block; } public boolean hasRandomTicks() { - return Block.BLOCKS_RANDOM_TICK[getBlock().id]; + return Block.BLOCKS_RANDOM_TICK[block.id]; } @Environment(EnvType.CLIENT) 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 { - -} 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; } 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); 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..f1e08a40d --- /dev/null +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/impl/world/chunk/CachedFlattenedChunk.java @@ -0,0 +1,43 @@ +package net.modificationstation.stationapi.impl.world.chunk; + +import net.minecraft.world.World; + +import java.util.Arrays; + +public class CachedFlattenedChunk extends FlattenedChunk { + 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 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); + section.blockIdCache = blockIdCache; + section.worldBottomY = bottomY; + return section; + } + + @Override + 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); + } + + 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..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,6 +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); + + // Used for CachedFlattenedChunk + public int worldBottomY = 0; + public short[] blockIdCache = null; public ChunkSection(int chunkPos, PalettedContainer blockStateContainer) { this.yOffset = (short) ChunkSection.blockCoordFromChunkCoord(chunkPos); @@ -50,6 +54,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 - worldBottomY) + 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..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 @@ -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; } @@ -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).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.getBlock(); + 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.getBlock().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.getBlock().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.getBlock(); + 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.getBlock().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.getBlock().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/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); } /** 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..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.getBlock().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 9c4d9cdf9..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.getBlock().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.getBlock().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/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/WorldMixin.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/WorldMixin.java index 2f43365b2..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.getBlock().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.getBlock().id); + blockUpdate(x, y, z, blockState.block.id); return oldBlockState; } return null; @@ -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( @@ -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).block.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().block ? null : value; } @ModifyVariable( 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); 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-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; } 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) { 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/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..e51046b00 --- /dev/null +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/ModelVariantMapOverrideEvent.java @@ -0,0 +1,53 @@ +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.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 + */ +@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; + /** + * 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} + * + * @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 new file mode 100644 index 000000000..a5c6871b0 --- /dev/null +++ b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/event/render/model/UnbakedModelLoadingFinishedEvent.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 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 aea0398ca..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 @@ -129,7 +129,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()); - for (Map.Entry> entry : blockStates2.entrySet()) + for (Map.Entry> entry : blockStates2.entrySet()) { list.add(CompletableFuture.supplyAsync(() -> { List resources = entry.getValue(); List list2 = new ArrayList<>(resources.size()); @@ -141,6 +141,7 @@ private static CompletableFuture blockStates.stream().filter(Objects::nonNull).collect(Collectors.toUnmodifiableMap(Pair::getFirst, Pair::getSecond))); }); } @@ -187,7 +188,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..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 @@ -16,8 +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.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; @@ -55,7 +54,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,7 +85,11 @@ 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.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(); } @@ -215,35 +225,38 @@ 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; 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).stateManager(stateManager).deserializationContext(variantMapDeserializationContext).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()); @@ -261,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/UnbakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/UnbakedModel.java index 768599d93..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 @@ -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 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/WeightedBakedModel.java b/station-renderer-api-v0/src/main/java/net/modificationstation/stationapi/api/client/render/model/WeightedBakedModel.java index a9059606f..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 @@ -19,11 +19,11 @@ @Environment(EnvType.CLIENT) public class WeightedBakedModel implements BakedModel { private final int totalWeight; - private final List models; + private final List models; private final BakedModel defaultModel; - public WeightedBakedModel(List models) { - this.models = models; + public WeightedBakedModel(List models) { + this.models = List.copyOf(models); this.totalWeight = WeightedPicker.getWeightSum(models); this.defaultModel = models.get(0).model; } @@ -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..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 @@ -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; @@ -28,8 +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(); @@ -94,6 +95,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()); @@ -130,6 +143,30 @@ public void setParents(Function modelLoader) { }); } + @Override + public Collection getTextures(Function modelLoader, Set> unresolvedTextureReferences) { + Set textures = 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)); + } + + textures.add(spriteIdentifier); + } + } + + if (this.getRootModel() == ModelLoader.GENERATION_MARKER) { + ItemModelGenerator.LAYERS.forEach((string) -> textures.add(this.resolveSprite(string))); + } + + return textures; + } + @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..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 @@ -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 getTextures(Function modelLoader, Set> unresolvedTextureReferences) { + return this.getComponents().stream().flatMap((multipartModelComponent) -> multipartModelComponent.getModel().getTextures(modelLoader, 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..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 @@ -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 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 public BakedModel bake(Baker baker, Function textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { if (this.getVariants().isEmpty()) { 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..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.getBlock(), 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); } 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) {