25w07a - rewrite dynamic resources, drop faster_item_rendering and wall block deduplication (for now)

This commit is contained in:
embeddedt 2025-02-15 12:13:07 -05:00
parent 37cb92a64f
commit 52d4aba120
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
28 changed files with 586 additions and 1146 deletions

View File

@ -21,7 +21,11 @@ dependencies {
modCompileOnly "curse.maven:spark-361579:${rootProject.spark_version}"
modCompileOnly fabricApi.module("fabric-model-loading-api-v1", rootProject.fabric_api_version)
try {
modCompileOnly fabricApi.module("fabric-model-loading-api-v1", rootProject.fabric_api_version)
} catch(ignored) {
println "Model Loading API not found for this Minecraft version!"
}
// Remove the next line if you don't want to depend on the API
// modApi "me.shedaniel:architectury:${rootProject.architectury_version}"
}

View File

@ -1,11 +1,5 @@
package org.embeddedt.modernfix.api.entrypoint;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.UnbakedModel;
/**
* Implement this interface in a mod class and add it to "modernfix:integration_v1" in your mod metadata file
* to integrate with ModernFix's features.
@ -19,19 +13,4 @@ public interface ModernFixClientIntegration {
*/
default void onDynamicResourcesStatusChange(boolean enabled) {
}
/**
* Called to allow mods to observe the loading of a baked model and either make changes to it or wrap it with their
* own instance.
*
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
* @param originalModel the original model
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
* with dynamic resources on
* @param textureGetter function to retrieve textures for this model
* @return the model which should actually be loaded for this resource location
*/
default BakedModel onBakedModelLoad(ModelResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter textureGetter) {
return originalModel;
}
}

View File

@ -1,76 +0,0 @@
package org.embeddedt.modernfix.api.helpers;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.resources.model.*;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.dynamicresources.ModelBakeryHelpers;
import org.embeddedt.modernfix.util.DynamicMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
@SuppressWarnings("unused")
public final class ModelHelpers {
/**
* Allows converting a ModelResourceLocation back into the corresponding BlockState(s). Try to avoid calling this
* multiple times if possible.
* @param location the location of the model
* @return a list of all blockstates related to the model
*/
public static ImmutableList<BlockState> getBlockStateForLocation(ModelResourceLocation location) {
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(location.id());
if(blockOpt.isPresent())
return ModelBakeryHelpers.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), location);
else
return ImmutableList.of();
}
/**
* Allows converting a ModelResourceLocation back into the corresponding BlockState(s). Faster version of its
* companion function if and only if you know the corresponding Block already for some reason.
* @param definition the state definition for the Block
* @param location the location of the model
* @return a list of all blockstates related to the model
*/
public static ImmutableList<BlockState> getBlockStateForLocation(StateDefinition<Block, BlockState> definition, ModelResourceLocation location) {
return ModelBakeryHelpers.getBlockStatesForMRL(definition, location);
}
/**
* Compatibility helper for mods to use to get a map-like view of the model bakery.
* @param modelGetter the model getter function supplied by the integration class
* @return a fake map of the top-level models
*/
public static Map<ResourceLocation, BakedModel> createFakeTopLevelMap(BiFunction<ResourceLocation, ModelState, BakedModel> modelGetter) {
return new DynamicMap<>(ResourceLocation.class, location -> modelGetter.apply(location, BlockModelRotation.X0_Y0));
}
/**
* Provides a ModelBaker for mods to use.
* @param bakery the ModelBakery supplied to your integration
* @return an appropriate ModelBaker
*/
public static ModelBaker adaptBakery(ModelBakery bakery) {
throw new UnsupportedOperationException("TODO");
/*
return new ModelBaker() {
@Override
public UnbakedModel getModel(ResourceLocation resourceLocation) {
return bakery.getModel(resourceLocation);
}
@Nullable
@Override
public BakedModel bake(ResourceLocation resourceLocation, ModelState modelState) {
return ((IExtendedModelBakery)bakery).bakeDefault(resourceLocation, modelState);
}
};
*/
}
}

View File

@ -47,8 +47,7 @@ public abstract class ServerLevelMixin extends Level implements IServerLevel {
@Inject(method = "<init>", at = @At("TAIL"))
private void ensureGeneration(CallbackInfo ci) {
mfix$strongholdCache = this.getDataStorage().computeIfAbsent(
StrongholdLocationCache.factory((ServerLevel)(Object)this),
StrongholdLocationCache.getFileId(this.dimensionTypeRegistration()));
StrongholdLocationCache.TYPE);
this.chunkSource.getGeneratorState().ensureStructuresGenerated();
}

View File

@ -16,6 +16,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* Most wall blocks use the default set of vanilla properties, and the default sizes for their shapes. This means
@ -31,36 +32,5 @@ public abstract class WallBlockMixin extends Block {
super(properties);
}
@Inject(method = "makeShapes", at = @At("HEAD"), cancellable = true)
private synchronized void useCachedShapeMap(float f1, float f2, float f3, float f4, float f5, float f6, CallbackInfoReturnable<Map<BlockState, VoxelShape>> cir) {
ImmutableList<Float> key = ImmutableList.of(f1, f2, f3, f4, f5, f6);
Pair<Map<Map<Property<?>, Comparable<?>>, VoxelShape>, StateDefinition<Block, BlockState>> cache = CACHE_BY_SHAPE_VALS.get(key);
// require the properties to be identical
if(cache == null || !cache.getSecond().getProperties().equals(this.stateDefinition.getProperties()))
return;
ImmutableMap.Builder<BlockState, VoxelShape> builder = ImmutableMap.builder();
for(BlockState state : this.stateDefinition.getPossibleStates()) {
VoxelShape shape = cache.getFirst().get(state.getValues());
if(shape == null)
return; // fallback to vanilla logic
builder.put(state, shape);
}
cir.setReturnValue(builder.build());
}
@Inject(method = "makeShapes", at = @At("RETURN"))
private synchronized void storeCachedShapesByProperty(float f1, float f2, float f3, float f4, float f5, float f6, CallbackInfoReturnable<Map<BlockState, VoxelShape>> cir) {
// never populate cache as a non-vanilla block
if((Class<?>)this.getClass() != WallBlock.class)
return;
ImmutableList<Float> key = ImmutableList.of(f1, f2, f3, f4, f5, f6);
if(!CACHE_BY_SHAPE_VALS.containsKey(key)) {
Map<Map<Property<?>, Comparable<?>>, VoxelShape> cacheByProperties = new HashMap<>();
Map<BlockState, VoxelShape> shapeMap = cir.getReturnValue();
for(Map.Entry<BlockState, VoxelShape> entry : shapeMap.entrySet()) {
cacheByProperties.put(entry.getKey().getValues(), entry.getValue());
}
CACHE_BY_SHAPE_VALS.put(key, Pair.of(cacheByProperties, this.stateDefinition));
}
}
// TODO reimplement for 1.21.5
}

View File

@ -1,75 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.IModelHoldingBlockState;
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
import org.embeddedt.modernfix.util.DynamicOverridableMap;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
@Mixin(BlockModelShaper.class)
@ClientOnlyMixin
public class BlockModelShaperMixin {
@Shadow @Final private ModelManager modelManager;
@Shadow
private Map<BlockState, BakedModel> modelByStateCache;
@Inject(method = { "<init>", "replaceCache" }, at = @At("RETURN"))
private void replaceModelMap(CallbackInfo ci) {
// replace the backing map for mods which will access it
this.modelByStateCache = new DynamicOverridableMap<>(BlockState.class, state -> modelManager.getModel(ModelLocationCache.get(state)));
// Clear the cached models on blockstate objects
for(Block block : BuiltInRegistries.BLOCK) {
for(BlockState state : block.getStateDefinition().getPossibleStates()) {
if(state instanceof IModelHoldingBlockState modelHolder) {
modelHolder.mfix$setModel(null);
}
}
}
}
private BakedModel cacheBlockModel(BlockState state) {
// Do all model system accesses in the unlocked path
ModelResourceLocation mrl = ModelLocationCache.get(state);
BakedModel model = mrl == null ? null : modelManager.getModel(mrl);
if (model == null) {
model = modelManager.getMissingModel();
}
return model;
}
/**
* @author embeddedt
* @reason get the model from the dynamic model provider
*/
@Overwrite
public BakedModel getBlockModel(BlockState state) {
if(state instanceof IModelHoldingBlockState modelHolder) {
BakedModel model = modelHolder.mfix$getModel();
if(model != null) {
return model;
}
model = this.cacheBlockModel(state);
modelHolder.mfix$setModel(model);
return model;
} else {
return this.cacheBlockModel(state);
}
}
}

View File

