From 05b0202b6a728e0a89646e54bdd602b403c58ec4 Mon Sep 17 00:00:00 2001 From: evlad Date: Tue, 3 Feb 2026 23:24:24 +0300 Subject: [PATCH 1/5] feat: add merge strategies to prioritize first or second container on error --- .../overlay/ResourceContainerImpl.java | 77 +++++++++++++++++-- .../creative/resources/MergeStrategy.java | 29 +++++++ .../creative/resources/MergeStrategyImpl.java | 5 +- 3 files changed, 102 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java b/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java index 638b25c8..e44bc581 100644 --- a/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java +++ b/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java @@ -31,12 +31,15 @@ import team.unnamed.creative.atlas.AtlasSource; import team.unnamed.creative.base.Writable; import team.unnamed.creative.blockstate.BlockState; +import team.unnamed.creative.blockstate.MultiVariant; import team.unnamed.creative.equipment.Equipment; import team.unnamed.creative.equipment.EquipmentLayer; import team.unnamed.creative.equipment.EquipmentLayerType; import team.unnamed.creative.font.Font; import team.unnamed.creative.font.FontProvider; import team.unnamed.creative.item.Item; +import team.unnamed.creative.item.ItemModel; +import team.unnamed.creative.item.RangeDispatchItemModel; import team.unnamed.creative.lang.Language; import team.unnamed.creative.model.ItemOverride; import team.unnamed.creative.model.Model; @@ -50,6 +53,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -378,11 +382,12 @@ public boolean removeUnknownFile(final @NotNull String path) { @Override public void merge(final @NotNull ResourceContainer other, final @NotNull MergeStrategy strategy) { final boolean override = strategy == MergeStrategy.override(); + final boolean shouldOverride = override || strategy == MergeStrategy.mergeBothAndPrioritizeSecondOnError(); // merge atlases for (final Atlas atlas : other.atlases()) { final Atlas oldAtlas = atlases.get(atlas.key()); - if (oldAtlas == null || override) { + if (oldAtlas == null || shouldOverride) { atlases.put(atlas.key(), atlas); continue; } @@ -396,11 +401,34 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt // merge block states for (final BlockState blockState : other.blockStates()) { if (blockStates.containsKey(blockState.key())) { - if (strategy == MergeStrategy.override()) { + if (override) { blockStates.put(blockState.key(), blockState); } else if (strategy == MergeStrategy.mergeAndFailOnError()) { throw new MergeException("Duplicate block state '" + blockState.key() + "': exists in both resource containers"); + } else if (MergeStrategy.isMergeBoth(strategy)) { + var thisVariants = blockStates.get(blockState.key()).variants(); + var thisMultipart = blockStates.get(blockState.key()).multipart(); + var otherVariants = blockState.variants(); + var otherMultipart = blockState.multipart(); + + var finalVariants = new HashMap(); + if (strategy == MergeStrategy.mergeBothAndPrioritizeFirstOnError()) { + finalVariants.putAll(thisVariants); + finalVariants.putAll(otherVariants); + } else { + finalVariants.putAll(otherVariants); + finalVariants.putAll(thisVariants); + } + + var finalMultipart = new ArrayList<>(thisMultipart); + finalMultipart.addAll(otherMultipart); + + blockStates.put(blockState.key(), BlockState.of( + blockState.key(), + finalVariants, + finalMultipart + )); } } else { blockStates.put(blockState.key(), blockState); @@ -410,7 +438,7 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt // merge equipment for (final Equipment equipment : other.equipment()) { final Equipment oldEquipment = this.equipment.get(equipment.key()); - if (oldEquipment == null || override) { + if (oldEquipment == null || shouldOverride) { this.equipment.put(equipment.key(), equipment); continue; } @@ -435,7 +463,7 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt // merge fonts for (final Font font : other.fonts()) { final Font oldFont = fonts.get(font.key()); - if (oldFont == null || override) { + if (oldFont == null || shouldOverride) { fonts.put(font.key(), font); continue; } @@ -454,6 +482,37 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt } else if (strategy == MergeStrategy.mergeAndFailOnError()) { throw new MergeException("Duplicated item '" + item.key() + "': exists in both resource containers"); + } else if (MergeStrategy.isMergeBoth(strategy)) { + var thisModel = items.get(item.key()).model(); + var otherModel = item.model(); + var isFirstPriority = strategy == MergeStrategy.mergeBothAndPrioritizeFirstOnError(); + + ItemModel finalModel; + // TODO : Add merging for other Item models + if (thisModel instanceof RangeDispatchItemModel a && + otherModel instanceof RangeDispatchItemModel b) { + + var first = isFirstPriority ? a : b; + var second = isFirstPriority ? b : a; + + finalModel = ItemModel.rangeDispatch() + .addEntries(a.entries()) + .addEntries(b.entries()) + .scale(first.scale()) + .fallback(first.fallback() == null ? second.fallback() : first.fallback()) + .property(first.property()) + .build(); + } else { + finalModel = otherModel; + } + + items.put(item.key(), Item.item( + item.key(), + finalModel, + item.handAnimationOnSwap(), + item.oversizedInGui(), + item.swapAnimationScale() + )); } } else { items.put(item.key(), item); @@ -478,6 +537,7 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt + ". Exists in both resource containers." ); } + // TODO: Add merge both strategies } languages.put(language.key(), Language.language(language.key(), translations)); } @@ -485,7 +545,7 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt // merge models for (final Model model : other.models()) { final Model oldModel = models.get(model.key()); - if (oldModel == null || strategy == MergeStrategy.override()) { + if (oldModel == null || override) { models.put(model.key(), model); continue; } @@ -517,6 +577,7 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt throw new MergeException("Duplicated sound event '" + soundEvent + "': exists" + " in both resource-packs"); } + // TODO: Add merge both strategies } soundRegistries.put( @@ -531,7 +592,7 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt // merge sounds for (final Sound sound : other.sounds()) { if (sounds.containsKey(sound.key())) { - if (override) { + if (shouldOverride) { sounds.put(sound.key(), sound); } else if (strategy == MergeStrategy.mergeAndFailOnError()) { throw new MergeException("Duplicated sound '" + sound.key() @@ -546,7 +607,7 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt // todo: should we merge metadata? for (final Texture texture : other.textures()) { if (textures.containsKey(texture.key())) { - if (override) { + if (shouldOverride) { textures.put(texture.key(), texture); } else if (strategy == MergeStrategy.mergeAndFailOnError()) { throw new MergeException("Duplicated texture '" + texture.key() @@ -560,7 +621,7 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt // merge unknown files for (final Map.Entry entry : other.unknownFiles().entrySet()) { if (files.containsKey(entry.getKey())) { - if (override) { + if (shouldOverride) { files.put(entry.getKey(), entry.getValue()); } else if (strategy == MergeStrategy.mergeAndFailOnError()) { throw new MergeException("Duplicated unknown file: '" + entry.getKey() diff --git a/api/src/main/java/team/unnamed/creative/resources/MergeStrategy.java b/api/src/main/java/team/unnamed/creative/resources/MergeStrategy.java index ecac5fc0..2e951e2e 100644 --- a/api/src/main/java/team/unnamed/creative/resources/MergeStrategy.java +++ b/api/src/main/java/team/unnamed/creative/resources/MergeStrategy.java @@ -73,4 +73,33 @@ public interface MergeStrategy { static @NotNull MergeStrategy mergeAndKeepFirstOnError() { return MergeStrategyImpl.MERGE_AND_KEEP_FIRST_ON_ERROR; } + + /** + * Gets a merge strategy that merges the resources of the + * first container with the resources of the second container. + * This will try to merge both files and prioritize the first container. + * + * @return The merge both and prioritize first on error merge strategy + * @since 1.11.13 + */ + static @NotNull MergeStrategy mergeBothAndPrioritizeFirstOnError() { + return MergeStrategyImpl.MERGE_BOTH_AND_PRIORITIZE_FIRST_ON_ERROR; + } + + /** + * Gets a merge strategy that merges the resources of the + * first container with the resources of the second container. + * This will try to merge both files and prioritize the second container. + * It will act as an override if there are duplicates. + * + * @return The merge both and prioritize second on error merge strategy + * @since 1.11.13 + */ + static @NotNull MergeStrategy mergeBothAndPrioritizeSecondOnError() { + return MergeStrategyImpl.MERGE_BOTH_AND_PRIORITIZE_SECOND_ON_ERROR; + } + + static boolean isMergeBoth(MergeStrategy strategy) { + return strategy == MergeStrategyImpl.MERGE_BOTH_AND_PRIORITIZE_FIRST_ON_ERROR || strategy == MergeStrategyImpl.MERGE_BOTH_AND_PRIORITIZE_SECOND_ON_ERROR; + } } diff --git a/api/src/main/java/team/unnamed/creative/resources/MergeStrategyImpl.java b/api/src/main/java/team/unnamed/creative/resources/MergeStrategyImpl.java index 0de1592d..2648b30a 100644 --- a/api/src/main/java/team/unnamed/creative/resources/MergeStrategyImpl.java +++ b/api/src/main/java/team/unnamed/creative/resources/MergeStrategyImpl.java @@ -26,5 +26,8 @@ enum MergeStrategyImpl implements MergeStrategy { OVERRIDE, MERGE_AND_FAIL_ON_ERROR, - MERGE_AND_KEEP_FIRST_ON_ERROR + MERGE_AND_KEEP_FIRST_ON_ERROR, + MERGE_BOTH_AND_PRIORITIZE_FIRST_ON_ERROR, + MERGE_BOTH_AND_PRIORITIZE_SECOND_ON_ERROR, + ; } From 9dda6af4f21d8edbf8e0253a2ecc32e1128b86f0 Mon Sep 17 00:00:00 2001 From: evlad Date: Wed, 4 Feb 2026 00:11:00 +0300 Subject: [PATCH 2/5] fix: font merge with newly added strategies --- .../team/unnamed/creative/overlay/ResourceContainerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java b/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java index e44bc581..8b279004 100644 --- a/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java +++ b/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java @@ -463,7 +463,7 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt // merge fonts for (final Font font : other.fonts()) { final Font oldFont = fonts.get(font.key()); - if (oldFont == null || shouldOverride) { + if (oldFont == null || override) { fonts.put(font.key(), font); continue; } From 533aed018ef0b81718a5f4918135e15299537f8e Mon Sep 17 00:00:00 2001 From: evlad Date: Wed, 4 Feb 2026 00:13:43 +0300 Subject: [PATCH 3/5] fix: atlas merge with newly added strategies --- .../team/unnamed/creative/overlay/ResourceContainerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java b/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java index 8b279004..2c9fbea2 100644 --- a/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java +++ b/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java @@ -387,7 +387,7 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt // merge atlases for (final Atlas atlas : other.atlases()) { final Atlas oldAtlas = atlases.get(atlas.key()); - if (oldAtlas == null || shouldOverride) { + if (oldAtlas == null || override) { atlases.put(atlas.key(), atlas); continue; } From a24f837e30e8a55ee02209f28af31d2849a4fef8 Mon Sep 17 00:00:00 2001 From: evlad Date: Sun, 8 Feb 2026 01:18:18 +0300 Subject: [PATCH 4/5] fix: merge items with prioritization support on top-level fields --- .../overlay/ResourceContainerImpl.java | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java b/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java index 2c9fbea2..459bb25b 100644 --- a/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java +++ b/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java @@ -476,46 +476,49 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt // merge items for (final Item item : other.items()) { - if (items.containsKey(item.key())) { - if (override) { - items.put(item.key(), item); - } else if (strategy == MergeStrategy.mergeAndFailOnError()) { - throw new MergeException("Duplicated item '" + item.key() - + "': exists in both resource containers"); - } else if (MergeStrategy.isMergeBoth(strategy)) { - var thisModel = items.get(item.key()).model(); - var otherModel = item.model(); - var isFirstPriority = strategy == MergeStrategy.mergeBothAndPrioritizeFirstOnError(); - - ItemModel finalModel; - // TODO : Add merging for other Item models - if (thisModel instanceof RangeDispatchItemModel a && - otherModel instanceof RangeDispatchItemModel b) { - - var first = isFirstPriority ? a : b; - var second = isFirstPriority ? b : a; - - finalModel = ItemModel.rangeDispatch() - .addEntries(a.entries()) - .addEntries(b.entries()) - .scale(first.scale()) - .fallback(first.fallback() == null ? second.fallback() : first.fallback()) - .property(first.property()) - .build(); - } else { - finalModel = otherModel; - } + if (!items.containsKey(item.key())) { + items.put(item.key(), item); + continue; + } - items.put(item.key(), Item.item( - item.key(), - finalModel, - item.handAnimationOnSwap(), - item.oversizedInGui(), - item.swapAnimationScale() - )); - } - } else { + var prevItem = items.get(item.key()); + + if (override) { items.put(item.key(), item); + } else if (strategy == MergeStrategy.mergeAndFailOnError()) { + throw new MergeException("Duplicated item '" + item.key() + + "': exists in both resource containers"); + } else if (MergeStrategy.isMergeBoth(strategy)) { + var thisModel = prevItem.model(); + var otherModel = item.model(); + var isFirstPriority = strategy == MergeStrategy.mergeBothAndPrioritizeFirstOnError(); + + ItemModel finalModel; + // TODO : Add merging for other Item models + if (thisModel instanceof RangeDispatchItemModel a && + otherModel instanceof RangeDispatchItemModel b) { + + var first = isFirstPriority ? a : b; + var second = isFirstPriority ? b : a; + + finalModel = ItemModel.rangeDispatch() + .addEntries(a.entries()) + .addEntries(b.entries()) + .scale(first.scale()) + .fallback(first.fallback() == null ? second.fallback() : first.fallback()) + .property(first.property()) + .build(); + } else { + finalModel = otherModel; + } + + items.put(item.key(), Item.item( + item.key(), + finalModel, + isFirstPriority ? prevItem.handAnimationOnSwap() : item.handAnimationOnSwap(), + isFirstPriority ? prevItem.oversizedInGui() : item.oversizedInGui(), + isFirstPriority ? prevItem.swapAnimationScale() : item.swapAnimationScale() + )); } } From fe9b7f6b3a1fa3f1e36b0b67afabaf61a69caec6 Mon Sep 17 00:00:00 2001 From: evlad Date: Sun, 8 Feb 2026 06:35:44 +0300 Subject: [PATCH 5/5] fix: enhance merge logic to avoid duplicate entries based on threshold --- .../creative/overlay/ResourceContainerImpl.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java b/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java index 459bb25b..1440fe9d 100644 --- a/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java +++ b/api/src/main/java/team/unnamed/creative/overlay/ResourceContainerImpl.java @@ -501,9 +501,19 @@ public void merge(final @NotNull ResourceContainer other, final @NotNull MergeSt var first = isFirstPriority ? a : b; var second = isFirstPriority ? b : a; + var entries = new ArrayList<>(first.entries()); + for (var entry : second.entries()) { + var exists = entries.stream().anyMatch(b2 -> { + return entry.threshold() == b2.threshold(); + }); + + if (!exists) { + entries.add(entry); + } + } + finalModel = ItemModel.rangeDispatch() - .addEntries(a.entries()) - .addEntries(b.entries()) + .addEntries(entries) .scale(first.scale()) .fallback(first.fallback() == null ? second.fallback() : first.fallback()) .property(first.property())