1
0
mirror of https://github.com/GeyserMC/Rainbow.git synced 2025-12-19 14:59:16 +00:00

Support mapping legacy custom model data items

This commit is contained in:
Eclipse
2025-07-19 12:31:14 +00:00
parent 983ae8c973
commit c6561a558f
5 changed files with 135 additions and 64 deletions

View File

@@ -5,6 +5,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.item.BlockModelWrapper;
import net.minecraft.client.renderer.item.ConditionalItemModel;
import net.minecraft.client.renderer.item.ItemModel;
import net.minecraft.client.renderer.item.RangeSelectItemModel;
import net.minecraft.client.renderer.item.SelectItemModel;
import net.minecraft.client.renderer.item.properties.conditional.Broken;
import net.minecraft.client.renderer.item.properties.conditional.CustomModelDataProperty;
@@ -12,6 +13,7 @@ import net.minecraft.client.renderer.item.properties.conditional.Damaged;
import net.minecraft.client.renderer.item.properties.conditional.FishingRodCast;
import net.minecraft.client.renderer.item.properties.conditional.HasComponent;
import net.minecraft.client.renderer.item.properties.conditional.ItemModelPropertyTest;
import net.minecraft.client.renderer.item.properties.numeric.RangeSelectItemModelProperty;
import net.minecraft.client.renderer.item.properties.select.Charge;
import net.minecraft.client.renderer.item.properties.select.ContextDimension;
import net.minecraft.client.renderer.item.properties.select.DisplayContext;
@@ -19,7 +21,6 @@ import net.minecraft.client.renderer.item.properties.select.SelectItemModelPrope
import net.minecraft.client.renderer.item.properties.select.TrimMaterialProperty;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.resources.model.ResolvedModel;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
@@ -29,7 +30,6 @@ import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.CrossbowItem;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.ItemAttributeModifiers;
import net.minecraft.world.item.equipment.trim.TrimMaterial;
import net.minecraft.world.level.Level;
@@ -43,20 +43,20 @@ import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext;
import org.geysermc.rainbow.mapping.geometry.GeometryMapper;
import org.geysermc.rainbow.mapping.geometry.GeometryRenderer;
import org.geysermc.rainbow.mapping.geyser.GeyserBaseDefinition;
import org.geysermc.rainbow.mapping.geyser.GeyserMappings;
import org.geysermc.rainbow.mapping.geyser.GeyserItemDefinition;
import org.geysermc.rainbow.mapping.geyser.GeyserLegacyDefinition;
import org.geysermc.rainbow.mapping.geyser.GeyserSingleDefinition;
import org.geysermc.rainbow.mapping.geyser.predicate.GeyserConditionPredicate;
import org.geysermc.rainbow.mapping.geyser.predicate.GeyserMatchPredicate;
import org.geysermc.rainbow.mapping.geyser.predicate.GeyserPredicate;
import org.geysermc.rainbow.mixin.ConditionalItemModelAccessor;
import org.geysermc.rainbow.mixin.RangeSelectItemModelAccessor;
import org.geysermc.rainbow.mixin.SelectItemModelAccessor;
import org.geysermc.rainbow.pack.BedrockItem;
import org.geysermc.rainbow.pack.BedrockTextures;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -65,32 +65,39 @@ public class BedrockItemMapper {
.map(ResourceLocation::withDefaultNamespace)
.toList();
public static void tryMapStack(ItemStack stack, ResourceLocation model, ProblemReporter reporter,
GeyserMappings mappings, Path packPath, BedrockItemConsumer itemConsumer, Consumer<ResourceLocation> additionalTextureConsumer) {
String displayName = stack.getHoverName().getString();
int protectionValue = 0;
ItemAttributeModifiers modifiers = stack.get(DataComponents.ATTRIBUTE_MODIFIERS);
if (modifiers != null) {
protectionValue = modifiers.modifiers().stream()
.filter(modifier -> modifier.attribute() == Attributes.ARMOR && modifier.modifier().operation() == AttributeModifier.Operation.ADD_VALUE)
.mapToInt(entry -> (int) entry.modifier().amount())
.sum();
}
mapItem(model, displayName, protectionValue, stack.getComponentsPatch(), reporter,
packPath, mapping -> mappings.map(stack.getItemHolder(), new GeyserSingleDefinition(mapping, Optional.of(model))),
itemConsumer, additionalTextureConsumer);
public static void tryMapStack(ItemStack stack, ResourceLocation modelLocation, ProblemReporter reporter, PackContext context) {
ItemModel model = Minecraft.getInstance().getModelManager().getItemModel(modelLocation);
mapItem(model, stack, reporter.forChild(() -> "client item definition " + modelLocation + " "), base -> new GeyserSingleDefinition(base, Optional.of(modelLocation)), context);
}
public static void mapItem(ResourceLocation modelLocation, String displayName, int protectionValue, DataComponentPatch componentPatch, ProblemReporter reporter,
Path packPath, Consumer<GeyserBaseDefinition> mappingConsumer, BedrockItemConsumer itemConsumer,
Consumer<ResourceLocation> additionalTextureConsumer) {
ItemModel model = Minecraft.getInstance().getModelManager().getItemModel(modelLocation);
MappingContext context = new MappingContext(List.of(), displayName, protectionValue, componentPatch,
reporter.forChild(() -> "client item definition " + modelLocation + " "),
packPath, mappingConsumer, itemConsumer, additionalTextureConsumer);
mapItem(model, context);
public static void tryMapStack(ItemStack stack, int customModelData, ProblemReporter reporter, PackContext context) {
ItemModel vanillaModel = Minecraft.getInstance().getModelManager().getItemModel(stack.get(DataComponents.ITEM_MODEL));
reporter = reporter.forChild(() -> "item model " + vanillaModel + " with custom model data " + customModelData + " ");
if (vanillaModel instanceof RangeSelectItemModel rangeModel) {
RangeSelectItemModelAccessor accessor = (RangeSelectItemModelAccessor) rangeModel;
RangeSelectItemModelProperty property = accessor.getProperty();
// WHY, Mojang?
if (property instanceof net.minecraft.client.renderer.item.properties.numeric.CustomModelDataProperty(int index)) {
if (index == 0) {
float scaledCustomModelData = customModelData * accessor.getScale();
int modelIndex = RangeSelectItemModelAccessor.invokeLastIndexLessOrEqual(accessor.getThresholds(), scaledCustomModelData);
ItemModel model = modelIndex == -1 ? accessor.getFallback() : accessor.getModels()[index];
mapItem(model, stack, reporter, base -> new GeyserLegacyDefinition(base, customModelData), context);
} else {
reporter.report(() -> "range_dispatch custom model data property index is not zero, unable to apply custom model data");
}
} else {
reporter.report(() -> "range_dispatch model property is not custom model data, unable to apply custom model data");
}
} else {
reporter.report(() -> "item model is not range_dispatch, unable to apply custom model data");
}
}
public static void mapItem(ItemModel model, ItemStack stack, ProblemReporter reporter,
Function<GeyserBaseDefinition, GeyserItemDefinition> definitionCreator, PackContext packContext) {
mapItem(model, new MappingContext(List.of(), stack, reporter, definitionCreator, packContext));
}
private static void mapItem(ItemModel model, MappingContext context) {
@@ -189,33 +196,30 @@ public class BedrockItemMapper {
mapItem(cases.defaultReturnValue(), context.child("select fallback case "));
}
private record MappingContext(List<GeyserPredicate> predicateStack, String displayName, int protectionValue, DataComponentPatch componentPatch, ProblemReporter reporter,
Path packPath, Consumer<GeyserBaseDefinition> mappingConsumer, BedrockItemConsumer itemConsumer,
Consumer<ResourceLocation> additionalTextureConsumer) {
private record MappingContext(List<GeyserPredicate> predicateStack, ItemStack stack, ProblemReporter reporter,
Function<GeyserBaseDefinition, GeyserItemDefinition> definitionCreator, PackContext packContext) {
public MappingContext with(GeyserPredicate predicate, String childName) {
return new MappingContext(Stream.concat(predicateStack.stream(), Stream.of(predicate)).toList(), displayName, protectionValue, componentPatch,
reporter.forChild(() -> childName), packPath, mappingConsumer, itemConsumer, additionalTextureConsumer);
return new MappingContext(Stream.concat(predicateStack.stream(), Stream.of(predicate)).toList(), stack, reporter.forChild(() -> childName), definitionCreator, packContext);
}
public MappingContext child(String childName) {
return new MappingContext(predicateStack, displayName, protectionValue, componentPatch, reporter.forChild(() -> childName),
packPath, mappingConsumer, itemConsumer, additionalTextureConsumer);
return new MappingContext(predicateStack, stack, reporter.forChild(() -> childName), definitionCreator, packContext);
}
public void create(ResourceLocation bedrockIdentifier, ResourceLocation texture, boolean displayHandheld,
Optional<ResolvedModel> customModel) {
GeyserBaseDefinition definition = new GeyserBaseDefinition(bedrockIdentifier, Optional.of(displayName), predicateStack,
new GeyserBaseDefinition.BedrockOptions(Optional.empty(), true, displayHandheld, protectionValue), componentPatch);
GeyserBaseDefinition base = new GeyserBaseDefinition(bedrockIdentifier, Optional.of(stack.getHoverName().getString()), predicateStack,
new GeyserBaseDefinition.BedrockOptions(Optional.empty(), true, displayHandheld, calculateProtectionValue(stack)), stack.getComponentsPatch());
try {
mappingConsumer.accept(definition);
packContext.mappings().map(stack.getItemHolder(), definitionCreator.apply(base));
} catch (Exception exception) {
reporter.forChild(() -> "mapping with bedrock identifier " + bedrockIdentifier + " ").report(() -> "failed to pass mapping: " + exception.getMessage());
return;
}
// TODO Should probably get a better way to get geometry texture
String safeIdentifier = definition.textureName();
String safeIdentifier = base.textureName();
String bone = "bone";
ResourceLocation geometryTexture = texture;
Optional<BedrockGeometryContext> bedrockGeometry = customModel.map(model -> GeometryMapper.mapGeometry(safeIdentifier, bone, model, geometryTexture));
@@ -223,18 +227,26 @@ public class BedrockItemMapper {
boolean exportTexture = true;
if (customModel.isPresent()) {
ItemStack fakeItem = new ItemStack(Items.FLINT);
//fakeItem.set(DataComponents.ITEM_MODEL, model); TODO
texture = texture.withPath(path -> path + "_icon");
GeometryRenderer.render(fakeItem, packPath.resolve(BedrockTextures.TEXTURES_FOLDER + texture.getPath() + ".png"));
GeometryRenderer.render(stack, packContext.packPath().resolve(BedrockTextures.TEXTURES_FOLDER + texture.getPath() + ".png"));
exportTexture = false;
additionalTextureConsumer.accept(geometryTexture);
packContext.additionalTextureConsumer().accept(geometryTexture);
}
itemConsumer.accept(new BedrockItem(bedrockIdentifier, definition.textureName(), texture, exportTexture,
AttachableMapper.mapItem(componentPatch, bedrockIdentifier, bedrockGeometry, bedrockAnimation, additionalTextureConsumer),
packContext.itemConsumer().accept(new BedrockItem(bedrockIdentifier, base.textureName(), texture, exportTexture,
AttachableMapper.mapItem(stack.getComponentsPatch(), bedrockIdentifier, bedrockGeometry, bedrockAnimation, packContext.additionalTextureConsumer()),
bedrockGeometry.map(BedrockGeometryContext::geometry), bedrockAnimation.map(BedrockAnimationContext::animation)));
}
private static int calculateProtectionValue(ItemStack stack) {
ItemAttributeModifiers modifiers = stack.get(DataComponents.ATTRIBUTE_MODIFIERS);
if (modifiers != null) {
return modifiers.modifiers().stream()
.filter(modifier -> modifier.attribute() == Attributes.ARMOR && modifier.modifier().operation() == AttributeModifier.Operation.ADD_VALUE)
.mapToInt(entry -> (int) entry.modifier().amount())
.sum();
}
return 0;
}
}
}

View File

@@ -0,0 +1,10 @@
package org.geysermc.rainbow.mapping;
import net.minecraft.resources.ResourceLocation;
import org.geysermc.rainbow.mapping.geyser.GeyserMappings;
import java.nio.file.Path;
import java.util.function.Consumer;
public record PackContext(GeyserMappings mappings, Path packPath, BedrockItemConsumer itemConsumer, Consumer<ResourceLocation> additionalTextureConsumer) {
}

View File

@@ -0,0 +1,32 @@
package org.geysermc.rainbow.mixin;
import net.minecraft.client.renderer.item.ItemModel;
import net.minecraft.client.renderer.item.RangeSelectItemModel;
import net.minecraft.client.renderer.item.properties.numeric.RangeSelectItemModelProperty;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(RangeSelectItemModel.class)
public interface RangeSelectItemModelAccessor {
@Accessor
RangeSelectItemModelProperty getProperty();
@Accessor
float getScale();
@Accessor
float[] getThresholds();
@Accessor
ItemModel[] getModels();
@Accessor
ItemModel getFallback();
@Invoker
static int invokeLastIndexLessOrEqual(float[] thresholds, float value) {
throw new AssertionError();
}
}

View File

@@ -1,5 +1,7 @@
package org.geysermc.rainbow.pack;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.SplashRenderer;
@@ -9,11 +11,13 @@ import net.minecraft.util.ProblemReporter;
import net.minecraft.util.RandomSource;
import net.minecraft.util.StringUtil;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.CustomModelData;
import org.apache.commons.io.IOUtils;
import org.geysermc.rainbow.CodecUtil;
import org.geysermc.rainbow.PackConstants;
import org.geysermc.rainbow.Rainbow;
import org.geysermc.rainbow.mapping.BedrockItemMapper;
import org.geysermc.rainbow.mapping.PackContext;
import org.geysermc.rainbow.mapping.geyser.GeyserMappings;
import org.geysermc.rainbow.mixin.SplashRendererAccessor;
import org.jetbrains.annotations.NotNull;
@@ -36,7 +40,8 @@ public class BedrockPack {
"use !!plshelp", "rm -rf --no-preserve-root /*", "welcome to the internet!", "beep beep. boop boop?", "FROG", "it is frog day", "it is cat day!",
"eclipse will hear about this.", "you must now say the word 'frog' in the #general channel", "You Just Lost The Game", "you are now breathing manually",
"you are now blinking manually", "you're eligible for a free hug token! <3", "don't mind me!", "hissss", "Gayser and Floodgayte, my favourite plugins.",
"meow", "we'll be done here soon™", "got anything else to say?", "we're done now!", "this will be fixed by v6053", "expect it to be done within 180 business days!");
"meow", "we'll be done here soon™", "got anything else to say?", "we're done now!", "this will be fixed by v6053", "expect it to be done within 180 business days!",
"any colour you like", "someone tell Mojang about this");
private static final RandomSource RANDOM = RandomSource.create();
private static final Path EXPORT_DIRECTORY = FabricLoader.getInstance().getGameDir().resolve(Rainbow.MOD_ID);
@@ -62,6 +67,7 @@ public class BedrockPack {
private final Set<BedrockItem> bedrockItems = new HashSet<>();
private final Set<ResourceLocation> texturesToExport = new HashSet<>();
private final Set<ResourceLocation> modelsMapped = new HashSet<>();
private final IntSet customModelDataMapped = new IntOpenHashSet();
private final ProblemReporter.Collector reporter;
@@ -90,16 +96,6 @@ public class BedrockPack {
return MappingResult.NONE_MAPPED;
}
Optional<? extends ResourceLocation> patchedModel = stack.getComponentsPatch().get(DataComponents.ITEM_MODEL);
//noinspection OptionalAssignedToNull - annoying Mojang
if (patchedModel == null || patchedModel.isEmpty()) {
return MappingResult.NONE_MAPPED;
}
ResourceLocation model = patchedModel.get();
if (!modelsMapped.add(model)) {
return MappingResult.NONE_MAPPED;
}
AtomicBoolean problems = new AtomicBoolean();
ProblemReporter mapReporter = new ProblemReporter() {
@@ -114,14 +110,34 @@ public class BedrockPack {
reporter.report(problem);
}
};
BedrockItemMapper.tryMapStack(stack, model, mapReporter, mappings, packPath, bedrockItem -> {
itemTextures.withItemTexture(bedrockItem);
if (bedrockItem.exportTexture()) {
texturesToExport.add(bedrockItem.texture());
PackContext context = new PackContext(mappings, packPath, item -> {
itemTextures.withItemTexture(item);
if (item.exportTexture()) {
texturesToExport.add(item.texture());
}
bedrockItems.add(bedrockItem);
bedrockItems.add(item);
}, texturesToExport::add);
Optional<? extends ResourceLocation> patchedModel = stack.getComponentsPatch().get(DataComponents.ITEM_MODEL);
//noinspection OptionalAssignedToNull - annoying Mojang
if (patchedModel == null || patchedModel.isEmpty()) {
CustomModelData customModelData = stack.get(DataComponents.CUSTOM_MODEL_DATA);
Float firstNumber;
if (customModelData == null || (firstNumber = customModelData.getFloat(0)) == null
|| !customModelDataMapped.add((firstNumber.intValue()))) {
return MappingResult.NONE_MAPPED;
}
BedrockItemMapper.tryMapStack(stack, firstNumber.intValue(), mapReporter, context);
} else {
ResourceLocation model = patchedModel.get();
if (!modelsMapped.add(model)) {
return MappingResult.NONE_MAPPED;
}
BedrockItemMapper.tryMapStack(stack, model, mapReporter, context);
}
return problems.get() ? MappingResult.PROBLEMS_OCCURRED : MappingResult.MAPPED_SUCCESSFULLY;
}

View File

@@ -13,6 +13,7 @@
"ModelManagerMixin",
"PictureInPictureRendererAccessor",
"PictureInPictureRendererMixin",
"RangeSelectItemModelAccessor",
"SelectItemModelAccessor",
"SelectItemModelMixin",
"SelectItemModelMixin$UnbakedSwitchMixin",