diff options
Diffstat (limited to 'defaults/src/main/java')
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; + } +} |