diff --git a/src/main/java/org/geysermc/packgenerator/GeyserMappingsGenerator.java b/src/main/java/org/geysermc/packgenerator/GeyserMappingsGenerator.java index e834bc2..325b2d1 100644 --- a/src/main/java/org/geysermc/packgenerator/GeyserMappingsGenerator.java +++ b/src/main/java/org/geysermc/packgenerator/GeyserMappingsGenerator.java @@ -15,6 +15,7 @@ public class GeyserMappingsGenerator implements ClientModInitializer { @Override public void onInitializeClient() { + // TODO do the exceptions properly ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> { dispatcher.register(ClientCommandManager.literal("packgenerator") .then(ClientCommandManager.literal("create") diff --git a/src/main/java/org/geysermc/packgenerator/PackManager.java b/src/main/java/org/geysermc/packgenerator/PackManager.java index d13ed4b..9f6d0f9 100644 --- a/src/main/java/org/geysermc/packgenerator/PackManager.java +++ b/src/main/java/org/geysermc/packgenerator/PackManager.java @@ -7,18 +7,13 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.serialization.JsonOps; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.core.component.DataComponents; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; -import org.geysermc.packgenerator.mappings.GeyserMapping; import org.geysermc.packgenerator.mappings.GeyserMappings; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; -import java.util.Optional; public final class PackManager { public static final Path EXPORT_DIRECTORY = FabricLoader.getInstance().getGameDir() @@ -44,24 +39,16 @@ public final class PackManager { public boolean map(ItemStack stack, boolean throwOnModelMissing) throws CommandSyntaxException { ensurePackIsCreated(); - Optional patchedModel = stack.getComponentsPatch().get(DataComponents.ITEM_MODEL); - //noinspection OptionalAssignedToNull - annoying Mojang - if (patchedModel == null || patchedModel.isEmpty()) { + try { + mappings.map(stack); + return true; + } catch (IllegalArgumentException exception) { if (throwOnModelMissing) { throw new SimpleCommandExceptionType(Component.literal("Item stack does not have a custom model")).create(); + } else { + return false; } - return false; } - - ResourceLocation model = patchedModel.get(); - String displayName = stack.getHoverName().getString(); - GeyserMapping mapping = new GeyserMapping(model, model, Optional.of(displayName), - List.of(), - new GeyserMapping.BedrockOptions(Optional.empty(), true, false, 0), - stack.getComponentsPatch()); - mappings.map(stack.getItemHolder(), mapping); - - return true; } public void finish() throws CommandSyntaxException { diff --git a/src/main/java/org/geysermc/packgenerator/accessor/BlockModelWrapperLocationAccessor.java b/src/main/java/org/geysermc/packgenerator/accessor/BlockModelWrapperLocationAccessor.java new file mode 100644 index 0000000..dd0353c --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/accessor/BlockModelWrapperLocationAccessor.java @@ -0,0 +1,11 @@ +package org.geysermc.packgenerator.accessor; + +import net.minecraft.resources.ResourceLocation; + +// Implemented on BlockModelWrapper, since this class doesn't store its model, we have to store it manually +public interface BlockModelWrapperLocationAccessor { + + ResourceLocation geyser_mappings_generator$getModelOrigin(); + + void geyser_mappings_generator$setModelOrigin(ResourceLocation model); +} diff --git a/src/main/java/org/geysermc/packgenerator/accessor/SelectItemModelCaseAccessor.java b/src/main/java/org/geysermc/packgenerator/accessor/SelectItemModelCaseAccessor.java new file mode 100644 index 0000000..5f9853a --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/accessor/SelectItemModelCaseAccessor.java @@ -0,0 +1,12 @@ +package org.geysermc.packgenerator.accessor; + +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import net.minecraft.client.renderer.item.ItemModel; + +// Implemented on BlockModelWrapper, since this class doesn't store the cases it has in a nice format, we have to store it manually +public interface SelectItemModelCaseAccessor { + + Object2ObjectMap geyser_mappings_generator$getCases(); + + void geyser_mappings_generator$setCases(Object2ObjectMap cases); +} diff --git a/src/main/java/org/geysermc/packgenerator/mappings/GeyserItemMapper.java b/src/main/java/org/geysermc/packgenerator/mappings/GeyserItemMapper.java new file mode 100644 index 0000000..650e00d --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/mappings/GeyserItemMapper.java @@ -0,0 +1,100 @@ +package org.geysermc.packgenerator.mappings; + +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.SelectItemModel; +import net.minecraft.client.renderer.item.properties.conditional.Broken; +import net.minecraft.client.renderer.item.properties.conditional.CustomModelDataProperty; +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.select.Charge; +import net.minecraft.client.renderer.item.properties.select.ContextDimension; +import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperty; +import net.minecraft.client.renderer.item.properties.select.TrimMaterialProperty; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.resources.ResourceLocation; +import org.geysermc.packgenerator.accessor.BlockModelWrapperLocationAccessor; +import org.geysermc.packgenerator.mappings.predicate.GeyserConditionPredicate; +import org.geysermc.packgenerator.mappings.predicate.GeyserMatchPredicate; +import org.geysermc.packgenerator.mappings.predicate.GeyserPredicate; +import org.geysermc.packgenerator.mixin.ConditionalItemModelAccessor; +import org.geysermc.packgenerator.mixin.SelectItemModelAccessor; + +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +public class GeyserItemMapper { + + public static Stream mapItem(ResourceLocation modelLocation, String displayName, int protectionValue, DataComponentPatch componentPatch) { + MappingContext context = new MappingContext(List.of(), modelLocation, displayName, protectionValue, componentPatch); + ItemModel model = Minecraft.getInstance().getModelManager().getItemModel(modelLocation); + return mapItem(model, context); + } + + private static Stream mapItem(ItemModel model, MappingContext context) { + switch (model) { + case BlockModelWrapper modelWrapper -> { + ResourceLocation itemModel = ((BlockModelWrapperLocationAccessor) modelWrapper).geyser_mappings_generator$getModelOrigin(); + return Stream.of(context.create(itemModel)); + } + case ConditionalItemModel conditional -> { + return mapConditionalModel(conditional, context); + } + case SelectItemModel select -> { + return Stream.of(); + } + default -> {} + } + throw new UnsupportedOperationException("Unable to map item model " + model.getClass()); + } + + private static Stream mapConditionalModel(ConditionalItemModel model, MappingContext context) { + ItemModelPropertyTest property = ((ConditionalItemModelAccessor) model).getProperty(); + GeyserConditionPredicate.Property predicateProperty = switch (property) { + case Broken ignored -> GeyserConditionPredicate.BROKEN; + case Damaged ignored -> GeyserConditionPredicate.DAMAGED; + case CustomModelDataProperty customModelData -> new GeyserConditionPredicate.CustomModelData(customModelData.index()); + case HasComponent hasComponent -> new GeyserConditionPredicate.HasComponent(hasComponent.componentType()); // ignoreDefault property not a thing, we should look into that in Geyser! TODO + case FishingRodCast ignored -> GeyserConditionPredicate.FISHING_ROD_CAST; + default -> throw new UnsupportedOperationException("Unsupported conditional model property " + property.getClass()); + }; + + ItemModel onTrue = ((ConditionalItemModelAccessor) model).getOnTrue(); + ItemModel onFalse = ((ConditionalItemModelAccessor) model).getOnFalse(); + + return Stream.concat( + mapItem(onTrue, context.with(new GeyserConditionPredicate(predicateProperty, true))), + mapItem(onFalse, context.with(new GeyserConditionPredicate(predicateProperty, false))) + ); + } + + private static Stream mapSelectModel(SelectItemModel model, MappingContext context) { + //noinspection unchecked + SelectItemModelProperty property = ((SelectItemModelAccessor) model).getProperty(); + Function dataConstructor = switch (property) { + case Charge ignored -> t -> new GeyserMatchPredicate.ChargeType(t); + case TrimMaterialProperty trimMaterial -> ; + case ContextDimension contextDimension -> ; + case net.minecraft.client.renderer.item.properties.select.CustomModelDataProperty customModelData -> ; // Why, Mojang? + } + } + + private record MappingContext(List predicateStack, ResourceLocation model, String displayName, int protectionValue, DataComponentPatch componentPatch) { + + public MappingContext with(GeyserPredicate predicate) { + return new MappingContext(Stream.concat(predicateStack.stream(), Stream.of(predicate)).toList(), model, displayName, protectionValue, componentPatch); + } + + public GeyserMapping create(ResourceLocation bedrockIdentifier) { + return new GeyserMapping(model, bedrockIdentifier, Optional.of(displayName), predicateStack, + new GeyserMapping.BedrockOptions(Optional.empty(), true, false, protectionValue), // TODO handheld prediction + componentPatch); + } + } +} diff --git a/src/main/java/org/geysermc/packgenerator/mappings/GeyserMapping.java b/src/main/java/org/geysermc/packgenerator/mappings/GeyserMapping.java index dd22e60..5444f61 100644 --- a/src/main/java/org/geysermc/packgenerator/mappings/GeyserMapping.java +++ b/src/main/java/org/geysermc/packgenerator/mappings/GeyserMapping.java @@ -12,7 +12,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Function; -// TODO predicates, etc. +// TODO other keys, etc. public record GeyserMapping(ResourceLocation model, ResourceLocation bedrockIdentifier, Optional displayName, List predicates, BedrockOptions bedrockOptions, DataComponentPatch components) { private static final List> SUPPORTED_COMPONENTS = List.of(DataComponents.CONSUMABLE, DataComponents.EQUIPPABLE, DataComponents.FOOD, diff --git a/src/main/java/org/geysermc/packgenerator/mappings/GeyserMappings.java b/src/main/java/org/geysermc/packgenerator/mappings/GeyserMappings.java index a38e2c7..c12e7d9 100644 --- a/src/main/java/org/geysermc/packgenerator/mappings/GeyserMappings.java +++ b/src/main/java/org/geysermc/packgenerator/mappings/GeyserMappings.java @@ -5,13 +5,18 @@ import com.google.common.collect.MultimapBuilder; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.core.Holder; +import net.minecraft.core.component.DataComponents; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import java.util.ArrayList; import java.util.Collection; import java.util.Map; +import java.util.Optional; import java.util.function.Function; +// TODO group definitions public class GeyserMappings { private static final Codec, Collection>> MAPPINGS_CODEC = Codec.unboundedMap(Item.CODEC, GeyserMapping.CODEC.listOf().xmap(Function.identity(), ArrayList::new)); @@ -37,6 +42,21 @@ public class GeyserMappings { mappings.put(item, mapping); } + public void map(ItemStack stack) { + Optional patchedModel = stack.getComponentsPatch().get(DataComponents.ITEM_MODEL); + //noinspection OptionalAssignedToNull - annoying Mojang + if (patchedModel == null || patchedModel.isEmpty()) { + throw new IllegalArgumentException("Item stack does not have a custom model"); + } + + ResourceLocation model = patchedModel.get(); + String displayName = stack.getHoverName().getString(); + int protectionValue = 0; // TODO check the attributes + + GeyserItemMapper.mapItem(model, displayName, protectionValue, stack.getComponentsPatch()) + .forEach(mapping -> map(stack.getItemHolder(), mapping)); + } + public Map, Collection> mappings() { return mappings.asMap(); } diff --git a/src/main/java/org/geysermc/packgenerator/mixin/BlockModelWrapperMixin.java b/src/main/java/org/geysermc/packgenerator/mixin/BlockModelWrapperMixin.java new file mode 100644 index 0000000..4bb67ca --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/mixin/BlockModelWrapperMixin.java @@ -0,0 +1,43 @@ +package org.geysermc.packgenerator.mixin; + +import net.minecraft.client.renderer.item.BlockModelWrapper; +import net.minecraft.client.renderer.item.ItemModel; +import net.minecraft.resources.ResourceLocation; +import org.geysermc.packgenerator.accessor.BlockModelWrapperLocationAccessor; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(BlockModelWrapper.class) +public abstract class BlockModelWrapperMixin implements ItemModel, BlockModelWrapperLocationAccessor { + + @Unique + private ResourceLocation modelOrigin; + + @Override + public ResourceLocation geyser_mappings_generator$getModelOrigin() { + return modelOrigin; + } + + @Override + public void geyser_mappings_generator$setModelOrigin(ResourceLocation model) { + modelOrigin = model; + } + + @Mixin(BlockModelWrapper.Unbaked.class) + public abstract static class UnbakedMixin implements Unbaked { + + @Shadow + @Final + private ResourceLocation model; + + @Inject(method = "bake", at = @At("TAIL")) + public void setModelOrigin(BakingContext context, CallbackInfoReturnable callbackInfoReturnable) { + ((BlockModelWrapperLocationAccessor) callbackInfoReturnable.getReturnValue()).geyser_mappings_generator$setModelOrigin(model); + } + } +} diff --git a/src/main/java/org/geysermc/packgenerator/mixin/ConditionalItemModelAccessor.java b/src/main/java/org/geysermc/packgenerator/mixin/ConditionalItemModelAccessor.java new file mode 100644 index 0000000..899e95c --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/mixin/ConditionalItemModelAccessor.java @@ -0,0 +1,20 @@ +package org.geysermc.packgenerator.mixin; + +import net.minecraft.client.renderer.item.ConditionalItemModel; +import net.minecraft.client.renderer.item.ItemModel; +import net.minecraft.client.renderer.item.properties.conditional.ItemModelPropertyTest; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ConditionalItemModel.class) +public interface ConditionalItemModelAccessor { + + @Accessor + ItemModelPropertyTest getProperty(); + + @Accessor + ItemModel getOnTrue(); + + @Accessor + ItemModel getOnFalse(); +} diff --git a/src/main/java/org/geysermc/packgenerator/mixin/SelectItemModelAccessor.java b/src/main/java/org/geysermc/packgenerator/mixin/SelectItemModelAccessor.java new file mode 100644 index 0000000..892b401 --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/mixin/SelectItemModelAccessor.java @@ -0,0 +1,16 @@ +package org.geysermc.packgenerator.mixin; + +import net.minecraft.client.renderer.item.SelectItemModel; +import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperty; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(SelectItemModel.class) +public interface SelectItemModelAccessor { + + @Accessor + SelectItemModelProperty getProperty(); + + @Accessor + SelectItemModel.ModelSelector getModels(); +} diff --git a/src/main/java/org/geysermc/packgenerator/mixin/SelectItemModelMixin.java b/src/main/java/org/geysermc/packgenerator/mixin/SelectItemModelMixin.java new file mode 100644 index 0000000..e326ae1 --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/mixin/SelectItemModelMixin.java @@ -0,0 +1,41 @@ +package org.geysermc.packgenerator.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import net.minecraft.client.renderer.item.ItemModel; +import net.minecraft.client.renderer.item.SelectItemModel; +import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperty; +import org.geysermc.packgenerator.accessor.SelectItemModelCaseAccessor; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(SelectItemModel.class) +public abstract class SelectItemModelMixin implements ItemModel, SelectItemModelCaseAccessor { + + @Unique + private Object2ObjectMap cases; + + @Override + public Object2ObjectMap geyser_mappings_generator$getCases() { + return cases; + } + + @Override + public void geyser_mappings_generator$setCases(Object2ObjectMap cases) { + this.cases = cases; + } + + @Mixin(SelectItemModel.UnbakedSwitch.class) + public abstract static class UnbakedSwitchMixin

, T> { + + @Inject(method = "bake", at = @At("TAIL")) + public void setCases(BakingContext bakingContext, ItemModel model, CallbackInfoReturnable callbackInfoReturnable, + @Local Object2ObjectMap cases) { + //noinspection unchecked + ((SelectItemModelCaseAccessor) callbackInfoReturnable.getReturnValue()).geyser_mappings_generator$setCases(cases); + } + } +} diff --git a/src/main/resources/geyser-mappings-generator.mixins.json b/src/main/resources/geyser-mappings-generator.mixins.json index 4d87209..391cd9a 100644 --- a/src/main/resources/geyser-mappings-generator.mixins.json +++ b/src/main/resources/geyser-mappings-generator.mixins.json @@ -4,7 +4,14 @@ "package": "org.geysermc.packgenerator.mixin", "compatibilityLevel": "JAVA_21", "mixins": [], - "client": [], + "client": [ + "BlockModelWrapperMixin", + "BlockModelWrapperMixin$UnbakedMixin", + "ConditionalItemModelAccessor", + "SelectItemModelAccessor", + "SelectItemModelMixin", + "SelectItemModelMixin$UnbakedSwitchMixin" + ], "injectors": { "defaultRequire": 1 }