From e43e2250d5005d8bd3e86c5079651437ec9611d3 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 3 Jul 2025 08:32:59 +0000 Subject: [PATCH] Use group definitions when generating mappings --- .../mapping/geyser/GeyserGroupDefinition.java | 21 ++++++++++ .../mapping/geyser/GeyserItemMapper.java | 2 +- .../mapping/geyser/GeyserMapping.java | 10 ++++- .../mapping/geyser/GeyserMappings.java | 39 ++++++++++++++----- .../geyser/GeyserSingleDefinition.java | 14 +++++-- 5 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserGroupDefinition.java b/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserGroupDefinition.java index e1582e6..00b1f5d 100644 --- a/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserGroupDefinition.java +++ b/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserGroupDefinition.java @@ -6,6 +6,7 @@ import net.minecraft.resources.ResourceLocation; import java.util.List; import java.util.Optional; +import java.util.stream.Stream; public record GeyserGroupDefinition(Optional model, List definitions) implements GeyserMapping { @@ -16,6 +17,26 @@ public record GeyserGroupDefinition(Optional model, List parentModel, GeyserSingleDefinition other) { + Optional thisModel = model.or(() -> parentModel); + for (GeyserMapping definition : definitions) { + if (definition instanceof GeyserGroupDefinition group && group.conflictsWith(thisModel, other)) { + return true; + } else if (definition instanceof GeyserSingleDefinition single && single.conflictsWith(thisModel, other)) { + return true; + } + } + return false; + } + @Override public Type type() { return Type.GROUP; diff --git a/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserItemMapper.java b/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserItemMapper.java index 03fb25a..fe458b6 100644 --- a/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserItemMapper.java +++ b/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserItemMapper.java @@ -128,7 +128,7 @@ public class GeyserItemMapper { } public GeyserSingleDefinition create(ResourceLocation bedrockIdentifier) { - return new GeyserSingleDefinition(model, bedrockIdentifier, Optional.of(displayName), predicateStack, + return new GeyserSingleDefinition(Optional.of(model), bedrockIdentifier, Optional.of(displayName), predicateStack, new GeyserSingleDefinition.BedrockOptions(Optional.empty(), true, false, protectionValue), // TODO handheld prediction componentPatch); } diff --git a/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserMapping.java b/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserMapping.java index 9db2b1e..902d081 100644 --- a/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserMapping.java +++ b/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserMapping.java @@ -1,13 +1,21 @@ package org.geysermc.packgenerator.mapping.geyser; import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; import com.mojang.serialization.MapCodec; import net.minecraft.util.StringRepresentable; import org.jetbrains.annotations.NotNull; public interface GeyserMapping { - Codec CODEC = Type.CODEC.dispatch(GeyserMapping::type, Type::codec); + Codec CODEC = Codec.lazyInitialized(() -> Type.CODEC.dispatch(GeyserMapping::type, Type::codec)); + // Not perfect since we're not checking single definitions in groups without a model... but good enough + Codec MODEL_SAFE_CODEC = CODEC.validate(mapping -> { + if (mapping instanceof GeyserSingleDefinition single && single.model().isEmpty()) { + return DataResult.error(() -> "Top level single definition must have a model"); + } + return DataResult.success(mapping); + }); Type type(); diff --git a/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserMappings.java b/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserMappings.java index 44343da..54c8f4b 100644 --- a/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserMappings.java +++ b/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserMappings.java @@ -14,14 +14,14 @@ import org.geysermc.packgenerator.CodecUtil; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; -// TODO group definitions public class GeyserMappings { - private static final Codec, Collection>> MAPPINGS_CODEC = Codec.unboundedMap(Item.CODEC, GeyserSingleDefinition.CODEC.listOf().xmap(Function.identity(), ArrayList::new)); + private static final Codec, Collection>> MAPPINGS_CODEC = Codec.unboundedMap(Item.CODEC, GeyserMapping.MODEL_SAFE_CODEC.listOf().xmap(Function.identity(), ArrayList::new)); public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( @@ -30,23 +30,44 @@ public class GeyserMappings { ).apply(instance, (format, mappings) -> new GeyserMappings(mappings)) ); - private final Multimap, GeyserSingleDefinition> mappings = MultimapBuilder.hashKeys().hashSetValues().build(); + private final Multimap, GeyserMapping> mappings = MultimapBuilder.hashKeys().hashSetValues().build(); public GeyserMappings() {} - private GeyserMappings(Map, Collection> mappings) { + private GeyserMappings(Map, Collection> mappings) { for (Holder item : mappings.keySet()) { this.mappings.putAll(item, mappings.get(item)); } } public void map(Holder item, GeyserSingleDefinition mapping) { - for (GeyserSingleDefinition existing : mappings.get(item)) { - if (existing.conflictsWith(mapping)) { - throw new IllegalArgumentException("Mapping conflicts with existing mapping"); + ResourceLocation model = mapping.model().orElseThrow(); + Optional modelGroup = Optional.empty(); + + Collection existingMappings = new ArrayList<>(mappings.get(item)); + for (GeyserMapping existing : existingMappings) { + if (existing instanceof GeyserGroupDefinition existingGroup && existingGroup.isFor(model)) { + if (existingGroup.conflictsWith(Optional.empty(), mapping)) { + throw new IllegalArgumentException("Mapping conflicts with existing group mapping"); + } + modelGroup = Optional.of(existingGroup); + break; + } else if (existing instanceof GeyserSingleDefinition single) { + if (single.conflictsWith(Optional.empty(), mapping)) { + throw new IllegalArgumentException("Mapping conflicts with existing single mapping"); + } else if (model.equals(single.model().orElseThrow())) { + mappings.remove(item, single); + modelGroup = Optional.of(new GeyserGroupDefinition(Optional.of(model), List.of(single.withoutModel()))); + } } } - mappings.put(item, mapping); + + if (modelGroup.isPresent()) { + mappings.remove(item, modelGroup.get()); + mappings.put(item, modelGroup.get().with(mapping.withoutModel())); + } else { + mappings.put(item, mapping); + } } public int size() { @@ -76,7 +97,7 @@ public class GeyserMappings { }); } - public Map, Collection> mappings() { + public Map, Collection> mappings() { return mappings.asMap(); } } diff --git a/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserSingleDefinition.java b/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserSingleDefinition.java index 6932829..eec98de 100644 --- a/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserSingleDefinition.java +++ b/src/main/java/org/geysermc/packgenerator/mapping/geyser/GeyserSingleDefinition.java @@ -15,7 +15,7 @@ import java.util.function.Function; // TODO other keys, etc. // TODO sometimes still includes components key when patch before filtering is not empty but after is -public record GeyserSingleDefinition(ResourceLocation model, ResourceLocation bedrockIdentifier, Optional displayName, +public record GeyserSingleDefinition(Optional model, ResourceLocation bedrockIdentifier, Optional displayName, List predicates, BedrockOptions bedrockOptions, DataComponentPatch components) implements GeyserMapping { private static final List> SUPPORTED_COMPONENTS = List.of(DataComponents.CONSUMABLE, DataComponents.EQUIPPABLE, DataComponents.FOOD, DataComponents.MAX_DAMAGE, DataComponents.MAX_STACK_SIZE, DataComponents.USE_COOLDOWN, DataComponents.ENCHANTABLE, DataComponents.ENCHANTMENT_GLINT_OVERRIDE); @@ -36,7 +36,7 @@ public record GeyserSingleDefinition(ResourceLocation model, ResourceLocation be public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( - ResourceLocation.CODEC.fieldOf("model").forGetter(GeyserSingleDefinition::model), + ResourceLocation.CODEC.optionalFieldOf("model").forGetter(GeyserSingleDefinition::model), ResourceLocation.CODEC.fieldOf("bedrock_identifier").forGetter(GeyserSingleDefinition::bedrockIdentifier), Codec.STRING.optionalFieldOf("display_name").forGetter(GeyserSingleDefinition::displayName), GeyserPredicate.LIST_CODEC.optionalFieldOf("predicate", List.of()).forGetter(GeyserSingleDefinition::predicates), @@ -49,8 +49,10 @@ public record GeyserSingleDefinition(ResourceLocation model, ResourceLocation be return bedrockOptions.icon.orElse(iconFromResourceLocation(bedrockIdentifier)); } - public boolean conflictsWith(GeyserSingleDefinition other) { - if (!model.equals(other.model)) { + public boolean conflictsWith(Optional parentModel, GeyserSingleDefinition other) { + ResourceLocation thisModel = model.or(() -> parentModel).orElseThrow(); + ResourceLocation otherModel = other.model.or(() -> parentModel).orElseThrow(); + if (!thisModel.equals(otherModel)) { return false; } else if (predicates.size() == other.predicates.size()) { boolean predicatesAreEqual = true; @@ -65,6 +67,10 @@ public record GeyserSingleDefinition(ResourceLocation model, ResourceLocation be return false; } + public GeyserSingleDefinition withoutModel() { + return new GeyserSingleDefinition(Optional.empty(), bedrockIdentifier, displayName, predicates, bedrockOptions, components); + } + @Override public Type type() { return Type.SINGLE;