From 35fa58fec9cfaee4d0636c2b78c83c37b7eaee16 Mon Sep 17 00:00:00 2001 From: Leonard Joensen Date: Mon, 28 Jul 2025 07:53:41 +0200 Subject: [PATCH 1/6] Block incorrect map bundle placements --- src/main/java/space/essem/image2map/Image2Map.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/space/essem/image2map/Image2Map.java b/src/main/java/space/essem/image2map/Image2Map.java index fc2f88d..f0ab437 100644 --- a/src/main/java/space/essem/image2map/Image2Map.java +++ b/src/main/java/space/essem/image2map/Image2Map.java @@ -431,6 +431,17 @@ public static boolean clickItemFrame(PlayerEntity player, Hand hand, ItemFrameEn var entities = world.getEntitiesByClass(ItemFrameEntity.class, Box.from(Vec3d.of(mut)), (entity1) -> entity1.getHorizontalFacing() == facing && entity1.getBlockPos().equals(mut)); if (!entities.isEmpty()) { frames[x + y * width] = entities.get(0); + } else { + player.sendMessage( + Text.literal( + String.format( + "Item frame wall is not large enough, expected %dx%d or larger", + width, height + ) + ), + false + ); + return true; } } } From 64c1d32e4a127ec542e21eff44d9ab3e1f70dc19 Mon Sep 17 00:00:00 2001 From: Leonard Joensen Date: Mon, 28 Jul 2025 09:05:50 +0200 Subject: [PATCH 2/6] Drop maps when map wall is destroyed --- .../java/space/essem/image2map/Image2Map.java | 94 ++++++++++++++++--- .../image2map/mixin/ItemFrameEntityMixin.java | 4 +- 2 files changed, 82 insertions(+), 16 deletions(-) diff --git a/src/main/java/space/essem/image2map/Image2Map.java b/src/main/java/space/essem/image2map/Image2Map.java index f0ab437..9b9f230 100644 --- a/src/main/java/space/essem/image2map/Image2Map.java +++ b/src/main/java/space/essem/image2map/Image2Map.java @@ -20,26 +20,22 @@ import net.minecraft.component.type.LoreComponent; import net.minecraft.component.type.NbtComponent; import net.minecraft.entity.Entity; -import net.minecraft.entity.data.DataTracker; import net.minecraft.entity.decoration.ItemFrameEntity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.BundleItem; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; -import net.minecraft.nbt.*; +import net.minecraft.nbt.NbtOps; import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.world.ServerWorld; import net.minecraft.text.HoverEvent; import net.minecraft.text.Style; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Hand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockPos.Mutable; import net.minecraft.util.math.Box; import net.minecraft.util.math.Direction; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import space.essem.image2map.config.Image2MapConfig; @@ -48,11 +44,9 @@ import javax.imageio.ImageIO; import java.awt.image.BufferedImage; -import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URL; -import java.net.URLConnection; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -62,9 +56,9 @@ import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.time.Duration; -import java.time.temporal.TemporalUnit; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -472,7 +466,37 @@ public static boolean clickItemFrame(PlayerEntity player, Hand hand, ItemFrameEn return false; } - public static boolean destroyItemFrame(Entity player, ItemFrameEntity itemFrameEntity) { + private static @Nullable ImageData getImageData(ItemStack item) { + var tag = item.get(DataComponentTypes.CUSTOM_DATA); + if (tag == null) return null; + var codec = tag.get(ImageData.CODEC); + return codec.isSuccess() ? codec.getOrThrow() : null; + } + + private static @Nullable String getUrlFromLore(ItemStack stack) { + if (getImageData(stack) == null) { + return null; + } + + var lore = stack.get(DataComponentTypes.LORE); + if (lore == null || lore.lines().isEmpty()) { + return null; + } + + // This could be simplified to lore.lines().getLast().getString() currently, + // however doing it this way ensures future compatibility with any + // added lore elements. + for (var line : lore.lines()) { + var lineString = line.getString(); + if (lineString.startsWith("http://") || lineString.startsWith("https://")) { + return lineString; + } + } + + return null; + } + + public static boolean destroyItemFrame(ServerWorld serverWorld, Entity player, ItemFrameEntity itemFrameEntity) { var stack = itemFrameEntity.getHeldItemStack(); var tag = stack.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT).get(ImageData.CODEC); @@ -489,6 +513,7 @@ public static boolean destroyItemFrame(Entity player, ItemFrameEntity itemFrameE Direction facing = tag.getOrThrow().facing().get(); var world = itemFrameEntity.getWorld(); + var itemFramePosition = itemFrameEntity.getBlockPos(); var start = itemFrameEntity.getBlockPos(); var mut = start.mutableCopy(); @@ -498,22 +523,33 @@ public static boolean destroyItemFrame(Entity player, ItemFrameEntity itemFrameE start = mut.toImmutable(); - for (var x = 0; x < width; x++) { - for (var y = 0; y < height; y++) { + ArrayList frameItems = new ArrayList<>(); + + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { mut.set(start); mut.move(right, x); mut.move(down, y); var entities = world.getEntitiesByClass(ItemFrameEntity.class, Box.from(Vec3d.of(mut)), (entity1) -> entity1.getHorizontalFacing() == facing && entity1.getBlockPos().equals(mut)); + + // Fix for the item frame technically not existing in the world + // after the block holding it has been destroyed + ItemFrameEntity frame = null; if (!entities.isEmpty()) { - var frame = entities.get(0); + frame = entities.getFirst(); + } else if (itemFramePosition.equals(mut)) { + frame = itemFrameEntity; + } + if (frame != null) { // Only apply to frames that contain an image2map map var frameStack = frame.getHeldItemStack(); tag = frameStack.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT).get(ImageData.CODEC); if (frameStack.getItem() == Items.FILLED_MAP && tag.isSuccess() && tag.getOrThrow().right().isPresent() && tag.getOrThrow().down().isPresent() && tag.getOrThrow().facing().isPresent()) { + frameItems.add(frameStack); frame.setHeldItemStack(ItemStack.EMPTY, true); frame.setInvisible(false); } @@ -521,6 +557,38 @@ public static boolean destroyItemFrame(Entity player, ItemFrameEntity itemFrameE } } + if (!frameItems.isEmpty()) { + String url = getUrlFromLore(frameItems.getFirst()); + if (url == null) { + url = "unknown"; + } + + // Clear the right/down/facing data from the items, + // so they don't get batch removed if placed individually later. + for (ItemStack item : frameItems) { + var customData = item.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT).get(ImageData.CODEC).getOrThrow(); + item.set( + DataComponentTypes.CUSTOM_DATA, + NbtComponent.DEFAULT.with( + NbtOps.INSTANCE, + ImageData.CODEC, + new ImageData( + customData.x(), + customData.y(), + customData.width(), + customData.height(), + customData.quickPlace(), + Optional.empty(), + Optional.empty(), + Optional.empty() + ) + ).getOrThrow() + ); + } + + itemFrameEntity.dropStack(serverWorld, toSingleStack(frameItems, url, width * 128, height * 128)); + } + return true; } diff --git a/src/main/java/space/essem/image2map/mixin/ItemFrameEntityMixin.java b/src/main/java/space/essem/image2map/mixin/ItemFrameEntityMixin.java index 7935d88..567d4dd 100644 --- a/src/main/java/space/essem/image2map/mixin/ItemFrameEntityMixin.java +++ b/src/main/java/space/essem/image2map/mixin/ItemFrameEntityMixin.java @@ -8,8 +8,6 @@ import net.minecraft.server.world.ServerWorld; import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; - -import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -34,7 +32,7 @@ public class ItemFrameEntityMixin { private void image2map$destroyMaps(ServerWorld world, Entity entity, boolean dropSelf, CallbackInfo ci) { var frame = (ItemFrameEntity) (Object) this; - if (!this.fixed && Image2Map.destroyItemFrame(entity, frame)) { + if (!this.fixed && Image2Map.destroyItemFrame(world, entity, frame)) { if (dropSelf) { frame.dropStack(world, new ItemStack(Items.ITEM_FRAME)); } From 294bb29ef586863c7872b68d4149285cbd91ae07 Mon Sep 17 00:00:00 2001 From: Leonard Joensen Date: Mon, 28 Jul 2025 09:30:35 +0200 Subject: [PATCH 3/6] Allow adding and removing maps to image2map bundles --- .../java/space/essem/image2map/Image2Map.java | 63 +++++++++ .../image2map/mixin/BundleItemMixin.java | 131 ++++++++++++++---- 2 files changed, 165 insertions(+), 29 deletions(-) diff --git a/src/main/java/space/essem/image2map/Image2Map.java b/src/main/java/space/essem/image2map/Image2Map.java index 9b9f230..164f13f 100644 --- a/src/main/java/space/essem/image2map/Image2Map.java +++ b/src/main/java/space/essem/image2map/Image2Map.java @@ -595,6 +595,69 @@ public static boolean destroyItemFrame(ServerWorld serverWorld, Entity player, I return false; } + public static void destroyBundleOnEmpty(ItemStack bundle) { + if (getImageData(bundle) == null) { + return; + } + + var contents = bundle.get(DataComponentTypes.BUNDLE_CONTENTS); + if (contents == null || contents.isEmpty()) { + bundle.decrement(1); + } + } + + public static boolean isInvalidMapForBundle(ItemStack bundle, ItemStack item) { + if (item.isOf(Items.AIR)) { + return false; + } + + var bundleData = getImageData(bundle); + + // Allow insert if bundle isn't an image2map bundle + if (bundleData == null) { + return false; + } + + // Block insert if item isn't a map + if (!item.isOf(Items.FILLED_MAP)) { + return true; + } + + var mapData = getImageData(item); + + // Block insert if map isn't an image2map map + if (mapData == null) { + return true; + } + + var bundleUrl = getUrlFromLore(bundle); + var mapUrl = getUrlFromLore(item); + + // Block insert if there's either no URL for either of the items, or they don't match + if (bundleUrl == null || !bundleUrl.equals(mapUrl)) { + return true; + } + + var bundleMaps = bundle.get(DataComponentTypes.BUNDLE_CONTENTS); + + // Potential edge case for empty image2map bundle? Best to check either way. + // Allow insert if bundle is empty. + if (bundleMaps == null) { + return false; + } + + // Block insert if the bundle already contains a map with the same tiling coordinates. + for (var map : bundleMaps.iterate()) { + var data = getImageData(map); + if (data == null) continue; + if (data.x() == mapData.x() && data.y() == mapData.y()) { + return true; + } + } + + return false; + } + private static boolean isValid(String url) { try { new URL(url).toURI(); diff --git a/src/main/java/space/essem/image2map/mixin/BundleItemMixin.java b/src/main/java/space/essem/image2map/mixin/BundleItemMixin.java index 25969d0..d802e15 100644 --- a/src/main/java/space/essem/image2map/mixin/BundleItemMixin.java +++ b/src/main/java/space/essem/image2map/mixin/BundleItemMixin.java @@ -1,10 +1,9 @@ package space.essem.image2map.mixin; -import net.minecraft.component.DataComponentTypes; -import net.minecraft.util.ActionResult; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import net.minecraft.entity.player.PlayerEntity; @@ -13,44 +12,118 @@ import net.minecraft.item.ItemStack; import net.minecraft.screen.slot.Slot; import net.minecraft.util.ClickType; -import net.minecraft.util.Hand; import net.minecraft.world.World; +import space.essem.image2map.Image2Map; @Mixin(BundleItem.class) public class BundleItemMixin { + /** + * When holding a bundle in your hand in-world, + * and right-clicking to drop an item, destroy the bundle if it ends up empty. + * Overridden if the player is in creative mode. + */ + @Inject(method = "dropContentsOnUse", at = @At("RETURN")) + private void image2map$dropContentsOnUse(World world, PlayerEntity player, ItemStack stack, CallbackInfo ci) { + if (player.isCreative()) { + return; + } - @Inject(method = "use", at = @At("HEAD"), cancellable = true) - private void image2map$useBundle(World world, PlayerEntity user, Hand hand, - CallbackInfoReturnable cir) { - ItemStack itemStack = user.getStackInHand(hand); - var tag = itemStack.get(DataComponentTypes.CUSTOM_DATA); + Image2Map.destroyBundleOnEmpty(stack); + } + + /** + * When holding a bundle with the mouse in the inventory, + * and left-clicking a slot with another item, only place + * the item into the bundle if it's a valid image2map map for the bundle. + * Overridden if the player is in creative mode. + */ + @Inject(method = "onStackClicked", at = @At("HEAD"), cancellable = true) + private void image2map$onStackClickedHead(ItemStack bundle, Slot slot, ClickType clickType, PlayerEntity player, CallbackInfoReturnable cir) { + if (player.isCreative()) { + return; + } + + if (clickType != ClickType.LEFT) { + return; + } - if (tag != null && tag.contains("image2map:quick_place") && !user.isCreative()) { - cir.setReturnValue(ActionResult.FAIL); - cir.cancel(); + if (Image2Map.isInvalidMapForBundle(bundle, slot.getStack())) { + cir.setReturnValue(false); + } } - } - @Inject(method = "onStackClicked", at = @At("HEAD"), cancellable = true) - private void image2map$addBundleItems(ItemStack bundle, Slot slot, ClickType clickType, PlayerEntity player, - CallbackInfoReturnable cir) { - var tag = bundle.get(DataComponentTypes.CUSTOM_DATA); + /** + * When holding a bundle with the mouse in the inventory, + * and right-clicking a slot with no item, + * destroy the bundle if it ends up empty. + * Overridden if the player is in creative mode. + */ + @Inject(method = "onStackClicked", at = @At("RETURN")) + private void image2map$onStackClickedReturn(ItemStack bundle, Slot slot, ClickType clickType, PlayerEntity player, CallbackInfoReturnable cir) { + if (player.isCreative()) { + return; + } - if (tag != null && tag.contains("image2map:quick_place") && !player.isCreative()) { - cir.setReturnValue(false); - cir.cancel(); + if (clickType != ClickType.RIGHT) { + return; + } + + Image2Map.destroyBundleOnEmpty(bundle); } - } - @Inject(method = "onClicked", at = @At("HEAD"), cancellable = true) - private void image2map$removeBundleItems(ItemStack bundle, ItemStack otherStack, Slot slot, ClickType clickType, - PlayerEntity player, StackReference cursorStackReference, - CallbackInfoReturnable cir) { - var tag = bundle.get(DataComponentTypes.CUSTOM_DATA); + /** + * When holding an item with the mouse in the inventory, + * and left-clicking a slot with a bundle, only place + * the item into the bundle if it's a valid image2map map for the bundle. + * Overridden if the player is in creative mode. + */ + @Inject(method = "onClicked", at = @At("HEAD"), cancellable = true) + private void image2map$onClickedHead( + ItemStack bundle, + ItemStack otherStack, + Slot slot, + ClickType clickType, + PlayerEntity player, + StackReference cursorStackReference, + CallbackInfoReturnable cir + ) { + if (player.isCreative()) { + return; + } + + if (clickType != ClickType.LEFT) { + return; + } + + if (Image2Map.isInvalidMapForBundle(bundle, otherStack)) { + cir.setReturnValue(false); + } + } + + /** + * When holding no item with the mouse in the inventory, + * and right-clicking a slot with a bundle, + * destroy the bundle if it ends up empty. + * Overridden if the player is in creative mode. + */ + @Inject(method = "onClicked", at = @At("RETURN")) + private void image2map$onClickedReturn( + ItemStack bundle, + ItemStack otherStack, + Slot slot, + ClickType clickType, + PlayerEntity player, + StackReference cursorStackReference, + CallbackInfoReturnable cir + ) { + if (player.isCreative()) { + return; + } + + if (clickType != ClickType.RIGHT) { + return; + } - if (tag != null && tag.contains("image2map:quick_place") && !player.isCreative()) { - cir.setReturnValue(false); - cir.cancel(); + Image2Map.destroyBundleOnEmpty(bundle); } - } } From 8ca4e9d26dcac277ee3a496ef64738eb721ec353 Mon Sep 17 00:00:00 2001 From: Leonard Joensen Date: Mon, 28 Jul 2025 14:52:23 +0200 Subject: [PATCH 4/6] Account for create-folder when getting input from lore --- .../java/space/essem/image2map/Image2Map.java | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/main/java/space/essem/image2map/Image2Map.java b/src/main/java/space/essem/image2map/Image2Map.java index 164f13f..c9c0f63 100644 --- a/src/main/java/space/essem/image2map/Image2Map.java +++ b/src/main/java/space/essem/image2map/Image2Map.java @@ -473,7 +473,7 @@ public static boolean clickItemFrame(PlayerEntity player, Hand hand, ItemFrameEn return codec.isSuccess() ? codec.getOrThrow() : null; } - private static @Nullable String getUrlFromLore(ItemStack stack) { + private static @Nullable String getInputPathFromLore(ItemStack stack) { if (getImageData(stack) == null) { return null; } @@ -483,17 +483,7 @@ public static boolean clickItemFrame(PlayerEntity player, Hand hand, ItemFrameEn return null; } - // This could be simplified to lore.lines().getLast().getString() currently, - // however doing it this way ensures future compatibility with any - // added lore elements. - for (var line : lore.lines()) { - var lineString = line.getString(); - if (lineString.startsWith("http://") || lineString.startsWith("https://")) { - return lineString; - } - } - - return null; + return lore.lines().getLast().getString(); } public static boolean destroyItemFrame(ServerWorld serverWorld, Entity player, ItemFrameEntity itemFrameEntity) { @@ -558,9 +548,9 @@ public static boolean destroyItemFrame(ServerWorld serverWorld, Entity player, I } if (!frameItems.isEmpty()) { - String url = getUrlFromLore(frameItems.getFirst()); - if (url == null) { - url = "unknown"; + String inputPath = getInputPathFromLore(frameItems.getFirst()); + if (inputPath == null) { + inputPath = "unknown"; } // Clear the right/down/facing data from the items, @@ -586,7 +576,7 @@ public static boolean destroyItemFrame(ServerWorld serverWorld, Entity player, I ); } - itemFrameEntity.dropStack(serverWorld, toSingleStack(frameItems, url, width * 128, height * 128)); + itemFrameEntity.dropStack(serverWorld, toSingleStack(frameItems, inputPath, width * 128, height * 128)); } return true; @@ -630,11 +620,11 @@ public static boolean isInvalidMapForBundle(ItemStack bundle, ItemStack item) { return true; } - var bundleUrl = getUrlFromLore(bundle); - var mapUrl = getUrlFromLore(item); + var bundleInputPath = getInputPathFromLore(bundle); + var mapInputPath = getInputPathFromLore(item); - // Block insert if there's either no URL for either of the items, or they don't match - if (bundleUrl == null || !bundleUrl.equals(mapUrl)) { + // Block insert if there's either no input for either of the items, or they don't match + if (bundleInputPath == null || !bundleInputPath.equals(mapInputPath)) { return true; } From 9d1ed24317dd9b4c62b20b937993cfd581637c33 Mon Sep 17 00:00:00 2001 From: Leonard Joensen Date: Wed, 13 May 2026 23:27:45 +0200 Subject: [PATCH 5/6] Update to 1.21.11 --- .../java/space/essem/image2map/Image2Map.java | 70 ++++++++++--------- .../image2map/mixin/BundleItemMixin.java | 58 +++++++-------- .../essem/image2map/mixin/ItemFrameMixin.java | 2 +- 3 files changed, 68 insertions(+), 62 deletions(-) diff --git a/src/main/java/space/essem/image2map/Image2Map.java b/src/main/java/space/essem/image2map/Image2Map.java index 723d6ad..a0e5ffd 100644 --- a/src/main/java/space/essem/image2map/Image2Map.java +++ b/src/main/java/space/essem/image2map/Image2Map.java @@ -23,6 +23,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.HoverEvent; import net.minecraft.network.chat.Style; +import net.minecraft.server.level.ServerLevel; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.Entity; @@ -60,6 +61,7 @@ import java.time.temporal.TemporalUnit; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -456,8 +458,8 @@ public static boolean clickItemFrame(Player player, InteractionHand hand, ItemFr if (!entities.isEmpty()) { frames[x + y * width] = entities.get(0); } else { - player.sendMessage( - Text.literal( + player.displayClientMessage( + Component.literal( String.format( "Item frame wall is not large enough, expected %dx%d or larger", width, height @@ -497,10 +499,10 @@ public static boolean clickItemFrame(Player player, InteractionHand hand, ItemFr } private static @Nullable ImageData getImageData(ItemStack item) { - var tag = item.get(DataComponentTypes.CUSTOM_DATA); + var tag = item.get(DataComponents.CUSTOM_DATA); if (tag == null) return null; - var codec = tag.get(ImageData.CODEC); - return codec.isSuccess() ? codec.getOrThrow() : null; + var codec = tag.copyTag().read(ImageData.CODEC); + return codec.orElse(null); } private static @Nullable String getInputPathFromLore(ItemStack stack) { @@ -508,7 +510,7 @@ public static boolean clickItemFrame(Player player, InteractionHand hand, ItemFr return null; } - var lore = stack.get(DataComponentTypes.LORE); + var lore = stack.get(DataComponents.LORE); if (lore == null || lore.lines().isEmpty()) { return null; } @@ -516,7 +518,7 @@ public static boolean clickItemFrame(Player player, InteractionHand hand, ItemFr return lore.lines().getLast().getString(); } - public static boolean destroyItemFrame(Entity player, ItemFrame itemFrameEntity) { + public static boolean destroyItemFrame(ServerLevel serverLevel, Entity player, ItemFrame itemFrameEntity) { var stack = itemFrameEntity.getItem(); var tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag().read(ImageData.CODEC); @@ -533,7 +535,7 @@ public static boolean destroyItemFrame(Entity player, ItemFrame itemFrameEntity) Direction facing = tag.orElseThrow().facing().get(); var world = itemFrameEntity.level(); - var itemFramePosition = itemFrameEntity.getBlockPos(); + var itemFramePosition = itemFrameEntity.blockPosition(); var start = itemFrameEntity.blockPosition(); var mut = start.mutable(); @@ -555,7 +557,7 @@ public static boolean destroyItemFrame(Entity player, ItemFrame itemFrameEntity) // Fix for the item frame technically not existing in the world // after the block holding it has been destroyed - ItemFrameEntity frame = null; + ItemFrame frame = null; if (!entities.isEmpty()) { frame = entities.getFirst(); } else if (itemFramePosition.equals(mut)) { @@ -586,27 +588,31 @@ public static boolean destroyItemFrame(Entity player, ItemFrame itemFrameEntity) // Clear the right/down/facing data from the items, // so they don't get batch removed if placed individually later. for (ItemStack item : frameItems) { - var customData = item.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT).get(ImageData.CODEC).getOrThrow(); + var customData = item.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY) + .copyTag() + .read(ImageData.CODEC) + .get(); item.set( - DataComponentTypes.CUSTOM_DATA, - NbtComponent.DEFAULT.with( - NbtOps.INSTANCE, - ImageData.CODEC, - new ImageData( - customData.x(), - customData.y(), - customData.width(), - customData.height(), - customData.quickPlace(), - Optional.empty(), - Optional.empty(), - Optional.empty() - ) - ).getOrThrow() + DataComponents.CUSTOM_DATA, + CustomData.of( + ImageData.CODEC.codec().encodeStart( + NbtOps.INSTANCE, + new ImageData( + customData.x(), + customData.y(), + customData.width(), + customData.height(), + customData.quickPlace(), + Optional.empty(), + Optional.empty(), + Optional.empty() + ) + ).result().orElseThrow().asCompound().orElseThrow() + ) ); } - itemFrameEntity.dropStack(serverWorld, toSingleStack(frameItems, inputPath, width * 128, height * 128)); + itemFrameEntity.spawnAtLocation(serverLevel, toSingleStack(frameItems, inputPath, width * 128, height * 128)); } return true; @@ -620,14 +626,14 @@ public static void destroyBundleOnEmpty(ItemStack bundle) { return; } - var contents = bundle.get(DataComponentTypes.BUNDLE_CONTENTS); + var contents = bundle.get(DataComponents.BUNDLE_CONTENTS); if (contents == null || contents.isEmpty()) { - bundle.decrement(1); + bundle.shrink(1); } } public static boolean isInvalidMapForBundle(ItemStack bundle, ItemStack item) { - if (item.isOf(Items.AIR)) { + if (item.is(Items.AIR)) { return false; } @@ -639,7 +645,7 @@ public static boolean isInvalidMapForBundle(ItemStack bundle, ItemStack item) { } // Block insert if item isn't a map - if (!item.isOf(Items.FILLED_MAP)) { + if (!item.is(Items.FILLED_MAP)) { return true; } @@ -658,7 +664,7 @@ public static boolean isInvalidMapForBundle(ItemStack bundle, ItemStack item) { return true; } - var bundleMaps = bundle.get(DataComponentTypes.BUNDLE_CONTENTS); + var bundleMaps = bundle.get(DataComponents.BUNDLE_CONTENTS); // Potential edge case for empty image2map bundle? Best to check either way. // Allow insert if bundle is empty. @@ -667,7 +673,7 @@ public static boolean isInvalidMapForBundle(ItemStack bundle, ItemStack item) { } // Block insert if the bundle already contains a map with the same tiling coordinates. - for (var map : bundleMaps.iterate()) { + for (var map : bundleMaps.items()) { var data = getImageData(map); if (data == null) continue; if (data.x() == mapData.x() && data.y() == mapData.y()) { diff --git a/src/main/java/space/essem/image2map/mixin/BundleItemMixin.java b/src/main/java/space/essem/image2map/mixin/BundleItemMixin.java index d802e15..5ec76af 100644 --- a/src/main/java/space/essem/image2map/mixin/BundleItemMixin.java +++ b/src/main/java/space/essem/image2map/mixin/BundleItemMixin.java @@ -1,18 +1,18 @@ package space.essem.image2map.mixin; +import net.minecraft.world.entity.SlotAccess; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.ClickAction; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.BundleItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.inventory.StackReference; -import net.minecraft.item.BundleItem; -import net.minecraft.item.ItemStack; -import net.minecraft.screen.slot.Slot; -import net.minecraft.util.ClickType; -import net.minecraft.world.World; import space.essem.image2map.Image2Map; @Mixin(BundleItem.class) @@ -22,8 +22,8 @@ public class BundleItemMixin { * and right-clicking to drop an item, destroy the bundle if it ends up empty. * Overridden if the player is in creative mode. */ - @Inject(method = "dropContentsOnUse", at = @At("RETURN")) - private void image2map$dropContentsOnUse(World world, PlayerEntity player, ItemStack stack, CallbackInfo ci) { + @Inject(method = "dropContent(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/item/ItemStack;)V", at = @At("RETURN")) + private void image2map$dropContent(Level level, Player player, ItemStack stack, CallbackInfo ci) { if (player.isCreative()) { return; } @@ -37,17 +37,17 @@ public class BundleItemMixin { * the item into the bundle if it's a valid image2map map for the bundle. * Overridden if the player is in creative mode. */ - @Inject(method = "onStackClicked", at = @At("HEAD"), cancellable = true) - private void image2map$onStackClickedHead(ItemStack bundle, Slot slot, ClickType clickType, PlayerEntity player, CallbackInfoReturnable cir) { + @Inject(method = "overrideStackedOnOther", at = @At("HEAD"), cancellable = true) + private void image2map$overrideStackedOnOtherHead(ItemStack itemStack, Slot slot, ClickAction clickAction, Player player, CallbackInfoReturnable cir) { if (player.isCreative()) { return; } - if (clickType != ClickType.LEFT) { + if (clickAction != ClickAction.PRIMARY) { return; } - if (Image2Map.isInvalidMapForBundle(bundle, slot.getStack())) { + if (Image2Map.isInvalidMapForBundle(itemStack, slot.getItem())) { cir.setReturnValue(false); } } @@ -58,17 +58,17 @@ public class BundleItemMixin { * destroy the bundle if it ends up empty. * Overridden if the player is in creative mode. */ - @Inject(method = "onStackClicked", at = @At("RETURN")) - private void image2map$onStackClickedReturn(ItemStack bundle, Slot slot, ClickType clickType, PlayerEntity player, CallbackInfoReturnable cir) { + @Inject(method = "overrideStackedOnOther", at = @At("RETURN")) + private void image2map$overrideStackedOnOtherReturn(ItemStack itemStack, Slot slot, ClickAction clickAction, Player player, CallbackInfoReturnable cir) { if (player.isCreative()) { return; } - if (clickType != ClickType.RIGHT) { + if (clickAction != ClickAction.SECONDARY) { return; } - Image2Map.destroyBundleOnEmpty(bundle); + Image2Map.destroyBundleOnEmpty(itemStack); } /** @@ -77,21 +77,21 @@ public class BundleItemMixin { * the item into the bundle if it's a valid image2map map for the bundle. * Overridden if the player is in creative mode. */ - @Inject(method = "onClicked", at = @At("HEAD"), cancellable = true) - private void image2map$onClickedHead( + @Inject(method = "overrideOtherStackedOnMe", at = @At("HEAD"), cancellable = true) + private void image2map$overrideOtherStackedOnMeHead( ItemStack bundle, ItemStack otherStack, Slot slot, - ClickType clickType, - PlayerEntity player, - StackReference cursorStackReference, + ClickAction clickAction, + Player player, + SlotAccess slotAccess, CallbackInfoReturnable cir ) { if (player.isCreative()) { return; } - if (clickType != ClickType.LEFT) { + if (clickAction != ClickAction.PRIMARY) { return; } @@ -106,21 +106,21 @@ public class BundleItemMixin { * destroy the bundle if it ends up empty. * Overridden if the player is in creative mode. */ - @Inject(method = "onClicked", at = @At("RETURN")) - private void image2map$onClickedReturn( + @Inject(method = "overrideOtherStackedOnMe", at = @At("RETURN")) + private void image2map$overrideOtherStackedOnMeReturn( ItemStack bundle, ItemStack otherStack, Slot slot, - ClickType clickType, - PlayerEntity player, - StackReference cursorStackReference, + ClickAction clickAction, + Player player, + SlotAccess slotAccess, CallbackInfoReturnable cir ) { if (player.isCreative()) { return; } - if (clickType != ClickType.RIGHT) { + if (clickAction != ClickAction.SECONDARY) { return; } diff --git a/src/main/java/space/essem/image2map/mixin/ItemFrameMixin.java b/src/main/java/space/essem/image2map/mixin/ItemFrameMixin.java index 860e339..6456b73 100644 --- a/src/main/java/space/essem/image2map/mixin/ItemFrameMixin.java +++ b/src/main/java/space/essem/image2map/mixin/ItemFrameMixin.java @@ -32,7 +32,7 @@ public class ItemFrameMixin { private void image2map$destroyMaps(ServerLevel world, Entity entity, boolean dropSelf, CallbackInfo ci) { var frame = (ItemFrame) (Object) this; - if (!this.fixed && Image2Map.destroyItemFrame(entity, frame)) { + if (!this.fixed && Image2Map.destroyItemFrame(world, entity, frame)) { if (dropSelf) { frame.spawnAtLocation(world, new ItemStack(Items.ITEM_FRAME)); } From f201e2173dd4013f0507e401486be465420406c9 Mon Sep 17 00:00:00 2001 From: Leonard Joensen Date: Thu, 14 May 2026 00:24:39 +0200 Subject: [PATCH 6/6] Update to 26.1 --- src/main/java/space/essem/image2map/Image2Map.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/space/essem/image2map/Image2Map.java b/src/main/java/space/essem/image2map/Image2Map.java index 01ac3b8..8079943 100644 --- a/src/main/java/space/essem/image2map/Image2Map.java +++ b/src/main/java/space/essem/image2map/Image2Map.java @@ -24,6 +24,7 @@ import net.minecraft.network.chat.HoverEvent; import net.minecraft.network.chat.Style; import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerLevel; import net.minecraft.server.permissions.PermissionLevel; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; @@ -464,14 +465,13 @@ public static boolean clickItemFrame(Player player, InteractionHand hand, ItemFr if (!entities.isEmpty()) { frames[x + y * width] = entities.get(0); } else { - player.displayClientMessage( + player.sendSystemMessage( Component.literal( String.format( "Item frame wall is not large enough, expected %dx%d or larger", width, height ) - ), - false + ) ); return true; } @@ -591,6 +591,7 @@ public static boolean destroyItemFrame(ServerLevel serverLevel, Entity player, I inputPath = "unknown"; } + ArrayList frameItemTemplates = new ArrayList<>(); // Clear the right/down/facing data from the items, // so they don't get batch removed if placed individually later. for (ItemStack item : frameItems) { @@ -616,9 +617,11 @@ public static boolean destroyItemFrame(ServerLevel serverLevel, Entity player, I ).result().orElseThrow().asCompound().orElseThrow() ) ); + + frameItemTemplates.add(ItemStackTemplate.fromNonEmptyStack(item)); } - itemFrameEntity.spawnAtLocation(serverLevel, toSingleStack(frameItems, inputPath, width * 128, height * 128)); + itemFrameEntity.spawnAtLocation(serverLevel, toSingleStack(frameItemTemplates, inputPath, width * 128, height * 128).create()); } return true; @@ -680,7 +683,7 @@ public static boolean isInvalidMapForBundle(ItemStack bundle, ItemStack item) { // Block insert if the bundle already contains a map with the same tiling coordinates. for (var map : bundleMaps.items()) { - var data = getImageData(map); + var data = getImageData(map.create()); if (data == null) continue; if (data.x() == mapData.x() && data.y() == mapData.y()) { return true;