diff --git a/README.md b/README.md index d1cc6aa..a3cc177 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,13 @@ If you have any questions or run into any problems, please do feel free to ask f Rainbow is currently capable of the following: -- Generating Geyser item mappings complete with data components and proper bedrock options, by detecting items with a custom `minecraft:item_model` component and analysing their components. +- Generating Geyser item mappings complete with data components and proper bedrock options, by detecting items with custom `minecraft:item_model` or `minecraft:custom_model_data` components, and analysing their components. - Also includes generating mappings with predicates for more complicated Java item model definitions, such as checks for if an item is broken. The following definition types are currently supported by Rainbow: - Plain item model definitions. - Conditional item models, supported properties are: - `broken`, - `damaged`, - - `cutom_model_data`, + - `custom_model_data`, - `has_component`, and, - `fishing_rod/cast`. - Range dispatch item models, supported properties are: diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index 1453b3f..adca7a5 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -72,9 +72,10 @@ private static Identifier getId(ExtraCodecs.LateBoundIdMapper return ((LateBoundIdMapperAccessor) mapper).getIdToValue().inverse().get(type); } - public static void tryMapStack(ItemStackTemplate stack, Identifier modelIdentifier, ProblemReporter reporter, PackContext context) { + public static void tryMapStack(ItemStackTemplate stack, Identifier modelIdentifier, ProblemReporter reporter, PackContext context, boolean ignoreTopPlainModel) { context.assetResolver().getClientItem(modelIdentifier).map(ClientItem::model) - .ifPresentOrElse(model -> mapItem(model, stack, reporter.forChild(() -> "client item definition " + modelIdentifier + " "), base -> new GeyserSingleDefinition(base, Optional.of(modelIdentifier)), context), + .ifPresentOrElse(model -> mapItem(model, stack, reporter.forChild(() -> "client item definition " + modelIdentifier + " "), + base -> new GeyserSingleDefinition(base, Optional.of(modelIdentifier)), context, ignoreTopPlainModel), () -> reporter.report(() -> "missing client item definition " + modelIdentifier)); } @@ -87,14 +88,18 @@ public static void tryMapStack(ItemStackTemplate stack, int customModelData, Pro // WHY, Mojang? if (property instanceof net.minecraft.client.renderer.item.properties.numeric.CustomModelDataProperty(int index)) { if (index == 0) { + List sortedEntries = entries.stream() + .sorted(RangeSelectItemModel.Entry.BY_THRESHOLD) + .toList(); float scaledCustomModelData = customModelData * scale; - float[] thresholds = ArrayUtils.toPrimitive(entries.stream() + float[] thresholds = ArrayUtils.toPrimitive(sortedEntries.stream() .map(RangeSelectItemModel.Entry::threshold) .toArray(Float[]::new)); int modelIndex = RangeSelectItemModelAccessor.invokeLastIndexLessOrEqual(thresholds, scaledCustomModelData); - Optional model = modelIndex == -1 ? fallback : Optional.of(entries.get(modelIndex).model()); - model.ifPresentOrElse(present -> mapItem(present, stack, childReporter, base -> new GeyserLegacyDefinition(base, customModelData), context), + Optional model = modelIndex == -1 ? fallback : Optional.of(sortedEntries.get(modelIndex).model()); + model.ifPresentOrElse(present -> mapItem(present, stack, childReporter, + base -> new GeyserLegacyDefinition(base, customModelData), context, false), () -> childReporter.report(() -> "custom model data index lookup returned -1, and no fallback is present")); } else { childReporter.report(() -> "range_dispatch custom model data property index is not zero, unable to apply custom model data"); @@ -106,13 +111,20 @@ public static void tryMapStack(ItemStackTemplate stack, int customModelData, Pro } public static void mapItem(ItemModel.Unbaked model, ItemStackTemplate stack, ProblemReporter reporter, - Function definitionCreator, PackContext packContext) { - mapItem(model, new MappingContext(stack, reporter, definitionCreator, packContext)); + Function definitionCreator, PackContext packContext, + boolean ignoreTopPlainModel) { + mapItem(model, new MappingContext(stack, reporter, definitionCreator, packContext, ignoreTopPlainModel)); } private static void mapItem(ItemModel.Unbaked model, MappingContext context) { switch (model) { - case CuboidItemModelWrapper.Unbaked modelWrapper -> mapBlockModelWrapper(modelWrapper, context.child("plain model " + modelWrapper.model())); + case CuboidItemModelWrapper.Unbaked modelWrapper -> { + if (context.ignorePlainModel) { + context.report("ignoring plain model as requested by context"); + } else { + mapBlockModelWrapper(modelWrapper, context.child("plain model " + modelWrapper.model())); + } + } case ConditionalItemModel.Unbaked conditional -> mapConditionalModel(conditional, context.child("condition model ")); case RangeSelectItemModel.Unbaked rangeSelect -> mapRangeSelectModel(rangeSelect, context.child("range select model ")); case SelectItemModel.Unbaked select -> mapSelectModel(select, context.child("select model ")); @@ -208,22 +220,28 @@ private static void mapSelectModel(SelectItemModel.Unbaked model, MappingContext private record MappingContext(List predicateStack, Optional transformationStack, ItemStackTemplate itemStack, ProblemReporter reporter, - Function definitionCreator, PackContext packContext) { + Function definitionCreator, PackContext packContext, + boolean ignorePlainModel) { - public MappingContext(ItemStackTemplate stack, ProblemReporter reporter, Function definitionCreator, PackContext packContext) { - this(List.of(), Optional.empty(), stack, reporter, definitionCreator, packContext); + public MappingContext(ItemStackTemplate stack, ProblemReporter reporter, Function definitionCreator, PackContext packContext, + boolean ignorePlainModel) { + this(List.of(), Optional.empty(), stack, reporter, definitionCreator, packContext, ignorePlainModel); } + // Only copy ignorePlainModel when there is not a predicate public MappingContext with(GeyserPredicate predicate, Optional transformation, String childName) { - return new MappingContext(Stream.concat(predicateStack.stream(), Stream.of(predicate)).toList(), addTransformation(transformation), itemStack, reporter.forChild(() -> childName), definitionCreator, packContext); + return new MappingContext(Stream.concat(predicateStack.stream(), Stream.of(predicate)).toList(), addTransformation(transformation), itemStack, + reporter.forChild(() -> childName), definitionCreator, packContext, false); } public MappingContext with(Optional transformation, String childName) { - return new MappingContext(predicateStack, addTransformation(transformation), itemStack, reporter.forChild(() -> childName), definitionCreator, packContext); + return new MappingContext(predicateStack, addTransformation(transformation), itemStack, + reporter.forChild(() -> childName), definitionCreator, packContext, ignorePlainModel); } public MappingContext child(String childName) { - return new MappingContext(predicateStack, transformationStack, itemStack, reporter.forChild(() -> childName), definitionCreator, packContext); + return new MappingContext(predicateStack, transformationStack, itemStack, + reporter.forChild(() -> childName), definitionCreator, packContext, ignorePlainModel); } public Transformation finaliseTransformation(Optional finalTransformation) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index 5d6976f..664d6ad 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -25,6 +25,7 @@ import java.nio.file.Path; import java.util.Collections; import java.util.HashSet; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -83,23 +84,32 @@ public void report(Problem problem) { } }; - if (!stack.components().split().added().has(DataComponents.ITEM_MODEL)) { + Identifier customModel = stack.components().split().added().get(DataComponents.ITEM_MODEL); + if (customModel == null) { + // If no custom item_model patch exists, try custom model data CustomModelData customModelData = stack.components().split().added().get(DataComponents.CUSTOM_MODEL_DATA); - Float firstNumber; - if (customModelData == null || (firstNumber = customModelData.getFloat(0)) == null - || !customModelDataMapped.add(Pair.of(stack.item(), firstNumber.intValue()))) { + if (customModelData == null) { return MappingResult.NONE_MAPPED; + } else if (isLegacyCustomModelData(customModelData)) { + // Legacy custom model data - only one float, nothing else + int customModelInt = Objects.requireNonNull(customModelData.getFloat(0)).intValue(); + if (!customModelDataMapped.add(Pair.of(stack.item(), customModelInt))) { + return MappingResult.NONE_MAPPED; + } + BedrockItemMapper.tryMapStack(stack, customModelInt, mapReporter, context); + } else { + // Try to map the vanilla model, but ignore the first direct plain model if present - this is the vanilla case + Identifier vanillaModel = Objects.requireNonNull(stack.get(DataComponents.ITEM_MODEL)); + if (!modelsMapped.add(vanillaModel)) { + return MappingResult.NONE_MAPPED; + } + BedrockItemMapper.tryMapStack(stack, vanillaModel, mapReporter, context, true); } - - BedrockItemMapper.tryMapStack(stack, firstNumber.intValue(), mapReporter, context); } else { - Identifier model = stack.components().split().added().get(DataComponents.ITEM_MODEL); - assert model != null; - if (!modelsMapped.add(model)) { + if (!modelsMapped.add(customModel)) { return MappingResult.NONE_MAPPED; } - - BedrockItemMapper.tryMapStack(stack, model, mapReporter, context); + BedrockItemMapper.tryMapStack(stack, customModel, mapReporter, context, false); } return problems.get() ? MappingResult.PROBLEMS_OCCURRED : MappingResult.MAPPED_SUCCESSFULLY; @@ -157,6 +167,10 @@ private PackSerializingContext createSerializingContext() { return new PackSerializingContext(context.assetResolver(), serializer, paths, reporter); } + private static boolean isLegacyCustomModelData(CustomModelData customModelData) { + return customModelData.floats().size() == 1 && customModelData.colors().isEmpty() && customModelData.flags().isEmpty() && customModelData.strings().isEmpty(); + } + public static Builder builder(String name, Path mappingsPath, Path packRootPath, PackSerializer packSerializer, AssetResolver assetResolver) { return new Builder(name, mappingsPath, packRootPath, packSerializer, assetResolver); }