aboutsummaryrefslogtreecommitdiff
path: root/defaults/src/main/java
diff options
context:
space:
mode:
authorSHsuperCM <shsupercm@gmail.com>2022-02-15 12:21:57 +0200
committerSHsuperCM <shsupercm@gmail.com>2022-02-15 12:28:36 +0200
commit972e6c330d03f205ab738ed28c4e47496f30e92d (patch)
treec90360cb169cdb53b608ede798020ae2ac20d85e /defaults/src/main/java
parent32caf009812501e9e9a8f9d835726f12723c4a17 (diff)
downloadCITResewn-972e6c330d03f205ab738ed28c4e47496f30e92d.tar.gz
CITResewn-972e6c330d03f205ab738ed28c4e47496f30e92d.tar.bz2
CITResewn-972e6c330d03f205ab738ed28c4e47496f30e92d.zip
Ported item type (missing asset resolution)
Diffstat (limited to 'defaults/src/main/java')
-rw-r--r--defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/cit/types/TypeItem.java440
-rw-r--r--defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/common/ResewnItemModelIdentifier.java16
-rw-r--r--defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/common/ResewnTextureIdentifier.java12
-rw-r--r--defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/common/SpriteAtlasTextureMixin.java18
-rw-r--r--defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/ItemRendererMixin.java83
-rw-r--r--defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/ItemStackMixin.java12
-rw-r--r--defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/JsonUnbakedModelAccessor.java22
-rw-r--r--defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/ModelLoaderMixin.java165
8 files changed, 766 insertions, 2 deletions
diff --git a/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/cit/types/TypeItem.java b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/cit/types/TypeItem.java
index 730c713..14b8813 100644
--- a/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/cit/types/TypeItem.java
+++ b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/cit/types/TypeItem.java
@@ -1,22 +1,449 @@
package shcm.shsupercm.fabric.citresewn.defaults.cit.types;
+import com.google.common.collect.ImmutableMap;
+import com.mojang.datafixers.util.Either;
import io.shcm.shsupercm.fabric.fletchingtable.api.Entrypoint;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import net.minecraft.client.render.model.BakedModel;
+import net.minecraft.client.render.model.json.JsonUnbakedModel;
+import net.minecraft.client.render.model.json.ModelOverride;
+import net.minecraft.client.render.model.json.ModelOverrideList;
+import net.minecraft.client.render.model.json.ModelTransformation;
+import net.minecraft.client.texture.SpriteAtlasTexture;
+import net.minecraft.client.util.SpriteIdentifier;
+import net.minecraft.client.world.ClientWorld;
import net.minecraft.item.Item;
+import net.minecraft.item.Items;
+import net.minecraft.resource.Resource;
+import net.minecraft.resource.ResourceManager;
+import net.minecraft.resource.ResourceType;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+import org.apache.commons.io.IOUtils;
+import shcm.shsupercm.fabric.citresewn.CITResewn;
import shcm.shsupercm.fabric.citresewn.api.CITTypeContainer;
import shcm.shsupercm.fabric.citresewn.cit.*;
import shcm.shsupercm.fabric.citresewn.defaults.cit.conditions.ConditionItems;
+import shcm.shsupercm.fabric.citresewn.defaults.common.ResewnItemModelIdentifier;
+import shcm.shsupercm.fabric.citresewn.defaults.common.ResewnTextureIdentifier;
+import shcm.shsupercm.fabric.citresewn.defaults.mixin.types.item.JsonUnbakedModelAccessor;
import shcm.shsupercm.fabric.citresewn.ex.CITParsingException;
import shcm.shsupercm.fabric.citresewn.pack.format.PropertyGroup;
+import shcm.shsupercm.fabric.citresewn.pack.format.PropertyValue;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
import java.util.*;
+import java.util.stream.Collectors;
public class TypeItem extends CITType {
@Entrypoint(CITTypeContainer.ENTRYPOINT)
public static final Container CONTAINER = new Container();
+ private static final String GENERATED_SUB_CITS_PREFIX = "sub_cititem_generated_";
+ public static final Set<Identifier> GENERATED_SUB_CITS_SEEN = new HashSet<>();
+
+ private final List<Item> items = new ArrayList<>();
+
+ public Map<Identifier, Identifier> assetIdentifiers = new LinkedHashMap<>();
+ public Map<List<ModelOverride.Condition>, JsonUnbakedModel> unbakedAssets = new LinkedHashMap<>();
+ private Map<String, Either<SpriteIdentifier, String>> textureOverrideMap = new HashMap<>();
+ private boolean isTexture = false;
+
+ public BakedModel bakedModel = null;
+ public CITOverrideList bakedSubModels = new CITOverrideList();
+
@Override
public void load(List<? extends CITCondition> conditions, PropertyGroup properties) throws CITParsingException {
+ for (CITCondition condition : conditions)
+ if (condition instanceof ConditionItems conditionItems)
+ items.addAll(Arrays.asList(conditionItems.items));
+
+ if (this.items.size() == 0)
+ throw new CITParsingException("Not targeting any item type", properties, -1);
+
+ Identifier assetIdentifier;
+ PropertyValue modelProp = properties.getLastWithoutMetadata("citresewn", "model");
+ boolean containsTexture = modelProp == null && !properties.get("citresewn", "texture", "tile").isEmpty();
+
+ if (!containsTexture) {
+ assetIdentifier = resolvePath(identifier, modelProp, ".json", id -> pack.resourcePack.contains(ResourceType.CLIENT_RESOURCES, id));
+ if (assetIdentifier != null)
+ assetIdentifiers.put(null, assetIdentifier);
+ else if (modelProp != null) {
+ assetIdentifier = resolvePath(identifier, modelProp, ".json", id -> pack.resourcePack.contains(ResourceType.CLIENT_RESOURCES, id));
+ if (assetIdentifier != null)
+ assetIdentifiers.put(null, assetIdentifier);
+ }
+ }
+
+ for (PropertyValue property : properties.get("citresewn", "model")) {
+ Identifier subIdentifier = resolvePath(identifier, properties.getProperty(property), ".json", id -> pack.resourcePack.contains(ResourceType.CLIENT_RESOURCES, id));
+ if (subIdentifier == null)
+ throw new CITParsingException("Cannot resolve path", properties, property.position());
+
+ String subItem = property.keyMetadata();
+ Identifier subItemIdentifier = fixDeprecatedSubItem(subItem, properties, property.position());
+ assetIdentifiers.put(subItemIdentifier == null ? new Identifier("minecraft", "item/" + subItem) : subItemIdentifier, subIdentifier);
+ }
+ if (assetIdentifiers.size() == 0) { // attempt to load texture
+ isTexture = true;
+ PropertyValue textureProp = properties.getLastWithoutMetadata("citresewn", "texture", "tile");
+ assetIdentifier = resolvePath(identifier, textureProp, ".png", id -> pack.resourcePack.contains(ResourceType.CLIENT_RESOURCES, id));
+ if (assetIdentifier != null)
+ assetIdentifiers.put(null, assetIdentifier);
+
+ for (PropertyValue property : properties.get("citresewn", "texture", "tile")) {
+ Identifier subIdentifier = resolvePath(identifier, properties.getProperty(property), ".png", id -> pack.resourcePack.contains(ResourceType.CLIENT_RESOURCES, id));
+ if (subIdentifier == null)
+ throw new CITParsingException("Cannot resolve path", properties, property.position());
+
+ String subItem = property.keyMetadata();
+ Identifier subItemIdentifier = fixDeprecatedSubItem(subItem, properties, property.position());
+ assetIdentifiers.put(subItemIdentifier == null ? new Identifier("minecraft", "item/" + subItem) : subItemIdentifier, subIdentifier);
+ }
+ } else { // attempt to load textureOverrideMap from textures
+ PropertyValue textureProp = properties.getLastWithoutMetadata("citresewn", "texture", "tile");
+ if (textureProp != null) {
+ assetIdentifier = resolvePath(identifier, textureProp, ".png", id -> pack.resourcePack.contains(ResourceType.CLIENT_RESOURCES, id));
+ if (assetIdentifier != null)
+ textureOverrideMap.put(null, Either.left(new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new ResewnTextureIdentifier(assetIdentifier))));
+ else
+ throw new CITParsingException("Cannot resolve path", properties, textureProp.position());
+ }
+
+ for (PropertyValue property : properties.get("citresewn", "texture", "tile")) {
+ textureProp = property;
+ Identifier subIdentifier = resolvePath(identifier, textureProp, ".png", id -> pack.resourcePack.contains(ResourceType.CLIENT_RESOURCES, id));
+ if (subIdentifier == null)
+ throw new CITParsingException("Cannot resolve path", properties, property.position());
+
+ textureOverrideMap.put(property.keyMetadata(), Either.left(new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new ResewnTextureIdentifier(subIdentifier))));
+ }
+ }
+
+ if (assetIdentifiers.size() == 0)
+ throw new CITParsingException("Could not resolve a replacement model/texture", properties, -1);
+ }
+
+ public void loadUnbakedAssets(ResourceManager resourceManager) throws Exception {
+ try {
+ if (isTexture) {
+ JsonUnbakedModel itemJson = getModelForFirstItemType(resourceManager);
+ if (((JsonUnbakedModelAccessor) itemJson).getTextureMap().size() > 1) { // use(some/all of) the asset identifiers to build texture override in layered models
+ textureOverrideMap = ((JsonUnbakedModelAccessor) itemJson).getTextureMap();
+ Identifier defaultAsset = assetIdentifiers.get(null);
+ textureOverrideMap.replaceAll((layerName, originalTextureEither) -> {
+ Identifier textureIdentifier = assetIdentifiers.remove(originalTextureEither.map(SpriteIdentifier::getTextureId, Identifier::new));
+ if (textureIdentifier != null)
+ return Either.left(new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new ResewnTextureIdentifier(textureIdentifier)));
+ if (defaultAsset != null)
+ return Either.left(new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new ResewnTextureIdentifier(defaultAsset)));
+ return null;
+ });
+
+ if (assetIdentifiers.size() == 0 || (assetIdentifiers.size() == 1 && assetIdentifiers.containsKey(null))) {
+ unbakedAssets.put(null, itemJson);
+ return;
+ }
+ }
+
+ Identifier baseIdentifier = assetIdentifiers.remove(null);
+
+ if (baseIdentifier != null)
+ unbakedAssets.put(null, loadUnbakedAsset(resourceManager, baseIdentifier));
+
+ if (!assetIdentifiers.isEmpty()) { // contains sub models
+ LinkedHashMap<Identifier, List<ModelOverride.Condition>> overrideConditions = new LinkedHashMap<>();
+ for (Item item : this.items) {
+ Identifier itemIdentifier = Registry.ITEM.getId(item);
+ overrideConditions.put(new Identifier(itemIdentifier.getNamespace(), "item/" + itemIdentifier.getPath()), Collections.emptyList());
+
+ Identifier itemModelIdentifier = new Identifier(itemIdentifier.getNamespace(), "models/item/" + itemIdentifier.getPath() + ".json");
+ try (Resource itemModelResource = resourceManager.getResource(itemModelIdentifier); Reader resourceReader = new InputStreamReader(itemModelResource.getInputStream())) {
+ JsonUnbakedModel itemModelJson = JsonUnbakedModel.deserialize(resourceReader);
+
+ if (itemModelJson.getOverrides() != null && !itemModelJson.getOverrides().isEmpty())
+ for (ModelOverride override : itemModelJson.getOverrides())
+ overrideConditions.put(override.getModelId(), override.streamConditions().toList());
+ }
+ }
+
+ ArrayList<Identifier> overrideModels = new ArrayList<>(overrideConditions.keySet());
+ Collections.reverse(overrideModels);
+
+ for (Identifier overrideModel : overrideModels) {
+ Identifier replacement = assetIdentifiers.remove(overrideModel);
+ if (replacement == null)
+ continue;
+
+ List<ModelOverride.Condition> conditions = overrideConditions.get(overrideModel);
+ unbakedAssets.put(conditions, loadUnbakedAsset(resourceManager, replacement));
+ }
+ }
+ } else { // isModel
+ Identifier baseIdentifier = assetIdentifiers.remove(null);
+
+ if (baseIdentifier != null) {
+ if (!GENERATED_SUB_CITS_SEEN.add(baseIdentifier)) // cit generated duplicate
+ baseIdentifier = new Identifier(baseIdentifier.getNamespace(), GENERATED_SUB_CITS_PREFIX + GENERATED_SUB_CITS_SEEN.size() + "_" + baseIdentifier.getPath());
+ GENERATED_SUB_CITS_SEEN.add(baseIdentifier);
+
+ JsonUnbakedModel model = loadUnbakedAsset(resourceManager, baseIdentifier);
+ unbakedAssets.put(null, model);
+
+ if (model.getOverrides().size() > 0 && textureOverrideMap.size() > 0) {
+ LinkedHashMap<Identifier, List<ModelOverride.Condition>> overrideConditions = new LinkedHashMap<>();
+
+ for (ModelOverride override : model.getOverrides())
+ overrideConditions.put(override.getModelId(), override.streamConditions().toList());
+
+ ArrayList<Identifier> overrideModels = new ArrayList<>(overrideConditions.keySet());
+ Collections.reverse(overrideModels);
+
+ for (Identifier overrideModel : overrideModels) {
+ Identifier replacement = resolvePath(baseIdentifier, overrideModel.toString(), ".json", resourceManager::containsResource);
+ if (replacement != null) {
+ String subTexturePath = replacement.toString().substring(0, replacement.toString().lastIndexOf('.'));
+ final String subTextureName = subTexturePath.substring(subTexturePath.lastIndexOf('/') + 1);
+
+ replacement = baseIdentifier;
+ if (!GENERATED_SUB_CITS_SEEN.add(replacement)) // cit generated duplicate
+ replacement = new Identifier(replacement.getNamespace(), GENERATED_SUB_CITS_PREFIX + GENERATED_SUB_CITS_SEEN.size() + "_" + replacement.getPath());
+ GENERATED_SUB_CITS_SEEN.add(replacement);
+
+ JsonUnbakedModel jsonModel = loadUnbakedAsset(resourceManager, replacement);
+ jsonModel.getOverrides().clear();
+
+ ((JsonUnbakedModelAccessor) jsonModel).getTextureMap().replaceAll((layerName, texture) -> {
+ if (layerName != null)
+ try {
+ for (String subTexture : textureOverrideMap.keySet())
+ if (subTextureName.equals(subTexture))
+ return textureOverrideMap.get(subTexture);
+ } catch (Exception ignored) { }
+ return texture;
+ });
+
+ unbakedAssets.put(overrideConditions.get(overrideModel), jsonModel);
+ }
+ }
+ }
+ }
+
+ if (!assetIdentifiers.isEmpty()) { // contains sub models
+ LinkedHashMap<Identifier, List<ModelOverride.Condition>> overrideConditions = new LinkedHashMap<>();
+ for (Item item : this.items) {
+ Identifier itemIdentifier = Registry.ITEM.getId(item);
+ overrideConditions.put(new Identifier(itemIdentifier.getNamespace(), "item/" + itemIdentifier.getPath()), Collections.emptyList());
+
+ Identifier itemModelIdentifier = new Identifier(itemIdentifier.getNamespace(), "models/item/" + itemIdentifier.getPath() + ".json");
+ try (Resource itemModelResource = resourceManager.getResource(itemModelIdentifier); Reader resourceReader = new InputStreamReader(itemModelResource.getInputStream())) {
+ JsonUnbakedModel itemModelJson = JsonUnbakedModel.deserialize(resourceReader);
+
+ if (itemModelJson.getOverrides() != null && !itemModelJson.getOverrides().isEmpty())
+ for (ModelOverride override : itemModelJson.getOverrides())
+ overrideConditions.put(override.getModelId(), override.streamConditions().toList());
+ }
+ }
+
+ ArrayList<Identifier> overrideModels = new ArrayList<>(overrideConditions.keySet());
+ Collections.reverse(overrideModels);
+
+ for (Identifier overrideModel : overrideModels) {
+ Identifier replacement = assetIdentifiers.remove(overrideModel);
+ if (replacement == null)
+ continue;
+
+ if (!GENERATED_SUB_CITS_SEEN.add(replacement)) // cit generated duplicate
+ replacement = new Identifier(replacement.getNamespace(), GENERATED_SUB_CITS_PREFIX + GENERATED_SUB_CITS_SEEN.size() + "_" + replacement.getPath());
+ GENERATED_SUB_CITS_SEEN.add(replacement);
+
+ List<ModelOverride.Condition> conditions = overrideConditions.get(overrideModel);
+ unbakedAssets.put(conditions, loadUnbakedAsset(resourceManager, replacement));
+ }
+ }
+ }
+ } finally {
+ assetIdentifiers = null;
+ textureOverrideMap = null;
+ }
+ }
+
+ private JsonUnbakedModel loadUnbakedAsset(ResourceManager resourceManager, Identifier assetIdentifier) throws Exception {
+ final Identifier identifier;
+ {
+ Identifier possibleIdentifier = assetIdentifier;
+ while (possibleIdentifier.getPath().startsWith(GENERATED_SUB_CITS_PREFIX))
+ possibleIdentifier = new Identifier(possibleIdentifier.getNamespace(), possibleIdentifier.getPath().substring(possibleIdentifier.getPath().substring(GENERATED_SUB_CITS_PREFIX.length()).indexOf('_') + GENERATED_SUB_CITS_PREFIX.length() + 1));
+ identifier = possibleIdentifier;
+ }
+ JsonUnbakedModel json;
+ if (identifier.getPath().endsWith(".json")) {
+ InputStream is = null;
+ Resource resource = null;
+ try {
+ json = JsonUnbakedModel.deserialize(IOUtils.toString(is = (resource = resourceManager.getResource(identifier)).getInputStream(), StandardCharsets.UTF_8));
+ json.id = assetIdentifier.toString();
+ json.id = json.id.substring(0, json.id.length() - 5);
+
+ ((JsonUnbakedModelAccessor) json).getTextureMap().replaceAll((layer, original) -> {
+ Optional<SpriteIdentifier> left = original.left();
+ if (left.isPresent()) {
+ Identifier resolvedIdentifier = resolvePath(identifier, left.get().getTextureId().getPath(), ".png", resourceManager::containsResource);
+ if (resolvedIdentifier != null)
+ return Either.left(new SpriteIdentifier(left.get().getAtlasId(), new ResewnTextureIdentifier(resolvedIdentifier)));
+ }
+ return original;
+ });
+
+ if (textureOverrideMap.size() > 0) {
+ Map<String, Either<SpriteIdentifier, String>> jsonTextureMap = ((JsonUnbakedModelAccessor) json).getTextureMap();
+ if (jsonTextureMap.size() == 0)
+ jsonTextureMap.put("layer0", null);
+
+ final Either<SpriteIdentifier, String> defaultTextureOverride = textureOverrideMap.get(null);
+ if (defaultTextureOverride != null)
+ jsonTextureMap.replaceAll((layerName, spriteIdentifierStringEither) -> defaultTextureOverride);
+
+ //jsonTextureMap.putAll(textureOverrideMap);
+ jsonTextureMap.replaceAll((layerName, texture) -> {
+ if (layerName != null)
+ try {
+ String[] split = texture.map(id -> id.getTextureId().getPath(), s -> s).split("/");
+ String textureName = split[split.length - 1];
+ if (textureName.endsWith(".png"))
+ textureName = textureName.substring(0, textureName.length() - 4);
+ return Objects.requireNonNull(textureOverrideMap.get(textureName));
+ } catch (Exception ignored) { }
+ return texture;
+ });
+ jsonTextureMap.values().removeIf(Objects::isNull);
+ }
+
+ Identifier parentId = ((JsonUnbakedModelAccessor) json).getParentId();
+ if (parentId != null) {
+ String[] parentIdPathSplit = parentId.getPath().split("/");
+ if (parentId.getPath().startsWith("./") || (parentIdPathSplit.length > 2 && parentIdPathSplit[1].equals("cit"))) {
+ parentId = resolvePath(identifier, parentId.getPath(), ".json", id -> pack.resourcePack.contains(ResourceType.CLIENT_RESOURCES, id));
+ if (parentId != null)
+ ((JsonUnbakedModelAccessor) json).setParentId(new ResewnItemModelIdentifier(parentId));
+ }
+ }
+
+ json.getOverrides().replaceAll(override -> {
+ String[] modelIdPathSplit = override.getModelId().getPath().split("/");
+ if (override.getModelId().getPath().startsWith("./") || (modelIdPathSplit.length > 2 && modelIdPathSplit[1].equals("cit"))) {
+ Identifier resolvedOverridePath = resolvePath(identifier, override.getModelId().getPath(), ".json", id -> pack.resourcePack.contains(ResourceType.CLIENT_RESOURCES, id));
+ if (resolvedOverridePath != null)
+ return new ModelOverride(new ResewnItemModelIdentifier(resolvedOverridePath), override.streamConditions().collect(Collectors.toList()));
+ }
+
+ return override;
+ });
+
+ return json;
+ } finally {
+ IOUtils.closeQuietly(is, resource);
+ }
+ } else if (identifier.getPath().endsWith(".png")) {
+ json = getModelForFirstItemType(resourceManager);
+ if (json == null)
+ json = new JsonUnbakedModel(new Identifier("minecraft", "item/generated"), new ArrayList<>(), ImmutableMap.of("layer0", Either.left(new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new ResewnTextureIdentifier(identifier)))), true, JsonUnbakedModel.GuiLight.ITEM, ModelTransformation.NONE, new ArrayList<>());
+ json.getOverrides().clear();
+ json.id = identifier.toString();
+ json.id = json.id.substring(0, json.id.length() - 4);
+
+ ((JsonUnbakedModelAccessor) json).getTextureMap().replaceAll((layerName, originalTextureEither) -> {
+ if (textureOverrideMap.size() > 0) {
+ Either<SpriteIdentifier, String> textureOverride = textureOverrideMap.get(layerName);
+ if (textureOverride == null)
+ textureOverride = textureOverrideMap.get(null);
+ return textureOverride == null ? originalTextureEither : textureOverride;
+ } else
+ return Either.left(new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new ResewnTextureIdentifier(identifier)));
+ });
+ return json;
+ }
+
+ throw new Exception("Unknown asset type");
+ }
+
+ public Identifier fixDeprecatedSubItem(String subItem, PropertyGroup properties, int position) {
+ String replacement = switch (subItem) {
+ case "bow_pulling_standby" -> "bow";
+ case "crossbow_standby" -> "crossbow";
+ case "potion_bottle_drinkable" -> "potion";
+ case "potion_bottle_splash" -> "splash_potion";
+ case "potion_bottle_lingering" -> "lingering_potion";
+
+
+ default -> null;
+ };
+
+ if (replacement != null) {
+ CITResewn.logWarnLoading(CITParsingException.descriptionOf("Warning: Using deprecated sub item id \"" + subItem + "\" instead of \"" + replacement + "\"", properties, position));
+
+ return new Identifier("minecraft", "item/" + replacement);
+ }
+
+ return null;
+ }
+
+ private JsonUnbakedModel getModelForFirstItemType(ResourceManager resourceManager) {
+ Identifier firstItemIdentifier = Registry.ITEM.getId(this.items.iterator().next()), firstItemModelIdentifier = new Identifier(firstItemIdentifier.getNamespace(), "models/item/" + firstItemIdentifier.getPath() + ".json");
+ Resource itemModelResource = null;
+ try {
+ JsonUnbakedModel json = JsonUnbakedModel.deserialize(IOUtils.toString((itemModelResource = resourceManager.getResource(firstItemModelIdentifier)).getInputStream(), StandardCharsets.UTF_8));
+
+ if (!GENERATED_SUB_CITS_SEEN.add(firstItemModelIdentifier)) // cit generated duplicate
+ firstItemModelIdentifier = new Identifier(firstItemModelIdentifier.getNamespace(), GENERATED_SUB_CITS_PREFIX + GENERATED_SUB_CITS_SEEN.size() + "_" + firstItemModelIdentifier.getPath());
+ GENERATED_SUB_CITS_SEEN.add(firstItemModelIdentifier);
+
+ json.id = firstItemModelIdentifier.toString();
+ json.id = json.id.substring(0, json.id.length() - 5);
+ return json;
+ } catch (Exception e) {
+ return null;
+ } finally {
+ IOUtils.closeQuietly(itemModelResource);
+ }
+ }
+
+ public BakedModel getItemModel(CITContext context, int seed) {
+ // get sub items or bakedModel if no sub item matches @Nullable
+ BakedModel bakedModel = bakedSubModels.apply(this.bakedModel, context.stack, (ClientWorld) context.world, context.entity, seed);
+
+ // apply model overrides
+ if (bakedModel != null && bakedModel.getOverrides() != null)
+ bakedModel = bakedModel.getOverrides().apply(bakedModel, context.stack, (ClientWorld) context.world, context.entity, seed);
+
+ return bakedModel;
+ }
+
+ public static class CITOverrideList extends ModelOverrideList {
+ public void override(List<ModelOverride.Condition> key, BakedModel bakedModel) {
+ Set<Identifier> conditionTypes = new LinkedHashSet<>(Arrays.asList(this.conditionTypes));
+ for (ModelOverride.Condition condition : key)
+ conditionTypes.add(condition.getType());
+ this.conditionTypes = conditionTypes.toArray(new Identifier[0]);
+
+ this.overrides = Arrays.copyOf(this.overrides, this.overrides.length + 1);
+
+ Object2IntMap<Identifier> object2IntMap = new Object2IntOpenHashMap<>();
+ for(int i = 0; i < this.conditionTypes.length; ++i)
+ object2IntMap.put(this.conditionTypes[i], i);
+
+ this.overrides[this.overrides.length - 1] = new BakedOverride(
+ key.stream()
+ .map((condition) -> new InlinedCondition(object2IntMap.getInt(condition.getType()), condition.getThreshold()))
+ .toArray(InlinedCondition[]::new)
+ , bakedModel);
+ }
}
public static class Container extends CITTypeContainer<TypeItem> {
@@ -41,16 +468,22 @@ public class TypeItem extends CITType {
loaded.clear();
}
- public CIT<TypeItem> getCIT(CITContext context) {
+ public CIT<TypeItem> getCIT(CITContext context, int seed) {
return ((CITCacheItem) (Object) context.stack).citresewn$getCacheTypeItem().get(context).get();
}
public CIT<TypeItem> getRealTimeCIT(CITContext context) {
+ ((CITCacheItem) (Object) context.stack).citresewn$setMojankCITTypeItem(false);
+
Set<CIT<TypeItem>> loadedForItemType = loaded.get(context.stack.getItem());
if (loadedForItemType != null)
for (CIT<TypeItem> cit : loadedForItemType)
- if (cit.test(context))
+ if (cit.test(context)) {
+ if (context.stack.isOf(Items.TRIDENT) || context.stack.isOf(Items.SPYGLASS))
+ ((CITCacheItem) (Object) context.stack).citresewn$setMojankCITTypeItem(true);
+
return cit;
+ }
return null;
}
@@ -58,5 +491,8 @@ public class TypeItem extends CITType {
public interface CITCacheItem {
CITCache.Single<TypeItem> citresewn$getCacheTypeItem();
+
+ boolean citresewn$isMojankCITTypeItem();
+ void citresewn$setMojankCITTypeItem(boolean mojankCIT);
}
} \ No newline at end of file
diff --git a/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/common/ResewnItemModelIdentifier.java b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/common/ResewnItemModelIdentifier.java
new file mode 100644
index 0000000..c3f30f5
--- /dev/null
+++ b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/common/ResewnItemModelIdentifier.java
@@ -0,0 +1,16 @@
+package shcm.shsupercm.fabric.citresewn.defaults.common;
+
+import net.minecraft.util.Identifier;
+
+/**
+ * Marks models as cit item models.
+ */
+public class ResewnItemModelIdentifier extends Identifier {
+ public ResewnItemModelIdentifier(String id) {
+ super(id);
+ }
+
+ public ResewnItemModelIdentifier(Identifier identifier) {
+ super(identifier.getNamespace(), identifier.getPath());
+ }
+}
diff --git a/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/common/ResewnTextureIdentifier.java b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/common/ResewnTextureIdentifier.java
new file mode 100644
index 0000000..a6dd2ef
--- /dev/null
+++ b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/common/ResewnTextureIdentifier.java
@@ -0,0 +1,12 @@
+package shcm.shsupercm.fabric.citresewn.defaults.common;
+
+import net.minecraft.util.Identifier;
+
+/**
+ * Marks path identifiers as forced literal texture paths.
+ */
+public class ResewnTextureIdentifier extends Identifier {
+ public ResewnTextureIdentifier(Identifier identifier) {
+ super(identifier.getNamespace(), identifier.getPath());
+ }
+}
diff --git a/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/common/SpriteAtlasTextureMixin.java b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/common/SpriteAtlasTextureMixin.java
new file mode 100644
index 0000000..61eff5c
--- /dev/null
+++ b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/common/SpriteAtlasTextureMixin.java
@@ -0,0 +1,18 @@
+package shcm.shsupercm.fabric.citresewn.defaults.mixin.common;
+
+import net.minecraft.client.texture.SpriteAtlasTexture;
+import net.minecraft.util.Identifier;
+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 shcm.shsupercm.fabric.citresewn.defaults.common.ResewnTextureIdentifier;
+
+@Mixin(SpriteAtlasTexture.class)
+public class SpriteAtlasTextureMixin {
+ @Inject(method = "getTexturePath", cancellable = true, at = @At("HEAD"))
+ public void forceLiteralResewnTextureIdentifier(Identifier id, CallbackInfoReturnable<Identifier> cir) {
+ if (id instanceof ResewnTextureIdentifier)
+ cir.setReturnValue(id);
+ }
+}
diff --git a/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/ItemRendererMixin.java b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/ItemRendererMixin.java
new file mode 100644
index 0000000..9f6063b
--- /dev/null
+++ b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/ItemRendererMixin.java
@@ -0,0 +1,83 @@
+package shcm.shsupercm.fabric.citresewn.defaults.mixin.types.item;
+
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.item.ItemModels;
+import net.minecraft.client.render.item.ItemRenderer;
+import net.minecraft.client.render.model.BakedModel;
+import net.minecraft.client.render.model.json.ModelTransformation;
+import net.minecraft.client.util.ModelIdentifier;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.world.World;
+import org.spongepowered.asm.mixin.Final;
+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.ModifyVariable;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+import shcm.shsupercm.fabric.citresewn.cit.ActiveCITs;
+import shcm.shsupercm.fabric.citresewn.cit.CIT;
+import shcm.shsupercm.fabric.citresewn.cit.CITContext;
+import shcm.shsupercm.fabric.citresewn.config.CITResewnConfig;
+import shcm.shsupercm.fabric.citresewn.defaults.cit.types.TypeItem;
+
+import java.lang.ref.WeakReference;
+
+import static shcm.shsupercm.fabric.citresewn.defaults.cit.types.TypeItem.CONTAINER;
+
+@Mixin(ItemRenderer.class)
+public class ItemRendererMixin {
+ @Shadow @Final private ItemModels models;
+
+ private static WeakReference<BakedModel> mojankCITModel = null;
+
+ @Inject(method = "getModel", cancellable = true, at = @At("HEAD"))
+ private void citresewn$getItemModel(ItemStack stack, World world, LivingEntity entity, int seed, CallbackInfoReturnable<BakedModel> cir) {
+ if (!CITResewnConfig.INSTANCE.enabled || !ActiveCITs.isActive())
+ return;
+
+ CITContext context = new CITContext(stack, world, entity);
+ CIT<TypeItem> cit = CONTAINER.getCIT(context, seed);
+ if (cit != null) {
+ BakedModel citModel = cit.type.getItemModel(context, seed);
+
+ if (citModel != null)
+ cir.setReturnValue(citModel);
+ }
+ }
+
+ @Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformation$Mode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;)V", at = @At("HEAD"))
+ private void citresewn$fixMojankCITsContext(ItemStack stack, ModelTransformation.Mode renderMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, CallbackInfo ci) {
+ if (!CITResewnConfig.INSTANCE.enabled || !ActiveCITs.isActive())
+ return;
+
+ mojankCITModel = null;
+ if (((TypeItem.CITCacheItem) (Object) stack).citresewn$isMojankCITTypeItem()) {
+ boolean bl = renderMode == ModelTransformation.Mode.GUI || renderMode == ModelTransformation.Mode.GROUND || renderMode == ModelTransformation.Mode.FIXED;
+ if (bl)
+ mojankCITModel = new WeakReference<>(model);
+ else { // rendered in hand model of trident/spyglass
+ if (stack.isOf(Items.TRIDENT))
+ mojankCITModel = new WeakReference<>(this.models.getModelManager().getModel(new ModelIdentifier("minecraft:trident_in_hand#inventory")));
+ else if (stack.isOf(Items.SPYGLASS))
+ mojankCITModel = new WeakReference<>(this.models.getModelManager().getModel(new ModelIdentifier("minecraft:spyglass_in_hand#inventory")));
+ }
+ } else
+ mojankCITModel = null;
+ }
+
+ @ModifyVariable(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformation$Mode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;)V", at = @At(value = "LOAD", ordinal = 0, target = "Lnet/minecraft/client/render/model/BakedModel;getTransformation()Lnet/minecraft/client/render/model/json/ModelTransformation;"), argsOnly = true)
+ private BakedModel citresewn$fixMojankCITs(BakedModel original) {
+ if (!CITResewnConfig.INSTANCE.enabled || !ActiveCITs.isActive())
+ return original;
+
+ if (mojankCITModel != null)
+ return mojankCITModel.get();
+
+ return original;
+ }
+}
diff --git a/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/ItemStackMixin.java b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/ItemStackMixin.java
index 450d8a3..2fb5396 100644
--- a/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/ItemStackMixin.java
+++ b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/ItemStackMixin.java
@@ -9,8 +9,20 @@ import shcm.shsupercm.fabric.citresewn.defaults.cit.types.TypeItem;
public class ItemStackMixin implements TypeItem.CITCacheItem {
private final CITCache.Single<TypeItem> citresewn$cacheTypeItem = new CITCache.Single<>(TypeItem.CONTAINER::getRealTimeCIT);
+ private boolean citresewn$mojankCITTypeItem = false;
+
@Override
public CITCache.Single<TypeItem> citresewn$getCacheTypeItem() {
return this.citresewn$cacheTypeItem;
}
+
+ @Override
+ public boolean citresewn$isMojankCITTypeItem() {
+ return this.citresewn$mojankCITTypeItem;
+ }
+
+ @Override
+ public void citresewn$setMojankCITTypeItem(boolean mojankCITTypeItem) {
+ this.citresewn$mojankCITTypeItem = mojankCITTypeItem;
+ }
}
diff --git a/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/JsonUnbakedModelAccessor.java b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/JsonUnbakedModelAccessor.java
new file mode 100644
index 0000000..350d01a
--- /dev/null
+++ b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/JsonUnbakedModelAccessor.java
@@ -0,0 +1,22 @@
+package shcm.shsupercm.fabric.citresewn.defaults.mixin.types.item;
+
+import com.mojang.datafixers.util.Either;
+import net.minecraft.client.render.model.json.JsonUnbakedModel;
+import net.minecraft.client.util.SpriteIdentifier;
+import net.minecraft.util.Identifier;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+import java.util.Map;
+
+@Mixin(JsonUnbakedModel.class)
+public interface JsonUnbakedModelAccessor {
+ @Accessor
+ Map<String, Either<SpriteIdentifier, String>> getTextureMap();
+
+ @Accessor
+ Identifier getParentId();
+
+ @Accessor
+ void setParentId(Identifier parentId);
+}
diff --git a/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/ModelLoaderMixin.java b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/ModelLoaderMixin.java
new file mode 100644
index 0000000..22e9373
--- /dev/null
+++ b/defaults/src/main/java/shcm/shsupercm/fabric/citresewn/defaults/mixin/types/item/ModelLoaderMixin.java
@@ -0,0 +1,165 @@
+package shcm.shsupercm.fabric.citresewn.defaults.mixin.types.item;
+
+import com.mojang.datafixers.util.Either;
+import net.minecraft.client.render.model.BakedModel;
+import net.minecraft.client.render.model.ModelLoader;
+import net.minecraft.client.render.model.SpriteAtlasManager;
+import net.minecraft.client.render.model.UnbakedModel;
+import net.minecraft.client.render.model.json.JsonUnbakedModel;
+import net.minecraft.client.render.model.json.ModelOverride;
+import net.minecraft.client.texture.TextureManager;
+import net.minecraft.client.util.ModelIdentifier;
+import net.minecraft.client.util.SpriteIdentifier;
+import net.minecraft.resource.Resource;
+import net.minecraft.resource.ResourceManager;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.profiler.Profiler;
+import org.apache.commons.io.IOUtils;
+import org.spongepowered.asm.mixin.Final;
+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.ModifyArg;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+import shcm.shsupercm.fabric.citresewn.CITResewn;
+import shcm.shsupercm.fabric.citresewn.cit.ActiveCITs;
+import shcm.shsupercm.fabric.citresewn.cit.CITType;
+import shcm.shsupercm.fabric.citresewn.defaults.cit.types.TypeItem;
+import shcm.shsupercm.fabric.citresewn.defaults.common.ResewnItemModelIdentifier;
+import shcm.shsupercm.fabric.citresewn.defaults.common.ResewnTextureIdentifier;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static shcm.shsupercm.fabric.citresewn.CITResewn.info;
+import static shcm.shsupercm.fabric.citresewn.defaults.cit.types.TypeItem.CONTAINER;
+
+@Mixin(ModelLoader.class)
+public class ModelLoaderMixin {
+ @Shadow @Final private ResourceManager resourceManager;
+ @Shadow @Final private Set<Identifier> modelsToLoad;
+ @Shadow @Final private Map<Identifier, UnbakedModel> modelsToBake;
+ @Shadow @Final private Map<Identifier, UnbakedModel> unbakedModels;
+ @Shadow @Final private Map<Identifier, BakedModel> bakedModels;
+
+ @Inject(method = "addModel", at = @At("TAIL"))
+ public void citresewn$addTypeItemModels(ModelIdentifier eventModelId, CallbackInfo ci) { if (eventModelId != ModelLoader.MISSING_ID) return;
+ if (!ActiveCITs.isActive())
+ return;
+
+ info("Loading CITItem models...");
+ CONTAINER.loaded.values().stream() // todo remove streams usage
+ .flatMap(Collection::stream)
+ .distinct().forEach(cit -> {
+ try {
+ cit.type.loadUnbakedAssets(resourceManager);
+
+ for (JsonUnbakedModel unbakedModel : cit.type.unbakedAssets.values()) {
+ ResewnItemModelIdentifier id = new ResewnItemModelIdentifier(unbakedModel.id);
+ this.unbakedModels.put(id, unbakedModel);
+ this.modelsToLoad.addAll(unbakedModel.getModelDependencies());
+ this.modelsToBake.put(id, unbakedModel);
+ }
+ } catch (Exception e) {
+ CITResewn.logErrorLoading(e.getMessage());
+ }
+ });
+
+ TypeItem.GENERATED_SUB_CITS_SEEN.clear();
+ }
+
+ @Inject(method = "upload", at = @At("RETURN"))
+ public void citresewn$linkTypeItemModels(TextureManager textureManager, Profiler profiler, CallbackInfoReturnable<SpriteAtlasManager> cir) {
+ if (!ActiveCITs.isActive())
+ return;
+
+ profiler.push("citresewn:item_linking");
+ info("Linking baked models to CITItems...");
+
+ CONTAINER.loaded.values().stream() // todo remove streams usage
+ .flatMap(Collection::stream)
+ .distinct().forEach(cit -> {
+ for (Map.Entry<List<ModelOverride.Condition>, JsonUnbakedModel> citModelEntry : cit.type.unbakedAssets.entrySet()) {
+ if (citModelEntry.getKey() == null) {
+ cit.type.bakedModel = this.bakedModels.get(new ResewnItemModelIdentifier(citModelEntry.getValue().id));
+ } else {
+ BakedModel bakedModel = bakedModels.get(new ResewnItemModelIdentifier(citModelEntry.getValue().id));
+ if (bakedModel == null)
+ CITResewn.logWarnLoading("Skipping sub cit: Failed loading model for \"" + citModelEntry.getValue().id + "\" in " + cit.propertiesIdentifier + " from " + cit.packName);
+ else
+ cit.type.bakedSubModels.override(citModelEntry.getKey(), bakedModel);
+ }
+ }
+ cit.type.unbakedAssets = null;
+ });
+
+ profiler.pop();
+ }
+
+
+ @Inject(method = "loadModelFromJson", cancellable = true, at = @At("HEAD"))
+ public void citresewn$forceLiteralResewnModelIdentifier(Identifier id, CallbackInfoReturnable<JsonUnbakedModel> cir) {
+ if (id instanceof ResewnItemModelIdentifier) {
+ InputStream is = null;
+ Resource resource = null;
+ try {
+ JsonUnbakedModel json = JsonUnbakedModel.deserialize(IOUtils.toString(is = (resource = resourceManager.getResource(id)).getInputStream(), StandardCharsets.UTF_8));
+ json.id = id.toString();
+ json.id = json.id.substring(0, json.id.length() - 5);
+
+ ((JsonUnbakedModelAccessor) json).getTextureMap().replaceAll((layer, original) -> {
+ Optional<SpriteIdentifier> left = original.left();
+ if (left.isPresent()) {
+ String originalPath = left.get().getTextureId().getPath();
+ String[] split = originalPath.split("/");
+ if (originalPath.startsWith("./") || (split.length > 2 && split[1].equals("cit"))) {
+ Identifier resolvedIdentifier = CITType.resolvePath(id, originalPath, ".png", identifier -> resourceManager.containsResource(identifier));
+ if (resolvedIdentifier != null)
+ return Either.left(new SpriteIdentifier(left.get().getAtlasId(), new ResewnTextureIdentifier(resolvedIdentifier)));
+ }
+ }
+ return original;
+ });
+
+ Identifier parentId = ((JsonUnbakedModelAccessor) json).getParentId();
+ if (parentId != null) {
+ String[] parentIdPathSplit = parentId.getPath().split("/");
+ if (parentId.getPath().startsWith("./") || (parentIdPathSplit.length > 2 && parentIdPathSplit[1].equals("cit"))) {
+ parentId = CITType.resolvePath(id, parentId.getPath(), ".json", identifier -> resourceManager.containsResource(identifier));
+ if (parentId != null)
+ ((JsonUnbakedModelAccessor) json).setParentId(new ResewnItemModelIdentifier(parentId));
+ }
+ }
+
+ json.getOverrides().replaceAll(override -> {
+ String[] modelIdPathSplit = override.getModelId().getPath().split("/");
+ if (override.getModelId().getPath().startsWith("./") || (modelIdPathSplit.length > 2 && modelIdPathSplit[1].equals("cit"))) {
+ Identifier resolvedOverridePath = CITType.resolvePath(id, override.getModelId().getPath(), ".json", identifier -> resourceManager.containsResource(identifier));
+ if (resolvedOverridePath != null)
+ return new ModelOverride(new ResewnItemModelIdentifier(resolvedOverridePath), override.streamConditions().collect(Collectors.toList()));
+ }
+
+ return override;
+ });
+
+ cir.setReturnValue(json);
+ } catch (Exception ignored) {
+ } finally {
+ IOUtils.closeQuietly(is, resource);
+ }
+ }
+ }
+
+ @ModifyArg(method = "loadModelFromJson", at =
+ @At(value = "INVOKE", target = "Lnet/minecraft/resource/ResourceManager;getResource(Lnet/minecraft/util/Identifier;)Lnet/minecraft/resource/Resource;"))
+ public Identifier citresewn$fixDuplicatePrefixSuffix(Identifier original) {
+ if (original.getPath().startsWith("models/models/") && original.getPath().endsWith(".json.json") && original.getPath().contains("cit"))
+ return new Identifier(original.getNamespace(), original.getPath().substring(7, original.getPath().length() - 5));
+
+ return original;
+ }
+}