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

Work on actually mapping predicates, conditional predicates work

This commit is contained in:
Eclipse
2025-06-28 20:28:52 +00:00
parent 5d9a5bdabb
commit 676782bc97
12 changed files with 279 additions and 21 deletions

View File

@@ -15,6 +15,7 @@ public class GeyserMappingsGenerator implements ClientModInitializer {
@Override @Override
public void onInitializeClient() { public void onInitializeClient() {
// TODO do the exceptions properly
ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> { ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> {
dispatcher.register(ClientCommandManager.literal("packgenerator") dispatcher.register(ClientCommandManager.literal("packgenerator")
.then(ClientCommandManager.literal("create") .then(ClientCommandManager.literal("create")

View File

@@ -7,18 +7,13 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.serialization.JsonOps; import com.mojang.serialization.JsonOps;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import org.geysermc.packgenerator.mappings.GeyserMapping;
import org.geysermc.packgenerator.mappings.GeyserMappings; import org.geysermc.packgenerator.mappings.GeyserMappings;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
public final class PackManager { public final class PackManager {
public static final Path EXPORT_DIRECTORY = FabricLoader.getInstance().getGameDir() 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 { public boolean map(ItemStack stack, boolean throwOnModelMissing) throws CommandSyntaxException {
ensurePackIsCreated(); ensurePackIsCreated();
Optional<? extends ResourceLocation> patchedModel = stack.getComponentsPatch().get(DataComponents.ITEM_MODEL); try {
//noinspection OptionalAssignedToNull - annoying Mojang mappings.map(stack);
if (patchedModel == null || patchedModel.isEmpty()) { return true;
} catch (IllegalArgumentException exception) {
if (throwOnModelMissing) { if (throwOnModelMissing) {
throw new SimpleCommandExceptionType(Component.literal("Item stack does not have a custom model")).create(); 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 { public void finish() throws CommandSyntaxException {

View File

@@ -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);
}

View File

@@ -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<T> {
Object2ObjectMap<T, ItemModel> geyser_mappings_generator$getCases();
void geyser_mappings_generator$setCases(Object2ObjectMap<T, ItemModel> cases);
}

View File

@@ -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<GeyserMapping> 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<GeyserMapping> 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<GeyserMapping> 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 <T> Stream<GeyserMapping> mapSelectModel(SelectItemModel<T> model, MappingContext context) {
//noinspection unchecked
SelectItemModelProperty<T> property = ((SelectItemModelAccessor<T>) model).getProperty();
Function<T, GeyserMatchPredicate.MatchPredicateData> 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<GeyserPredicate> 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);
}
}
}

View File

@@ -12,7 +12,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
// TODO predicates, etc. // TODO other keys, etc.
public record GeyserMapping(ResourceLocation model, ResourceLocation bedrockIdentifier, Optional<String> displayName, public record GeyserMapping(ResourceLocation model, ResourceLocation bedrockIdentifier, Optional<String> displayName,
List<GeyserPredicate> predicates, BedrockOptions bedrockOptions, DataComponentPatch components) { List<GeyserPredicate> predicates, BedrockOptions bedrockOptions, DataComponentPatch components) {
private static final List<DataComponentType<?>> SUPPORTED_COMPONENTS = List.of(DataComponents.CONSUMABLE, DataComponents.EQUIPPABLE, DataComponents.FOOD, private static final List<DataComponentType<?>> SUPPORTED_COMPONENTS = List.of(DataComponents.CONSUMABLE, DataComponents.EQUIPPABLE, DataComponents.FOOD,

View File

@@ -5,13 +5,18 @@ import com.google.common.collect.MultimapBuilder;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder; import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.Holder; 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.Item;
import net.minecraft.world.item.ItemStack;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
// TODO group definitions
public class GeyserMappings { public class GeyserMappings {
private static final Codec<Map<Holder<Item>, Collection<GeyserMapping>>> MAPPINGS_CODEC = Codec.unboundedMap(Item.CODEC, GeyserMapping.CODEC.listOf().xmap(Function.identity(), ArrayList::new)); private static final Codec<Map<Holder<Item>, Collection<GeyserMapping>>> 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); mappings.put(item, mapping);
} }
public void map(ItemStack stack) {
Optional<? extends ResourceLocation> 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<Holder<Item>, Collection<GeyserMapping>> mappings() { public Map<Holder<Item>, Collection<GeyserMapping>> mappings() {
return mappings.asMap(); return mappings.asMap();
} }

View File

@@ -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<ItemModel> callbackInfoReturnable) {
((BlockModelWrapperLocationAccessor) callbackInfoReturnable.getReturnValue()).geyser_mappings_generator$setModelOrigin(model);
}
}
}

View File

@@ -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();
}

View File

@@ -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<T> {
@Accessor
SelectItemModelProperty<T> getProperty();
@Accessor
SelectItemModel.ModelSelector<T> getModels();
}

View File

@@ -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<T> implements ItemModel, SelectItemModelCaseAccessor<T> {
@Unique
private Object2ObjectMap<T, ItemModel> cases;
@Override
public Object2ObjectMap<T, ItemModel> geyser_mappings_generator$getCases() {
return cases;
}
@Override
public void geyser_mappings_generator$setCases(Object2ObjectMap<T, ItemModel> cases) {
this.cases = cases;
}
@Mixin(SelectItemModel.UnbakedSwitch.class)
public abstract static class UnbakedSwitchMixin<P extends SelectItemModelProperty<T>, T> {
@Inject(method = "bake", at = @At("TAIL"))
public void setCases(BakingContext bakingContext, ItemModel model, CallbackInfoReturnable<ItemModel> callbackInfoReturnable,
@Local Object2ObjectMap<T, ItemModel> cases) {
//noinspection unchecked
((SelectItemModelCaseAccessor<T>) callbackInfoReturnable.getReturnValue()).geyser_mappings_generator$setCases(cases);
}
}
}

View File

@@ -4,7 +4,14 @@
"package": "org.geysermc.packgenerator.mixin", "package": "org.geysermc.packgenerator.mixin",
"compatibilityLevel": "JAVA_21", "compatibilityLevel": "JAVA_21",
"mixins": [], "mixins": [],
"client": [], "client": [
"BlockModelWrapperMixin",
"BlockModelWrapperMixin$UnbakedMixin",
"ConditionalItemModelAccessor",
"SelectItemModelAccessor",
"SelectItemModelMixin",
"SelectItemModelMixin$UnbakedSwitchMixin"
],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1
} }