@ -1,6 +1,6 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.renderer.block.model.BlockStateModel;
import net.minecraft.world.level.block.state.BlockBehaviour;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.IModelHoldingBlockState;
@ -11,16 +11,16 @@ import java.lang.ref.SoftReference;
@Mixin(BlockBehaviour.BlockStateBase.class)
@ClientOnlyMixin
public class BlockStateBaseMixin implements IModelHoldingBlockState {
private volatile SoftReference<BakedModel> mfix$model;
private volatile SoftReference<BlockStateModel> mfix$model;
@Override
public BakedModel mfix$getModel() {
public BlockStateModel mfix$getModel() {
var ref = mfix$model;
return ref != null ? ref.get() : null;
}
@Override
public void mfix$setModel(BakedModel model) {
public void mfix$setModel(BlockStateModel model) {
mfix$model = model != null ? new SoftReference<>(model) : null;
}
}

View File

@ -15,7 +15,7 @@ public class ModelDiscoveryMixin {
* @reason We will show the warning ourselves later when loading the model dynamically, this is just spam since
* the models don't exist during early loading
*/
@Redirect(method = "loadBlockModel", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V"))
@Redirect(method = "method_68027", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V"))
private void disableMissingModelWarning(Logger instance, String s, Object o) {
}

View File

@ -1,24 +1,26 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.Maps;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.geom.EntityModelSet;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.renderer.block.model.BlockStateModel;
import net.minecraft.client.renderer.item.ClientItem;
import net.minecraft.client.renderer.item.ItemModel;
import net.minecraft.client.resources.model.AtlasSet;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.BlockStateModelLoader;
import net.minecraft.client.resources.model.ClientItemInfoLoader;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.apache.commons.lang3.ArrayUtils;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.IModelHoldingBlockState;
import org.embeddedt.modernfix.dynamicresources.DynamicModelProvider;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@ -32,13 +34,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@Mixin(ModelManager.class)
@ClientOnlyMixin
public class ModelManagerMixin implements DynamicModelProvider.ModelManagerExtension {
@Shadow private Map<ModelResourceLocation, BakedModel> bakedBlockStateModels;
@Shadow private Map<ResourceLocation, ItemModel> bakedItemStackModels;
@Shadow private Map<ResourceLocation, ClientItem.Properties> itemProperties;
@ -50,18 +52,43 @@ public class ModelManagerMixin implements DynamicModelProvider.ModelManagerExten
return CompletableFuture.completedFuture(Map.of());
}
@Redirect(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/BlockStateModelLoader;loadBlockStates(Lnet/minecraft/client/resources/model/UnbakedModel;Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
private CompletableFuture<BlockStateModelLoader.LoadedModels> deferBlockStateLoad(UnbakedModel unbakedModel, ResourceManager resourceManager, Executor executor) {
@Redirect(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/BlockStateModelLoader;loadBlockStates(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
private CompletableFuture<BlockStateModelLoader.LoadedModels> deferBlockStateLoad(ResourceManager resourceManager, Executor executor) {
return CompletableFuture.completedFuture(new BlockStateModelLoader.LoadedModels(Map.of()));
}
/**
* @author embeddedt
* @reason disable map creation
* @reason disable map creation, use dynamic dispatch
*/
@Overwrite
private static Map<BlockState, BakedModel> createBlockStateToModelDispatch(Map<ModelResourceLocation, BakedModel> map, BakedModel bakedModel) {
return Map.of();
private static Map<BlockState, BlockStateModel> createBlockStateToModelDispatch(Map<BlockState, BlockStateModel> map, BlockStateModel missingModel) {
var dynamicProvider = Objects.requireNonNull(DynamicModelProvider.currentReloadingModelProvider.get());
var dynamicRegistry = dynamicProvider.getTopLevelEmulatedRegistry();
return new ForwardingMap<>() {
@Override
protected Map<BlockState, BlockStateModel> delegate() {
return dynamicRegistry;
}
@Override
public BlockStateModel get(Object key) {
BlockStateModel result;
if (key instanceof IModelHoldingBlockState state) {
result = state.mfix$getModel();
if (result != null) {
return result;
}
}
result = dynamicRegistry.getOrDefault(key, dynamicProvider.getMissingBakedModel());
if (key instanceof IModelHoldingBlockState state) {
state.mfix$setModel(result);
}
return result;
}
};
}
@Redirect(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ClientItemInfoLoader;scheduleLoad(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
@ -86,9 +113,15 @@ public class ModelManagerMixin implements DynamicModelProvider.ModelManagerExten
@Inject(method = "apply", at = @At("RETURN"))
private void setModelRegistries(CallbackInfo ci) {
this.bakedBlockStateModels = this.mfix$modelProvider.getTopLevelEmulatedRegistry();
this.bakedItemStackModels = this.mfix$modelProvider.getItemModelEmulatedRegistry();
this.itemProperties = this.mfix$modelProvider.getItemPropertiesEmulatedRegistry();
for(Block block : BuiltInRegistries.BLOCK) {
for(BlockState state : block.getStateDefinition().getPossibleStates()) {
if(state instanceof IModelHoldingBlockState modelHolder) {
modelHolder.mfix$setModel(null);
}
}
}
}
@Override

View File

@ -1,70 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.faster_item_rendering;
import net.minecraft.client.renderer.block.model.ItemTransform;
import net.minecraft.client.renderer.item.ItemStackRenderState;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.SimpleBakedModel;
import net.minecraft.world.item.ItemDisplayContext;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.render.FastItemRenderType;
import org.embeddedt.modernfix.render.RenderState;
import org.embeddedt.modernfix.render.SimpleItemModelView;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
@Mixin(value = ItemStackRenderState.LayerRenderState.class, priority = 600)
@ClientOnlyMixin
public abstract class LayerRenderStateMixin {
@Shadow(aliases = {"this$0"}) @Final private ItemStackRenderState field_55345;
@Shadow abstract ItemTransform transform();
@Unique
private final SimpleItemModelView modelView = new SimpleItemModelView();
/**
* If a model
* - is a vanilla item model (SimpleBakedModel),
* - has no custom GUI transforms, and
* - is being rendered in 2D on a GUI
* we do not need to go through the process of rendering every quad. Just render the south ones (the ones facing the
* camera).
*/
@ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/ItemRenderer;renderItem(Lnet/minecraft/world/item/ItemDisplayContext;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;II[ILnet/minecraft/client/resources/model/BakedModel;Lnet/minecraft/client/renderer/RenderType;Lnet/minecraft/client/renderer/item/ItemStackRenderState$FoilType;)V"), index = 6)
private BakedModel useSimpleWrappedItemModel(BakedModel model) {
var transformType = ((ItemStackRenderStateAccessor)this.field_55345).getDisplayContext();
// Forge composite models split themselves into a smaller simple model, we need to detect that the parent
// was not simple
// TODO 1.21.4 - I don't think that is needed anymore with the changes to item rendering
/*
if(originalModel != null && originalModel.getClass() != SimpleBakedModel.class) {
return model;
}
*/
if(!RenderState.IS_RENDERING_LEVEL && model.getClass() == SimpleBakedModel.class && transformType == ItemDisplayContext.GUI) {
FastItemRenderType type;
ItemTransform transform = this.transform();
if(transform == ItemTransform.NO_TRANSFORM)
type = FastItemRenderType.SIMPLE_ITEM;
else if(model.isGui3d() && isBlockTransforms(transform))
type = FastItemRenderType.SIMPLE_BLOCK;
else
return model;
modelView.setItem(model);
modelView.setType(type);
return modelView;
} else
return model;
}
private boolean isBlockTransforms(ItemTransform transform) {
return transform.rotation.x() == 30f
&& transform.rotation.y() == 225f
&& transform.rotation.z() == 0f;
}
}

View File

@ -1,31 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.client.renderer.block.model.multipart.Selector;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
@Mixin(Selector.class)
@ClientOnlyMixin
public class SelectorMixin {
private ConcurrentHashMap<StateDefinition<Block, BlockState>, Predicate<BlockState>> predicateCache = new ConcurrentHashMap<>();
@Inject(method = "getPredicate", at = @At("HEAD"), cancellable = true)
private void useCachedPredicate(StateDefinition<Block, BlockState> pState, CallbackInfoReturnable<Predicate<BlockState>> cir) {
Predicate<BlockState> cached = this.predicateCache.get(pState);
if(cached != null)
cir.setReturnValue(cached);
}
@Inject(method = "getPredicate", at = @At("RETURN"))
private void storeCachedPredicate(StateDefinition<Block, BlockState> pState, CallbackInfoReturnable<Predicate<BlockState>> cir) {
this.predicateCache.put(pState, cir.getReturnValue());
}
}

View File

@ -1,8 +1,8 @@
package org.embeddedt.modernfix.common.mixin.perf.model_optimizations;
import com.mojang.math.Transformation;
import org.joml.Matrix4f;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.joml.Matrix4fc;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@ -13,7 +13,7 @@ import java.util.Objects;
@Mixin(Transformation.class)
@ClientOnlyMixin
public class TransformationMatrixMixin {
@Shadow @Final private Matrix4f matrix;
@Shadow @Final private Matrix4fc matrix;
private Integer cachedHashCode = null;
/**
* @author embeddedt

View File

@ -1,7 +0,0 @@
package org.embeddedt.modernfix.duck;
import net.minecraft.client.resources.model.ModelResourceLocation;
public interface IBlockStateModelLoader {
void loadSpecificBlock(ModelResourceLocation location);
}

View File

@ -1,14 +0,0 @@
package org.embeddedt.modernfix.duck;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import java.util.concurrent.locks.ReentrantLock;
public interface IExtendedModelBakery {
void mfix$tick();
void mfix$finishLoading();
UnbakedModel mfix$loadUnbakedModelDynamic(ModelResourceLocation location);
UnbakedModel mfix$getMissingModel();
ReentrantLock mfix$getLock();
}

View File

@ -1,8 +1,8 @@
package org.embeddedt.modernfix.duck;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.renderer.block.model.BlockStateModel;
public interface IModelHoldingBlockState {
BakedModel mfix$getModel();
void mfix$setModel(BakedModel model);
BlockStateModel mfix$getModel();
void mfix$setModel(BlockStateModel model);
}

View File

@ -0,0 +1,86 @@
package org.embeddedt.modernfix.dynamicresources;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
public class BlockStateSet implements Set<BlockState> {
private static final BlockStateSet INSTANCE = new BlockStateSet();
private BlockStateSet() {
}
public static BlockStateSet instance() {
return INSTANCE;
}
@Override
public int size() {
return Block.BLOCK_STATE_REGISTRY.size();
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean contains(Object o) {
return o instanceof BlockState;
}
@Override
public @NotNull Iterator<BlockState> iterator() {
return Block.BLOCK_STATE_REGISTRY.iterator();
}
@Override
public @NotNull Object[] toArray() {
throw new UnsupportedOperationException();
}
@Override
public @NotNull <T> T[] toArray(@NotNull T[] a) {
throw new UnsupportedOperationException();
}
@Override
public boolean add(BlockState blockState) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll(@NotNull Collection<?> c) {
return c.stream().allMatch(o -> o instanceof BlockState);
}
@Override
public boolean addAll(@NotNull Collection<? extends BlockState> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(@NotNull Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(@NotNull Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
}

View File

@ -10,28 +10,34 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.mojang.serialization.JsonOps;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import it.unimi.dsi.fastutil.objects.ReferenceSets;
import net.minecraft.client.model.geom.EntityModelSet;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.renderer.block.model.BlockModelDefinition;
import net.minecraft.client.renderer.block.model.BlockStateModel;
import net.minecraft.client.renderer.block.model.ItemModelGenerator;
import net.minecraft.client.renderer.block.model.UnbakedBlockStateModel;
import net.minecraft.client.renderer.block.model.SimpleModelWrapper;
import net.minecraft.client.renderer.item.ClientItem;
import net.minecraft.client.renderer.item.ItemModel;
import net.minecraft.client.renderer.item.MissingItemModel;
import net.minecraft.client.renderer.item.ModelRenderProperties;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.AtlasSet;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.BlockModelRotation;
import net.minecraft.client.resources.model.BlockStateDefinitions;
import net.minecraft.client.resources.model.BlockStateModelLoader;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.resources.model.MissingBlockModel;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelDebugName;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.ModelDiscovery;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.ResolvableModel;
import net.minecraft.client.resources.model.ResolvedModel;
import net.minecraft.client.resources.model.SpriteGetter;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.registries.BuiltInRegistries;
@ -75,7 +81,10 @@ public class DynamicModelProvider {
private final LoadingCache<ResourceLocation, Optional<UnbakedModel>> loadedBlockModels =
this.makeLoadingCache(this::loadBlockModel);
private final LoadingCache<ModelResourceLocation, Optional<BakedModel>> loadedBakedModels =
private final LoadingCache<ResourceLocation, Optional<ModelDiscovery.ModelWrapper>> resolvedBlockModels =
this.makeLoadingCache(this::resolveBlockModel);
private final LoadingCache<BlockState, Optional<BlockStateModel>> loadedBakedModels =
this.makeLoadingCache(this::loadBakedModel);
private final LoadingCache<ResourceLocation, Optional<ClientItem>> loadedClientItemProperties =
@ -84,23 +93,26 @@ public class DynamicModelProvider {
private final LoadingCache<ResourceLocation, Optional<ItemModel>> loadedItemModels =
this.makeLoadingCache(this::loadItemModel);
/*
private final LoadingCache<ResourceLocation, Optional<BakedModel>> loadedStandaloneModels =
this.makeLoadingCache(this::loadStandaloneModel);
private final BakedModel missingModel;
*/
private final BlockStateModel missingModel;
private final ModelDiscovery.ModelWrapper resolvedMissingModel;
private final ItemModel missingItemModel;
private final UnbakedModel unbakedMissingModel;
private final Function<ResourceLocation, StateDefinition<Block, BlockState>> stateMapper;
private final ResourceManager resourceManager;
private final ModelBakery.TextureGetter textureGetter;
private final DynamicResolver resolver;
private final SpriteGetter textureGetter;
private final EntityModelSet entityModelSet;
private final ItemModelGenerator itemModelGenerator;
private final Map<ModelResourceLocation, BakedModel> mrlModelOverrides = new ConcurrentHashMap<>();
private final Map<BlockState, BlockStateModel> mrlModelOverrides = new ConcurrentHashMap<>();
private final Map<ResourceLocation, ItemModel> itemStackModelOverrides = new ConcurrentHashMap<>();
private final Map<ResourceLocation, BakedModel> standaloneModelOverrides = new ConcurrentHashMap<>();
private final Map<ModelResourceLocation, UnbakedBlockStateModel> unbakedBlockStateModelOverrides = new ConcurrentHashMap<>();
//private final Map<ResourceLocation, BakedModel> standaloneModelOverrides = new ConcurrentHashMap<>();
private final Map<BlockState, BlockStateModel.Unbaked> unbakedBlockStateModelOverrides = new ConcurrentHashMap<>();
private final List<DynamicModelProvider.DynamicModelPlugin> pluginList = new ArrayList<>();
@ -111,39 +123,54 @@ public class DynamicModelProvider {
this.unbakedMissingModel = MissingBlockModel.missingModel();
this.entityModelSet = entityModelSet;
var missing = atlasMap.get(TextureAtlas.LOCATION_BLOCKS).missing();
this.textureGetter = new ModelBakery.TextureGetter() {
this.textureGetter = new SpriteGetter() {
@Override
public TextureAtlasSprite get(ModelDebugName modelDebugName, Material material) {
public TextureAtlasSprite get(Material material, ModelDebugName modelDebugName) {
var atlas = atlasMap.get(material.atlasLocation());
var sprite = atlas.getSprite(material.texture());
if (sprite != null) {
return sprite;
} else {
ModernFix.LOGGER.warn("Unable to find sprite '{}' referenced by model '{}'", material.texture(), modelDebugName.get());
ModernFix.LOGGER.warn("Unable to find sprite '{}' referenced by model '{}'", material.texture(), modelDebugName.debugName());
return missing;
}
}
@Override
public TextureAtlasSprite reportMissingReference(ModelDebugName modelDebugName, String string) {
public TextureAtlasSprite reportMissingReference(String string, ModelDebugName modelDebugName) {
return missing;
}
};
this.stateMapper = BlockStateModelLoader.definitionLocationToBlockMapper();
this.stateMapper = BlockStateDefinitions.definitionLocationToBlockStateMapper();
this.resourceManager = resourceManager;
this.resolver = new DynamicResolver();
this.itemModelGenerator = new ItemModelGenerator();
this.missingModel = this.bakeMissingModel();
this.missingItemModel = new MissingItemModel(this.missingModel);
this.resolvedMissingModel = new ModelDiscovery.ModelWrapper(MissingBlockModel.LOCATION, this.unbakedMissingModel, true);
var missingModelBaker = new ModelBaker() {
@Override
public ResolvedModel getModel(ResourceLocation resourceLocation) {
throw new IllegalStateException("Missing model should not have dependencies");
}
@Override
public SpriteGetter sprites() {
return DynamicModelProvider.this.textureGetter;
}
};
var textureSlots = this.resolvedMissingModel.getTopTextureSlots();
var quadCollection = this.resolvedMissingModel.bakeTopGeometry(textureSlots, missingModelBaker, BlockModelRotation.X0_Y0);
var particleSprite = this.resolvedMissingModel.resolveParticleSprite(textureSlots, missingModelBaker);
this.missingModel = new SimpleModelWrapper(quadCollection, resolvedMissingModel.getTopAmbientOcclusion(), particleSprite);
this.missingItemModel = new MissingItemModel(quadCollection.getAll(), new ModelRenderProperties(resolvedMissingModel.getTopGuiLight().lightLikeBlock(), particleSprite, resolvedMissingModel.getTopTransforms()));
try {
Class.forName("net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin");
pluginList.add(new FabricDynamicModelHandler(this, this.resourceManager));
// TODO
//pluginList.add(new FabricDynamicModelHandler(this, this.resourceManager));
} catch(Exception ignored) {
// Fabric API likely not present
}
}
public BakedModel getMissingBakedModel() {
public BlockStateModel getMissingBakedModel() {
return this.missingModel;
}
@ -151,25 +178,15 @@ public class DynamicModelProvider {
return this.missingItemModel;
}
private static final Supplier<Set<ModelResourceLocation>> TOP_LEVEL_LOCATIONS_SUPPLIER = Suppliers.memoizeWithExpiration(() -> {
Set<ModelResourceLocation> set = new HashSet<>();
// Skip going through ModelLocationCache because most of the accesses will be misses
BuiltInRegistries.BLOCK.entrySet().forEach(entry -> {
var location = entry.getKey().location();
for(BlockState state : entry.getValue().getStateDefinition().getPossibleStates()) {
set.add(BlockModelShaper.stateToModelLocation(location, state));
}
});
return Collections.unmodifiableSet(set);
}, 2, TimeUnit.MINUTES);
public Map<ModelResourceLocation, BakedModel> getTopLevelEmulatedRegistry() {
return new EmulatedRegistry<>(ModelResourceLocation.class, this.loadedBakedModels, TOP_LEVEL_LOCATIONS_SUPPLIER, this.mrlModelOverrides);
public Map<BlockState, BlockStateModel> getTopLevelEmulatedRegistry() {
return new EmulatedRegistry<>(BlockState.class, this.loadedBakedModels, BlockStateSet::instance, this.mrlModelOverrides);
}
/*
public Map<ResourceLocation, BakedModel> getStandaloneEmulatedRegistry() {
return new EmulatedRegistry<>(ResourceLocation.class, this.loadedStandaloneModels, Set::of, this.standaloneModelOverrides);
}
*/
public Map<ResourceLocation, ItemModel> getItemModelEmulatedRegistry() {
return new EmulatedRegistry<>(ResourceLocation.class, this.loadedItemModels, BuiltInRegistries.ITEM::keySet, this.itemStackModelOverrides);
@ -348,62 +365,31 @@ public class DynamicModelProvider {
for(Resource resource : resources) {
try(Reader reader = resource.openAsReader()) {
JsonObject jsonObject = GsonHelper.parse(reader);
BlockModelDefinition blockModelDefinition = BlockModelDefinition.fromJsonElement(jsonObject);
BlockModelDefinition blockModelDefinition = BlockModelDefinition.CODEC.decode(JsonOps.INSTANCE, jsonObject).getOrThrow().getFirst();
loadedDefinitions.add(new BlockStateModelLoader.LoadedBlockModelDefinition(resource.sourcePackId(), blockModelDefinition));
} catch(Exception e) {
ModernFix.LOGGER.error("Failed to load blockstate definition {} from pack '{}'", location, resource.sourcePackId(), e);
}
}
var loadedModels = new HashMap<>(BlockStateModelLoader.loadBlockStateDefinitionStack(location, stateDefinition, loadedDefinitions, this.unbakedMissingModel).models());
var loadedModels = new HashMap<>(BlockStateModelLoader.loadBlockStateDefinitionStack(location, stateDefinition, loadedDefinitions).models());
if (!pluginList.isEmpty()) {
loadedModels.replaceAll((mrl, oldModel) -> {
UnbakedBlockStateModel ubm = oldModel.model();
BlockStateModel.Unbaked ubm = oldModel;
for (var plugin : pluginList) {
ubm = plugin.modifyBlockModelOnLoad(ubm, mrl, oldModel.state());
}
if (ubm == oldModel.model()) {
return oldModel;
} else {
return new BlockStateModelLoader.LoadedModel(oldModel.state(), ubm);
ubm = plugin.modifyBlockModelOnLoad(oldModel, mrl);
}
return ubm;
});
}
return Optional.of(new BlockStateModelLoader.LoadedModels(loadedModels));
}
private BakedModel bakeMissingModel() {
this.resolver.clearResolver();
this.unbakedMissingModel.resolveDependencies(this.resolver);
var modelBaker = new DynamicBaker(() -> "missing");
return UnbakedModel.bakeWithTopModelValues(this.unbakedMissingModel, modelBaker, BlockModelRotation.X0_Y0);
}
private BakedModel bakeModel(UnbakedModel model, ResourceLocation location) {
if (DEBUG_DYNAMIC_MODEL_LOADING) {
ModernFix.LOGGER.info("Baking model '{}'", location);
}
synchronized (this) {
this.resolver.clearResolver();
model.resolveDependencies(this.resolver);
var modelBaker = new DynamicBaker(location::toString);
for (var plugin : pluginList) {
model = plugin.modifyModelBeforeBake(model, location, BlockModelRotation.X0_Y0, modelBaker);
}
var bakedModel = UnbakedModel.bakeWithTopModelValues(model, modelBaker, BlockModelRotation.X0_Y0);
for (var plugin : pluginList) {
bakedModel = plugin.modifyModelAfterBake(bakedModel, model, location, BlockModelRotation.X0_Y0, modelBaker);
}
return bakedModel;
}
}
private BakedModel bakeModel(UnbakedBlockStateModel model, ModelResourceLocation mrl) {
private BlockStateModel bakeModel(BlockStateModel.Unbaked model, BlockState mrl) {
if (DEBUG_DYNAMIC_MODEL_LOADING) {
ModernFix.LOGGER.info("Baking model '{}'", mrl);
}
synchronized (this) {
this.resolver.clearResolver();
model.resolveDependencies(this.resolver);
model.resolveDependencies(dep -> {});
var modelBaker = new DynamicBaker(mrl::toString);
for (var plugin : pluginList) {
model = plugin.modifyBlockModelBeforeBake(model, mrl, modelBaker);
@ -416,32 +402,26 @@ public class DynamicModelProvider {
}
}
private Optional<BakedModel> loadBakedModel(ModelResourceLocation location) {
var override = this.mrlModelOverrides.get(location);
private Optional<BlockStateModel> loadBakedModel(BlockState state) {
var override = this.mrlModelOverrides.get(state);
if (override != null) {
return Optional.of(override);
}
if (location.variant().equals("standalone") || location.variant().equals("fabric_resource")) {
return this.loadStandaloneModel(location.id());
if (false) { //location.variant().equals("standalone") || location.variant().equals("fabric_resource")) {
throw new UnsupportedOperationException(); //return this.loadStandaloneModel(location.id());
} else {
Optional<UnbakedBlockStateModel> unbakedModelOpt = Optional.ofNullable(this.unbakedBlockStateModelOverrides.get(location));
Optional<BlockStateModel.Unbaked> unbakedModelOpt = Optional.ofNullable(this.unbakedBlockStateModelOverrides.get(state));
if (unbakedModelOpt.isEmpty()) {
var optLoadedModels = this.loadedStateDefinitions.getUnchecked(location.id());
unbakedModelOpt = optLoadedModels.map(loadedModels -> {
var loadedModel = loadedModels.models().get(location);
if(loadedModel != null) {
return loadedModel.model();
} else {
return null;
}
});
var optLoadedModels = this.loadedStateDefinitions.getUnchecked(state.getBlock().builtInRegistryHolder().key().location());
unbakedModelOpt = optLoadedModels.map(loadedModels -> loadedModels.models().get(state));
}
return unbakedModelOpt.map(unbakedModel -> {
return this.bakeModel(unbakedModel, location);
return this.bakeModel(unbakedModel, state);
});
}
}
/*
private Optional<BakedModel> loadStandaloneModel(ResourceLocation location) {
var override = this.standaloneModelOverrides.get(location);
if (override != null) {
@ -451,6 +431,7 @@ public class DynamicModelProvider {
return this.bakeModel(unbakedModel, location);
});
}
*/
private Optional<UnbakedModel> loadBlockModelDefault(ResourceLocation location) {
if (DEBUG_DYNAMIC_MODEL_LOADING) {
@ -484,6 +465,29 @@ public class DynamicModelProvider {
return value;
}
private Optional<ModelDiscovery.ModelWrapper> resolveBlockModel(ResourceLocation location) {
var unbakedOpt = this.loadedBlockModels.getUnchecked(location);
if (unbakedOpt.isEmpty()) {
return Optional.empty();
}
ModelDiscovery.ModelWrapper wrapper = new ModelDiscovery.ModelWrapper(location, unbakedOpt.get(), true);
var parent = wrapper.wrapped().parent();
if (parent != null) {
Optional<ModelDiscovery.ModelWrapper> resolvedParentOpt;
try {
resolvedParentOpt = this.resolvedBlockModels.getUnchecked(parent);
} catch (Exception e) {
// Possible recursive load, etc.
ModernFix.LOGGER.error("Error while resolving model '{}'", location, e);
return Optional.empty();
}
if (resolvedParentOpt.isPresent()) {
wrapper.parent = resolvedParentOpt.get();
}
}
return Optional.of(wrapper);
}
private Optional<ClientItem> loadClientItemProperties(ResourceLocation location) {
if (DEBUG_DYNAMIC_MODEL_LOADING) {
@ -513,12 +517,12 @@ public class DynamicModelProvider {
return Optional.of(override);
}
return this.loadedClientItemProperties.getUnchecked(location).map(clientItem -> {
var bakingContext = new ItemModel.BakingContext(new DynamicBaker(location::toString), this.entityModelSet, this.missingItemModel);
var bakingContext = new ItemModel.BakingContext(new DynamicBaker(location::toString), this.entityModelSet, this.missingItemModel, clientItem.registrySwapper());
return clientItem.model().bake(bakingContext);
});
}
public BakedModel getModel(ModelResourceLocation location) {
public BlockStateModel getModel(BlockState location) {
return this.loadedBakedModels.getUnchecked(location).orElse(this.missingModel);
}
@ -530,11 +534,14 @@ public class DynamicModelProvider {
return this.loadedItemModels.getUnchecked(location).orElse(this.missingItemModel);
}
/*
public BakedModel getStandaloneModel(ResourceLocation location) {
return this.loadedStandaloneModels.getUnchecked(location).orElse(this.missingModel);
}
public void addUnbakedBlockStateOverride(ModelResourceLocation location, UnbakedBlockStateModel model) {
*/
public void addUnbakedBlockStateOverride(BlockState location, BlockStateModel.Unbaked model) {
this.unbakedBlockStateModelOverrides.put(location, model);
}
@ -546,56 +553,13 @@ public class DynamicModelProvider {
}
@Override
public BakedModel bake(ResourceLocation location, ModelState transform) {
return DynamicModelProvider.this.loadedBlockModels.getUnchecked(location).map(unbakedModel -> {
DynamicModelProvider.this.resolver.clearResolver();
unbakedModel.resolveDependencies(DynamicModelProvider.this.resolver);
return UnbakedModel.bakeWithTopModelValues(unbakedModel, this, transform);
}).orElse(DynamicModelProvider.this.missingModel);
public ResolvedModel getModel(ResourceLocation location) {
return DynamicModelProvider.this.resolvedBlockModels.getUnchecked(location).orElse(DynamicModelProvider.this.resolvedMissingModel);
}
@Override
public SpriteGetter sprites() {
return DynamicModelProvider.this.textureGetter.bind(this.modelDebugName);
}
@Override
public ModelDebugName rootName() {
return this.modelDebugName;
}
}
/**
* Based on the Mojang impl but with some changes to make it slightly more efficient.
*/
private class DynamicResolver implements UnbakedModel.Resolver {
private final Set<ResourceLocation> stack = new ObjectOpenHashSet<>(4);
private final Set<ResourceLocation> resolvedModels = new ObjectOpenHashSet<>();
@Override
public UnbakedModel resolve(ResourceLocation resourceLocation) {
if (this.stack.contains(resourceLocation)) {
ModernFix.LOGGER.warn("Detected model loading loop: {}->{}", this.stacktraceToString(), resourceLocation);
return DynamicModelProvider.this.unbakedMissingModel;
} else {
UnbakedModel unbakedModel = DynamicModelProvider.this.loadedBlockModels.getUnchecked(resourceLocation).orElse(DynamicModelProvider.this.unbakedMissingModel);
if (this.resolvedModels.add(resourceLocation)) {
this.stack.add(resourceLocation);
unbakedModel.resolveDependencies(this);
this.stack.remove(resourceLocation);
}
return unbakedModel;
}
}
private String stacktraceToString() {
return this.stack.stream().map(ResourceLocation::toString).collect(Collectors.joining("->"));
}
public void clearResolver() {
this.stack.clear();
this.resolvedModels.clear();
return DynamicModelProvider.this.textureGetter;
}
}
@ -607,12 +571,12 @@ public class DynamicModelProvider {
public interface DynamicModelPlugin {
Optional<UnbakedModel> modifyModelOnLoad(Optional<UnbakedModel> model, ResourceLocation id);
UnbakedBlockStateModel modifyBlockModelOnLoad(UnbakedBlockStateModel model, ModelResourceLocation id, BlockState state);
BlockStateModel.Unbaked modifyBlockModelOnLoad(BlockStateModel.Unbaked model, BlockState state);
UnbakedModel modifyModelBeforeBake(UnbakedModel model, ResourceLocation id, ModelState state, ModelBaker baker);
BakedModel modifyModelAfterBake(BakedModel bakedModel, UnbakedModel model, ResourceLocation id, ModelState state, ModelBaker baker);
//BakedModel modifyModelAfterBake(BakedModel bakedModel, UnbakedModel model, ResourceLocation id, ModelState state, ModelBaker baker);
UnbakedBlockStateModel modifyBlockModelBeforeBake(UnbakedBlockStateModel model, ModelResourceLocation id, ModelBaker baker);
BakedModel modifyBlockModelAfterBake(BakedModel bakedModel, UnbakedBlockStateModel model, ModelResourceLocation id, ModelBaker baker);
BlockStateModel.Unbaked modifyBlockModelBeforeBake(BlockStateModel.Unbaked model, BlockState state, ModelBaker baker);
BlockStateModel modifyBlockModelAfterBake(BlockStateModel bakedModel, BlockStateModel.Unbaked unbaked, BlockState state, ModelBaker baker);
}
}

View File

@ -1,301 +1,301 @@
package org.embeddedt.modernfix.dynamicresources;
import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver;
import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier;
import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.renderer.block.model.UnbakedBlockStateModel;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.embeddedt.modernfix.ModernFix;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public class FabricDynamicModelHandler implements DynamicModelProvider.DynamicModelPlugin {
// Borrowed from Fabric API, this dispatching logic is extremely trivial
private static final ResourceLocation[] MODEL_MODIFIER_PHASES = new ResourceLocation[] { ModelModifier.OVERRIDE_PHASE, ModelModifier.DEFAULT_PHASE, ModelModifier.WRAP_PHASE, ModelModifier.WRAP_LAST_PHASE };
private final Event<ModelModifier.OnLoad> onLoadModifiers = EventFactory.createWithPhases(ModelModifier.OnLoad.class, modifiers -> (model, context) -> {
for (ModelModifier.OnLoad modifier : modifiers) {
try {
model = modifier.modifyModelOnLoad(model, context);
} catch (Exception exception) {
ModernFix.LOGGER.error("Failed to modify unbaked model on load", exception);
}
}
return model;
}, MODEL_MODIFIER_PHASES);
private final Event<ModelModifier.OnLoadBlock> onLoadBlockModifiers = EventFactory.createWithPhases(ModelModifier.OnLoadBlock.class, modifiers -> (model, context) -> {
for (ModelModifier.OnLoadBlock modifier : modifiers) {
try {
model = modifier.modifyModelOnLoad(model, context);
} catch (Exception exception) {
ModernFix.LOGGER.error("Failed to modify unbaked block model on load", exception);
}
}
return model;
}, MODEL_MODIFIER_PHASES);
private final Event<ModelModifier.BeforeBakeBlock> beforeBakeBlockModifiers = EventFactory.createWithPhases(ModelModifier.BeforeBakeBlock.class, modifiers -> (model, context) -> {
for (ModelModifier.BeforeBakeBlock modifier : modifiers) {
try {
model = modifier.modifyModelBeforeBake(model, context);
} catch (Exception exception) {
ModernFix.LOGGER.error("Failed to modify unbaked block model before bake", exception);
}
}
return model;
}, MODEL_MODIFIER_PHASES);
private final Event<ModelModifier.AfterBakeBlock> afterBakeBlockModifiers = EventFactory.createWithPhases(ModelModifier.AfterBakeBlock.class, modifiers -> (model, context) -> {
for (ModelModifier.AfterBakeBlock modifier : modifiers) {
try {
model = modifier.modifyModelAfterBake(model, context);
} catch (Exception exception) {
ModernFix.LOGGER.error("Failed to modify baked block model after bake", exception);
}
}
return model;
}, MODEL_MODIFIER_PHASES);
private final Event<ModelModifier.BeforeBake> beforeBakeModifiers = EventFactory.createWithPhases(ModelModifier.BeforeBake.class, modifiers -> (model, context) -> {
for (ModelModifier.BeforeBake modifier : modifiers) {
try {
model = modifier.modifyModelBeforeBake(model, context);
} catch (Exception exception) {
ModernFix.LOGGER.error("Failed to modify unbaked model before bake", exception);
}
}
return model;
}, MODEL_MODIFIER_PHASES);
private final Event<ModelModifier.AfterBake> afterBakeModifiers = EventFactory.createWithPhases(ModelModifier.AfterBake.class, modifiers -> (model, context) -> {
for (ModelModifier.AfterBake modifier : modifiers) {
try {
model = modifier.modifyModelAfterBake(model, context);
} catch (Exception exception) {
ModernFix.LOGGER.error("Failed to modify baked model after bake", exception);
}
}
return model;
}, MODEL_MODIFIER_PHASES);
record PreparablePluginData<T>(CompletableFuture<T> data, PreparableModelLoadingPlugin.Holder<T> plugin) {
void initialize(ModelLoadingPlugin.Context context) {
plugin.plugin().initialize(data.join(), context);
}
}
private <T> PreparablePluginData<T> makeDataRecord(ResourceManager manager, PreparableModelLoadingPlugin.Holder<T> holder) {
return new PreparablePluginData<>(holder.loader().load(manager, ModernFix.resourceReloadExecutor()), holder);
}
public FabricDynamicModelHandler(DynamicModelProvider provider, ResourceManager manager) {
List<ModelLoadingPlugin> pluginList = new ArrayList<>(ModelLoadingPlugin.getAll());
var preparablePluginData = new ArrayList<PreparablePluginData<?>>();
for (var holder : PreparableModelLoadingPlugin.getAll()) {
preparablePluginData.add(makeDataRecord(manager, holder));
}
// Wait for all the preparable plugins to finish loading
CompletableFuture.allOf(preparablePluginData.stream().map(PreparablePluginData::data).toArray(CompletableFuture[]::new)).join();
var context = new PluginContext(provider);
for (var plugin : pluginList) {
plugin.initialize(context);
}
for (var data : preparablePluginData) {
data.initialize(context);
}
context.fireResolvers();
}
@Override
public Optional<UnbakedModel> modifyModelOnLoad(Optional<UnbakedModel> model, ResourceLocation id) {
return Optional.ofNullable(this.onLoadModifiers.invoker().modifyModelOnLoad(model.orElse(null), () -> id));
}
@Override
public UnbakedBlockStateModel modifyBlockModelOnLoad(UnbakedBlockStateModel model, ModelResourceLocation id, BlockState state) {
return this.onLoadBlockModifiers.invoker().modifyModelOnLoad(model, new ModelModifier.OnLoadBlock.Context() {
@Override
public ModelResourceLocation id() {
return id;
}
@Override
public BlockState state() {
return state;
}
});
}
@Override
public UnbakedModel modifyModelBeforeBake(UnbakedModel model, ResourceLocation id, ModelState state, ModelBaker baker) {
return beforeBakeModifiers.invoker().modifyModelBeforeBake(model, new ModelModifier.BeforeBake.Context() {
@Override
public ResourceLocation id() {
return id;
}
@Override
public ModelState settings() {
return state;
}
@Override
public ModelBaker baker() {
return baker;
}
});
}
@Override
public BakedModel modifyModelAfterBake(BakedModel bakedModel, UnbakedModel model, ResourceLocation id, ModelState state, ModelBaker baker) {
return afterBakeModifiers.invoker().modifyModelAfterBake(bakedModel, new ModelModifier.AfterBake.Context() {
@Override
public ResourceLocation id() {
return id;
}
@Override
public UnbakedModel sourceModel() {
return model;
}
@Override
public ModelState settings() {
return state;
}
@Override
public ModelBaker baker() {
return baker;
}
});
}
@Override
public UnbakedBlockStateModel modifyBlockModelBeforeBake(UnbakedBlockStateModel model, ModelResourceLocation id, ModelBaker baker) {
return beforeBakeBlockModifiers.invoker().modifyModelBeforeBake(model, new ModelModifier.BeforeBakeBlock.Context() {
@Override
public ModelResourceLocation id() {
return id;
}
@Override
public ModelBaker baker() {
return baker;
}
});
}
@Override
public BakedModel modifyBlockModelAfterBake(BakedModel bakedModel, UnbakedBlockStateModel model, ModelResourceLocation id, ModelBaker baker) {
return afterBakeBlockModifiers.invoker().modifyModelAfterBake(bakedModel, new ModelModifier.AfterBakeBlock.Context() {
@Override
public ModelResourceLocation id() {
return id;
}
@Override
public UnbakedBlockStateModel sourceModel() {
return model;
}
@Override
public ModelBaker baker() {
return baker;
}
});
}
private class PluginContext implements ModelLoadingPlugin.Context {
private final DynamicModelProvider provider;
private final Map<Block, BlockStateResolver> resolvers = new HashMap<>();
private PluginContext(DynamicModelProvider provider) {
this.provider = provider;
}
@Override
public void addModels(ResourceLocation... ids) {
/* no-op on dynamic model loader */
}
@Override
public void addModels(Collection<? extends ResourceLocation> ids) {
/* no-op on dynamic model loader */
}
@Override
public void registerBlockStateResolver(Block block, BlockStateResolver resolver) {
resolvers.put(block, resolver);
}
public void fireResolvers() {
resolvers.forEach((block, resolver) -> {
resolver.resolveBlockStates(new BlockStateResolver.Context() {
@Override
public Block block() {
return block;
}
@Override
public void setModel(BlockState state, UnbakedBlockStateModel model) {
provider.addUnbakedBlockStateOverride(BlockModelShaper.stateToModelLocation(state), model);
}
});
});
}
@Override
public Event<ModelModifier.OnLoad> modifyModelOnLoad() {
return onLoadModifiers;
}
@Override
public Event<ModelModifier.OnLoadBlock> modifyBlockModelOnLoad() {
return onLoadBlockModifiers;
}
@Override
public Event<ModelModifier.BeforeBake> modifyModelBeforeBake() {
return beforeBakeModifiers;
}
@Override
public Event<ModelModifier.AfterBake> modifyModelAfterBake() {
return afterBakeModifiers;
}
@Override
public Event<ModelModifier.BeforeBakeBlock> modifyBlockModelBeforeBake() {
return beforeBakeBlockModifiers;
}
@Override
public Event<ModelModifier.AfterBakeBlock> modifyBlockModelAfterBake() {
return afterBakeBlockModifiers;
}
}
}
//package org.embeddedt.modernfix.dynamicresources;
//
//import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver;
//import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
//import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier;
//import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin;
//import net.fabricmc.fabric.api.event.Event;
//import net.fabricmc.fabric.api.event.EventFactory;
//import net.minecraft.client.renderer.block.BlockModelShaper;
//import net.minecraft.client.renderer.block.model.UnbakedBlockStateModel;
//import net.minecraft.client.resources.model.BakedModel;
//import net.minecraft.client.resources.model.ModelBaker;
//import net.minecraft.client.resources.model.ModelResourceLocation;
//import net.minecraft.client.resources.model.ModelState;
//import net.minecraft.client.resources.model.UnbakedModel;
//import net.minecraft.resources.ResourceLocation;
//import net.minecraft.server.packs.resources.ResourceManager;
//import net.minecraft.world.level.block.Block;
//import net.minecraft.world.level.block.state.BlockState;
//import org.embeddedt.modernfix.ModernFix;
//
//import java.util.ArrayList;
//import java.util.Collection;
//import java.util.HashMap;
//import java.util.List;
//import java.util.Map;
//import java.util.Optional;
//import java.util.concurrent.CompletableFuture;
//
//public class FabricDynamicModelHandler implements DynamicModelProvider.DynamicModelPlugin {
// // Borrowed from Fabric API, this dispatching logic is extremely trivial
//
// private static final ResourceLocation[] MODEL_MODIFIER_PHASES = new ResourceLocation[] { ModelModifier.OVERRIDE_PHASE, ModelModifier.DEFAULT_PHASE, ModelModifier.WRAP_PHASE, ModelModifier.WRAP_LAST_PHASE };
//
// private final Event<ModelModifier.OnLoad> onLoadModifiers = EventFactory.createWithPhases(ModelModifier.OnLoad.class, modifiers -> (model, context) -> {
// for (ModelModifier.OnLoad modifier : modifiers) {
// try {
// model = modifier.modifyModelOnLoad(model, context);
// } catch (Exception exception) {
// ModernFix.LOGGER.error("Failed to modify unbaked model on load", exception);
// }
// }
//
// return model;
// }, MODEL_MODIFIER_PHASES);
//
// private final Event<ModelModifier.OnLoadBlock> onLoadBlockModifiers = EventFactory.createWithPhases(ModelModifier.OnLoadBlock.class, modifiers -> (model, context) -> {
// for (ModelModifier.OnLoadBlock modifier : modifiers) {
// try {
// model = modifier.modifyModelOnLoad(model, context);
// } catch (Exception exception) {
// ModernFix.LOGGER.error("Failed to modify unbaked block model on load", exception);
// }
// }
//
// return model;
// }, MODEL_MODIFIER_PHASES);
//
// private final Event<ModelModifier.BeforeBakeBlock> beforeBakeBlockModifiers = EventFactory.createWithPhases(ModelModifier.BeforeBakeBlock.class, modifiers -> (model, context) -> {
// for (ModelModifier.BeforeBakeBlock modifier : modifiers) {
// try {
// model = modifier.modifyModelBeforeBake(model, context);
// } catch (Exception exception) {
// ModernFix.LOGGER.error("Failed to modify unbaked block model before bake", exception);
// }
// }
//
// return model;
// }, MODEL_MODIFIER_PHASES);
// private final Event<ModelModifier.AfterBakeBlock> afterBakeBlockModifiers = EventFactory.createWithPhases(ModelModifier.AfterBakeBlock.class, modifiers -> (model, context) -> {
// for (ModelModifier.AfterBakeBlock modifier : modifiers) {
// try {
// model = modifier.modifyModelAfterBake(model, context);
// } catch (Exception exception) {
// ModernFix.LOGGER.error("Failed to modify baked block model after bake", exception);
// }
// }
//
// return model;
// }, MODEL_MODIFIER_PHASES);
// private final Event<ModelModifier.BeforeBake> beforeBakeModifiers = EventFactory.createWithPhases(ModelModifier.BeforeBake.class, modifiers -> (model, context) -> {
// for (ModelModifier.BeforeBake modifier : modifiers) {
// try {
// model = modifier.modifyModelBeforeBake(model, context);
// } catch (Exception exception) {
// ModernFix.LOGGER.error("Failed to modify unbaked model before bake", exception);
// }
// }
//
// return model;
// }, MODEL_MODIFIER_PHASES);
// private final Event<ModelModifier.AfterBake> afterBakeModifiers = EventFactory.createWithPhases(ModelModifier.AfterBake.class, modifiers -> (model, context) -> {
// for (ModelModifier.AfterBake modifier : modifiers) {
// try {
// model = modifier.modifyModelAfterBake(model, context);
// } catch (Exception exception) {
// ModernFix.LOGGER.error("Failed to modify baked model after bake", exception);
// }
// }
//
// return model;
// }, MODEL_MODIFIER_PHASES);
//
// record PreparablePluginData<T>(CompletableFuture<T> data, PreparableModelLoadingPlugin.Holder<T> plugin) {
// void initialize(ModelLoadingPlugin.Context context) {
// plugin.plugin().initialize(data.join(), context);
// }
// }
//
// private <T> PreparablePluginData<T> makeDataRecord(ResourceManager manager, PreparableModelLoadingPlugin.Holder<T> holder) {
// return new PreparablePluginData<>(holder.loader().load(manager, ModernFix.resourceReloadExecutor()), holder);
// }
//
// public FabricDynamicModelHandler(DynamicModelProvider provider, ResourceManager manager) {
// List<ModelLoadingPlugin> pluginList = new ArrayList<>(ModelLoadingPlugin.getAll());
// var preparablePluginData = new ArrayList<PreparablePluginData<?>>();
// for (var holder : PreparableModelLoadingPlugin.getAll()) {
// preparablePluginData.add(makeDataRecord(manager, holder));
// }
// // Wait for all the preparable plugins to finish loading
// CompletableFuture.allOf(preparablePluginData.stream().map(PreparablePluginData::data).toArray(CompletableFuture[]::new)).join();
// var context = new PluginContext(provider);
// for (var plugin : pluginList) {
// plugin.initialize(context);
// }
// for (var data : preparablePluginData) {
// data.initialize(context);
// }
// context.fireResolvers();
// }
//
// @Override
// public Optional<UnbakedModel> modifyModelOnLoad(Optional<UnbakedModel> model, ResourceLocation id) {
// return Optional.ofNullable(this.onLoadModifiers.invoker().modifyModelOnLoad(model.orElse(null), () -> id));
// }
//
// @Override
// public UnbakedBlockStateModel modifyBlockModelOnLoad(UnbakedBlockStateModel model, ModelResourceLocation id, BlockState state) {
// return this.onLoadBlockModifiers.invoker().modifyModelOnLoad(model, new ModelModifier.OnLoadBlock.Context() {
// @Override
// public ModelResourceLocation id() {
// return id;
// }
//
// @Override
// public BlockState state() {
// return state;
// }
// });
// }
//
// @Override
// public UnbakedModel modifyModelBeforeBake(UnbakedModel model, ResourceLocation id, ModelState state, ModelBaker baker) {
// return beforeBakeModifiers.invoker().modifyModelBeforeBake(model, new ModelModifier.BeforeBake.Context() {
// @Override
// public ResourceLocation id() {
// return id;
// }
//
// @Override
// public ModelState settings() {
// return state;
// }
//
// @Override
// public ModelBaker baker() {
// return baker;
// }
// });
// }
//
// @Override
// public BakedModel modifyModelAfterBake(BakedModel bakedModel, UnbakedModel model, ResourceLocation id, ModelState state, ModelBaker baker) {
// return afterBakeModifiers.invoker().modifyModelAfterBake(bakedModel, new ModelModifier.AfterBake.Context() {
// @Override
// public ResourceLocation id() {
// return id;
// }
//
// @Override
// public UnbakedModel sourceModel() {
// return model;
// }
//
// @Override
// public ModelState settings() {
// return state;
// }
//
// @Override
// public ModelBaker baker() {
// return baker;
// }
// });
// }
//
// @Override
// public UnbakedBlockStateModel modifyBlockModelBeforeBake(UnbakedBlockStateModel model, ModelResourceLocation id, ModelBaker baker) {
// return beforeBakeBlockModifiers.invoker().modifyModelBeforeBake(model, new ModelModifier.BeforeBakeBlock.Context() {
// @Override
// public ModelResourceLocation id() {
// return id;
// }
//
// @Override
// public ModelBaker baker() {
// return baker;
// }
// });
// }
//
// @Override
// public BakedModel modifyBlockModelAfterBake(BakedModel bakedModel, UnbakedBlockStateModel model, ModelResourceLocation id, ModelBaker baker) {
// return afterBakeBlockModifiers.invoker().modifyModelAfterBake(bakedModel, new ModelModifier.AfterBakeBlock.Context() {
// @Override
// public ModelResourceLocation id() {
// return id;
// }
//
// @Override
// public UnbakedBlockStateModel sourceModel() {
// return model;
// }
//
// @Override
// public ModelBaker baker() {
// return baker;
// }
// });
// }
//
// private class PluginContext implements ModelLoadingPlugin.Context {
// private final DynamicModelProvider provider;
// private final Map<Block, BlockStateResolver> resolvers = new HashMap<>();
//
// private PluginContext(DynamicModelProvider provider) {
// this.provider = provider;
// }
//
// @Override
// public void addModels(ResourceLocation... ids) {
// /* no-op on dynamic model loader */
// }
//
// @Override
// public void addModels(Collection<? extends ResourceLocation> ids) {
// /* no-op on dynamic model loader */
// }
//
// @Override
// public void registerBlockStateResolver(Block block, BlockStateResolver resolver) {
// resolvers.put(block, resolver);
// }
//
// public void fireResolvers() {
// resolvers.forEach((block, resolver) -> {
// resolver.resolveBlockStates(new BlockStateResolver.Context() {
// @Override
// public Block block() {
// return block;
// }
//
// @Override
// public void setModel(BlockState state, UnbakedBlockStateModel model) {
// provider.addUnbakedBlockStateOverride(BlockModelShaper.stateToModelLocation(state), model);
// }
// });
// });
// }
//
// @Override
// public Event<ModelModifier.OnLoad> modifyModelOnLoad() {
// return onLoadModifiers;
// }
//
// @Override
// public Event<ModelModifier.OnLoadBlock> modifyBlockModelOnLoad() {
// return onLoadBlockModifiers;
// }
//
// @Override
// public Event<ModelModifier.BeforeBake> modifyModelBeforeBake() {
// return beforeBakeModifiers;
// }
//
// @Override
// public Event<ModelModifier.AfterBake> modifyModelAfterBake() {
// return afterBakeModifiers;
// }
//
// @Override
// public Event<ModelModifier.BeforeBakeBlock> modifyBlockModelBeforeBake() {
// return beforeBakeBlockModifiers;
// }
//
// @Override
// public Event<ModelModifier.AfterBakeBlock> modifyBlockModelAfterBake() {
// return afterBakeBlockModifiers;
// }
// }
//}

View File

@ -1,68 +0,0 @@
package org.embeddedt.modernfix.dynamicresources;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.resources.model.*;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.Property;
import java.util.*;
public class ModelBakeryHelpers {
private static <T extends Comparable<T>, V extends T> BlockState setPropertyGeneric(BlockState state, Property<T> prop, Object o) {
return state.setValue(prop, (V)o);
}
private static <T extends Comparable<T>> T getValueHelper(Property<T> property, String value) {
return property.getValue(value).orElse(null);
}
private static final Splitter COMMA_SPLITTER = Splitter.on(',');
private static final Splitter EQUAL_SPLITTER = Splitter.on('=').limit(2);
public static ImmutableList<BlockState> getBlockStatesForMRL(StateDefinition<Block, BlockState> stateDefinition, ModelResourceLocation location) {
if(Objects.equals(location.getVariant(), "inventory"))
return ImmutableList.of();
Set<Property<?>> fixedProperties = new HashSet<>();
BlockState fixedState = stateDefinition.any();
for(String s : COMMA_SPLITTER.split(location.getVariant())) {
Iterator<String> iterator = EQUAL_SPLITTER.split(s).iterator();
if (iterator.hasNext()) {
String s1 = iterator.next();
Property<?> property = stateDefinition.getProperty(s1);
if (property != null && iterator.hasNext()) {
String s2 = iterator.next();
Object value = getValueHelper(property, s2);
if (value == null) {
throw new RuntimeException("Unknown value: '" + s2 + "' for blockstate property: '" + s1 + "' " + property.getPossibleValues());
}
fixedState = setPropertyGeneric(fixedState, property, value);
fixedProperties.add(property);
} else if (!s1.isEmpty()) {
throw new RuntimeException("Unknown blockstate property: '" + s1 + "'");
}
}
}
// check if there is only one possible state
if(fixedProperties.size() == stateDefinition.getProperties().size()) {
return ImmutableList.of(fixedState);
}
// generate all possible blockstates from the remaining properties
ArrayList<Property<?>> anyProperties = new ArrayList<>(stateDefinition.getProperties());
anyProperties.removeAll(fixedProperties);
ArrayList<BlockState> finalList = new ArrayList<>();
finalList.add(fixedState);
for(Property<?> property : anyProperties) {
ArrayList<BlockState> newPermutations = new ArrayList<>();
for(BlockState state : finalList) {
for(Comparable<?> value : property.getPossibleValues()) {
newPermutations.add(setPropertyGeneric(state, property, value));
}
}
finalList = newPermutations;
}
return ImmutableList.copyOf(finalList);
}
}

View File

@ -1,52 +0,0 @@
package org.embeddedt.modernfix.dynamicresources;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.state.BlockState;
import java.util.concurrent.ExecutionException;
public class ModelLocationCache {
private static final LoadingCache<BlockState, ModelResourceLocation> blockLocationCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.build(new CacheLoader<BlockState, ModelResourceLocation>() {
@Override
public ModelResourceLocation load(BlockState key) throws Exception {
return BlockModelShaper.stateToModelLocation(key);
}
});
private static final LoadingCache<Item, ModelResourceLocation> itemLocationCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.build(new CacheLoader<Item, ModelResourceLocation>() {
@Override
public ModelResourceLocation load(Item key) throws Exception {
return new ModelResourceLocation(BuiltInRegistries.ITEM.getKey(key), "inventory");
}
});
public static ModelResourceLocation get(BlockState state) {
if(state == null)
return null;
try {
return blockLocationCache.get(state);
} catch(ExecutionException e) {
throw new RuntimeException(e.getCause());
}
}
public static ModelResourceLocation get(Item item) {
if(item == null)
return null;
try {
return itemLocationCache.get(item);
} catch(ExecutionException e) {
throw new RuntimeException(e.getCause());
}
}
}

View File

@ -1,84 +0,0 @@
package org.embeddedt.modernfix.render;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Wrapper class that presents a fake view of item models (only showing the simple front-facing quads), rather
* than every quad.
*/
public class SimpleItemModelView implements BakedModel {
private BakedModel wrappedItem;
private FastItemRenderType type;
public void setItem(BakedModel model) {
this.wrappedItem = model;
}
public void setType(FastItemRenderType type) {
this.type = type;
}
private boolean isCorrectDirectionForType(Direction direction) {
if(type == FastItemRenderType.SIMPLE_ITEM)
return direction == Direction.SOUTH;
else {
return direction == Direction.UP || direction == Direction.EAST || direction == Direction.NORTH;
}
}
private final List<BakedQuad> nullQuadList = new ObjectArrayList<>();
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand) {
boolean isWholeListValid = isCorrectDirectionForType(side);
List<BakedQuad> realList = wrappedItem.getQuads(state, side, rand);
if (isWholeListValid) {
return realList;
}
nullQuadList.clear();
//noinspection ForLoopReplaceableByForEach
for(int i = 0; i < realList.size(); i++) {
BakedQuad quad = realList.get(i);
if(isCorrectDirectionForType(quad.getDirection())) {
nullQuadList.add(quad);
}
}
return nullQuadList;
}
@Override
public boolean useAmbientOcclusion() {
return wrappedItem.useAmbientOcclusion();
}
@Override
public boolean isGui3d() {
return wrappedItem.isGui3d();
}
@Override
public boolean usesBlockLight() {
return wrappedItem.usesBlockLight();
}
@Override
public TextureAtlasSprite getParticleIcon() {
return wrappedItem.getParticleIcon();
}
@Override
public ItemTransforms getTransforms() {
return wrappedItem.getTransforms();
}
}

View File

@ -41,7 +41,7 @@ public class OptionList extends ContainerObjectSelectionList<OptionList.Entry> {
String friendlyKey = "modernfix.option.name." + option.getName();
MutableComponent baseComponent = Component.literal(option.getSelfName());
if(I18n.exists(friendlyKey))
return Component.translatable(friendlyKey).withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, baseComponent)));
return Component.translatable(friendlyKey).withStyle(style -> style.withHoverEvent(new HoverEvent.ShowText(baseComponent)));
else
return baseComponent;
}
@ -78,7 +78,7 @@ public class OptionList extends ContainerObjectSelectionList<OptionList.Entry> {
for(String category : theCategories) {
String categoryTranslationKey = "modernfix.option.category." + category;
this.addEntry(new CategoryEntry(Component.translatable(categoryTranslationKey)
.withStyle(Style.EMPTY.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable(categoryTranslationKey + ".description"))))
.withStyle(Style.EMPTY.withHoverEvent(new HoverEvent.ShowText(Component.translatable(categoryTranslationKey + ".description"))))
));
optionsByCategory.get(category).stream().filter(key -> {
int dotCount = 0;

View File

@ -1,83 +0,0 @@
package org.embeddedt.modernfix.util;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.registries.BuiltInRegistries;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
public class ItemMesherMap<K> implements Map<K, ModelResourceLocation> {
private final Function<K, ModelResourceLocation> getLocation;
public ItemMesherMap(Function<K, ModelResourceLocation> getLocation) {
this.getLocation = getLocation;
}
@Override
public int size() {
return BuiltInRegistries.ITEM.keySet().size();
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean containsKey(Object key) {
return true;
}
@Override
public boolean containsValue(Object value) {
return false;
}
@Override
public ModelResourceLocation get(Object key) {
return getLocation.apply((K)key);
}
@Nullable
@Override
public ModelResourceLocation put(K key, ModelResourceLocation value) {
throw new UnsupportedOperationException();
}
@Override
public ModelResourceLocation remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(@NotNull Map<? extends K, ? extends ModelResourceLocation> m) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<K> keySet() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Collection<ModelResourceLocation> values() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<Map.Entry<K, ModelResourceLocation>> entrySet() {
throw new UnsupportedOperationException();
}
}

View File

@ -1,5 +1,7 @@
package org.embeddedt.modernfix.world;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
@ -9,6 +11,7 @@ import net.minecraft.util.datafix.DataFixTypes;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.saveddata.SavedData;
import net.minecraft.world.level.saveddata.SavedDataType;
import java.util.ArrayList;
import java.util.List;
@ -20,11 +23,22 @@ public class StrongholdLocationCache extends SavedData {
chunkPosList = new ArrayList<>();
}
public static SavedData.Factory<StrongholdLocationCache> factory(ServerLevel serverLevel) {
// FIXME datafixer will probably throw on update
return new SavedData.Factory<>(StrongholdLocationCache::new, StrongholdLocationCache::load, DataFixTypes.SAVED_DATA_FORCED_CHUNKS);
private StrongholdLocationCache(List<ChunkPos> list) {
this.chunkPosList = new ArrayList<>(list);
}
public static final Codec<StrongholdLocationCache> CODEC = RecordCodecBuilder.create(instance ->
instance.group(ChunkPos.CODEC.listOf().optionalFieldOf("stronghold_positions", List.of()).forGetter(StrongholdLocationCache::getChunkPosList))
.apply(instance, StrongholdLocationCache::new)
);
public static final SavedDataType<StrongholdLocationCache> TYPE = new SavedDataType<>(
"modernfix_stronghold_cache",
StrongholdLocationCache::new,
CODEC,
DataFixTypes.SAVED_DATA_FORCED_CHUNKS
);
public List<ChunkPos> getChunkPosList() {
return new ArrayList<>(chunkPosList);
}
@ -33,30 +47,4 @@ public class StrongholdLocationCache extends SavedData {
this.chunkPosList = new ArrayList<>(positions);
this.setDirty();
}
public static StrongholdLocationCache load(CompoundTag arg, HolderLookup.Provider provider) {
StrongholdLocationCache cache = new StrongholdLocationCache();
if(arg.contains("Positions", Tag.TAG_LONG_ARRAY)) {
long[] positions = arg.getLongArray("Positions");
for(long position : positions) {
cache.chunkPosList.add(new ChunkPos(position));
}
}
return cache;
}
@Override
public CompoundTag save(CompoundTag compoundTag, HolderLookup.Provider provider) {
long[] serialized = new long[chunkPosList.size()];
for(int i = 0; i < chunkPosList.size(); i++) {
ChunkPos thePos = chunkPosList.get(i);
serialized[i] = thePos.toLong();
}
compoundTag.putLongArray("Positions", serialized);
return compoundTag;
}
public static String getFileId(Holder<DimensionType> dimensionType) {
return "mfix_strongholds";
}
}

View File

@ -43,7 +43,7 @@ accessible method net/minecraft/client/resources/model/ModelBakery$BakedCacheKey
accessible class net/minecraft/client/resources/model/ModelBakery$ModelBakerImpl
accessible class net/minecraft/client/resources/model/ModelManager$ReloadState
accessible method net/minecraft/client/resources/model/BlockStateModelLoader definitionLocationToBlockMapper ()Ljava/util/function/Function;
accessible method net/minecraft/client/resources/model/BlockStateModelLoader loadBlockStateDefinitionStack (Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/world/level/block/state/StateDefinition;Ljava/util/List;Lnet/minecraft/client/resources/model/UnbakedModel;)Lnet/minecraft/client/resources/model/BlockStateModelLoader$LoadedModels;
accessible method net/minecraft/client/resources/model/BlockStateModelLoader loadBlockStateDefinitionStack (Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/world/level/block/state/StateDefinition;Ljava/util/List;)Lnet/minecraft/client/resources/model/BlockStateModelLoader$LoadedModels;
accessible class net/minecraft/client/resources/model/BlockStateModelLoader$LoadedBlockModelDefinition
accessible method net/minecraft/client/resources/model/BlockStateModelLoader$LoadedBlockModelDefinition <init> (Ljava/lang/String;Lnet/minecraft/client/renderer/block/model/BlockModelDefinition;)V
accessible class net/minecraft/world/level/chunk/PalettedContainer$Data
@ -53,3 +53,8 @@ accessible method net/minecraft/client/gui/screens/Screen addRenderableWidget (L
accessible field net/minecraft/client/KeyMapping ALL Ljava/util/Map;
accessible method net/minecraft/world/level/block/state/StateDefinition appendPropertyCodec (Lcom/mojang/serialization/MapCodec;Ljava/util/function/Supplier;Ljava/lang/String;Lnet/minecraft/world/level/block/state/properties/Property;)Lcom/mojang/serialization/MapCodec;
accessible class net/minecraft/client/multiplayer/SessionSearchTrees$Key
accessible class net/minecraft/client/resources/model/ModelDiscovery$ModelWrapper
accessible method net/minecraft/client/resources/model/ModelDiscovery$ModelWrapper <init> (Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/UnbakedModel;Z)V
accessible field net/minecraft/client/resources/model/ModelDiscovery$ModelWrapper parent Lnet/minecraft/client/resources/model/ModelDiscovery$ModelWrapper;
accessible method net/minecraft/client/resources/model/BlockStateDefinitions definitionLocationToBlockStateMapper ()Ljava/util/function/Function;

View File

@ -33,7 +33,11 @@ dependencies {
modCompileOnly(fabricApi.module("fabric-api-base", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modCompileOnly(fabricApi.module("fabric-screen-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modCompileOnly(fabricApi.module("fabric-command-api-v2", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modCompileOnly(fabricApi.module("fabric-model-loading-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
try {
modCompileOnly(fabricApi.module("fabric-model-loading-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
} catch(ignored) {
// already reported in common
}
modCompileOnly(fabricApi.module("fabric-resource-loader-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modCompileOnly(fabricApi.module("fabric-data-generation-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modCompileOnly("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { transitive false }

View File

@ -1,32 +0,0 @@
package org.embeddedt.modernfix.fabric.mixin.safety;
import com.mojang.blaze3d.platform.NativeImage;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(DynamicTexture.class)
@ClientOnlyMixin
public class DynamicTextureMixin {
@Shadow @Nullable private NativeImage pixels;
private Exception closeTrace;
@Inject(method = "method_22793", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/texture/DynamicTexture;pixels:Lcom/mojang/blaze3d/platform/NativeImage;", ordinal = 0))
private void checkNullPixels(CallbackInfo ci) {
if(pixels == null) {
ModernFix.LOGGER.error("Attempted to upload null texture! This is not allowed, closed here", closeTrace);
}
}
@Inject(method = "close", at = @At("HEAD"))
private void storeCloseTrace(CallbackInfo ci) {
closeTrace = new Exception();
}
}

View File

@ -5,8 +5,8 @@ junit_version=5.10.0-M1
mixinextras_version=0.4.1
mod_id=modernfix
minecraft_version=1.21.4
enabled_platforms=fabric,neoforge
minecraft_version=25w07a
enabled_platforms=fabric
forge_version=21.4.47-beta
parchment_version=2024.12.22
parchment_mc_version=1.21.4
@ -17,10 +17,10 @@ ctm_version=1.21-1.2.0+2
ldlib_version=5782845
kubejs_version=1902.6.0-build.142
rhino_version=1902.2.2-build.268
supported_minecraft_versions=1.21.4
supported_minecraft_versions=25w07a
fabric_loader_version=0.16.10
fabric_api_version=0.113.0+1.21.4
fabric_api_version=0.116.1+1.21.5
continuity_version=3.0.0-beta.4+1.20.2