Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 150 additions & 4 deletions src/main/java/space/essem/image2map/Image2Map.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -63,6 +64,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;
Expand Down Expand Up @@ -462,6 +464,16 @@ public static boolean clickItemFrame(Player player, InteractionHand hand, ItemFr
var entities = world.getEntitiesOfClass(ItemFrame.class, AABB.unitCubeFromLowerCorner(Vec3.atLowerCornerOf(mut)), (entity1) -> entity1.getDirection() == facing && entity1.blockPosition().equals(mut));
if (!entities.isEmpty()) {
frames[x + y * width] = entities.get(0);
} else {
player.sendSystemMessage(
Component.literal(
String.format(
"Item frame wall is not large enough, expected %dx%d or larger",
width, height
)
)
);
return true;
}
}
}
Expand Down Expand Up @@ -492,7 +504,27 @@ public static boolean clickItemFrame(Player player, InteractionHand hand, ItemFr
return false;
}

public static boolean destroyItemFrame(Entity player, ItemFrame itemFrameEntity) {
private static @Nullable ImageData getImageData(ItemStack item) {
var tag = item.get(DataComponents.CUSTOM_DATA);
if (tag == null) return null;
var codec = tag.copyTag().read(ImageData.CODEC);
return codec.orElse(null);
}

private static @Nullable String getInputPathFromLore(ItemStack stack) {
if (getImageData(stack) == null) {
return null;
}

var lore = stack.get(DataComponents.LORE);
if (lore == null || lore.lines().isEmpty()) {
return null;
}

return lore.lines().getLast().getString();
}

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

Expand All @@ -509,6 +541,7 @@ public static boolean destroyItemFrame(Entity player, ItemFrame itemFrameEntity)
Direction facing = tag.orElseThrow().facing().get();

var world = itemFrameEntity.level();
var itemFramePosition = itemFrameEntity.blockPosition();
var start = itemFrameEntity.blockPosition();

var mut = start.mutable();
Expand All @@ -518,35 +551,148 @@ public static boolean destroyItemFrame(Entity player, ItemFrame itemFrameEntity)

start = mut.immutable();

for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
ArrayList<ItemStack> 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.getEntitiesOfClass(ItemFrame.class, AABB.unitCubeFromLowerCorner(Vec3.atLowerCornerOf(mut)),
(entity1) -> entity1.getDirection() == facing && entity1.blockPosition().equals(mut));

// Fix for the item frame technically not existing in the world
// after the block holding it has been destroyed
ItemFrame 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.getItem();
tag = frameStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag().read(ImageData.CODEC);

if (frameStack.getItem() == Items.FILLED_MAP && tag.isPresent() && tag.orElseThrow().right().isPresent()
&& tag.orElseThrow().down().isPresent() && tag.orElseThrow().facing().isPresent()) {
frameItems.add(frameStack);
frame.setItem(ItemStack.EMPTY, true);
frame.setInvisible(false);
}
}
}
}

if (!frameItems.isEmpty()) {
String inputPath = getInputPathFromLore(frameItems.getFirst());
if (inputPath == null) {
inputPath = "unknown";
}

ArrayList<ItemStackTemplate> 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) {
var customData = item.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY)
.copyTag()
.read(ImageData.CODEC)
.get();
item.set(
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()
)
);

frameItemTemplates.add(ItemStackTemplate.fromNonEmptyStack(item));
}

itemFrameEntity.spawnAtLocation(serverLevel, toSingleStack(frameItemTemplates, inputPath, width * 128, height * 128).create());
}

return true;
}

return false;
}

public static void destroyBundleOnEmpty(ItemStack bundle) {
if (getImageData(bundle) == null) {
return;
}

var contents = bundle.get(DataComponents.BUNDLE_CONTENTS);
if (contents == null || contents.isEmpty()) {
bundle.shrink(1);
}
}

public static boolean isInvalidMapForBundle(ItemStack bundle, ItemStack item) {
if (item.is(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.is(Items.FILLED_MAP)) {
return true;
}

var mapData = getImageData(item);

// Block insert if map isn't an image2map map
if (mapData == null) {
return true;
}

var bundleInputPath = getInputPathFromLore(bundle);
var mapInputPath = getInputPathFromLore(item);

// 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;
}

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.
if (bundleMaps == null) {
return false;
}

// Block insert if the bundle already contains a map with the same tiling coordinates.
for (var map : bundleMaps.items()) {
var data = getImageData(map.create());
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();
Expand Down
132 changes: 103 additions & 29 deletions src/main/java/space/essem/image2map/mixin/BundleItemMixin.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package space.essem.image2map.mixin;

import net.minecraft.core.component.DataComponents;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.SlotAccess;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ClickAction;
Expand All @@ -13,43 +10,120 @@
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 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 = "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;
}

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 = "overrideStackedOnOther", at = @At("HEAD"), cancellable = true)
private void image2map$overrideStackedOnOtherHead(ItemStack itemStack, Slot slot, ClickAction clickAction, Player player, CallbackInfoReturnable<Boolean> cir) {
if (player.isCreative()) {
return;
}

if (clickAction != ClickAction.PRIMARY) {
return;
}

@Inject(method = "use", at = @At("HEAD"), cancellable = true)
private void image2map$useBundle(Level world, Player user, InteractionHand hand,
CallbackInfoReturnable<InteractionResult> cir) {
ItemStack itemStack = user.getItemInHand(hand);
var tag = itemStack.get(DataComponents.CUSTOM_DATA);
if (Image2Map.isInvalidMapForBundle(itemStack, slot.getItem())) {
cir.setReturnValue(false);
}
}

/**
* 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 = "overrideStackedOnOther", at = @At("RETURN"))
private void image2map$overrideStackedOnOtherReturn(ItemStack itemStack, Slot slot, ClickAction clickAction, Player player, CallbackInfoReturnable<Boolean> cir) {
if (player.isCreative()) {
return;
}

if (clickAction != ClickAction.SECONDARY) {
return;
}

if (tag != null && tag.copyTag().contains("image2map:quick_place") && !user.isCreative()) {
cir.setReturnValue(InteractionResult.FAIL);
cir.cancel();
Image2Map.destroyBundleOnEmpty(itemStack);
}
}

@Inject(method = "overrideStackedOnOther", at = @At("HEAD"), cancellable = true)
private void image2map$addBundleItems(ItemStack bundle, Slot slot, ClickAction clickType, Player player,
CallbackInfoReturnable<Boolean> cir) {
var tag = bundle.get(DataComponents.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 = "overrideOtherStackedOnMe", at = @At("HEAD"), cancellable = true)
private void image2map$overrideOtherStackedOnMeHead(
ItemStack bundle,
ItemStack otherStack,
Slot slot,
ClickAction clickAction,
Player player,
SlotAccess slotAccess,
CallbackInfoReturnable<Boolean> cir
) {
if (player.isCreative()) {
return;
}

if (tag != null && tag.copyTag().contains("image2map:quick_place") && !player.isCreative()) {
cir.setReturnValue(false);
cir.cancel();
if (clickAction != ClickAction.PRIMARY) {
return;
}

if (Image2Map.isInvalidMapForBundle(bundle, otherStack)) {
cir.setReturnValue(false);
}
}
}

@Inject(method = "overrideOtherStackedOnMe", at = @At("HEAD"), cancellable = true)
private void image2map$removeBundleItems(ItemStack bundle, ItemStack otherStack, Slot slot, ClickAction clickType,
Player player, SlotAccess cursorStackReference,
CallbackInfoReturnable<Boolean> cir) {
var tag = bundle.get(DataComponents.CUSTOM_DATA);
/**
* 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 = "overrideOtherStackedOnMe", at = @At("RETURN"))
private void image2map$overrideOtherStackedOnMeReturn(
ItemStack bundle,
ItemStack otherStack,
Slot slot,
ClickAction clickAction,
Player player,
SlotAccess slotAccess,
CallbackInfoReturnable<Boolean> cir
) {
if (player.isCreative()) {
return;
}

if (clickAction != ClickAction.SECONDARY) {
return;
}

if (tag != null && tag.copyTag().contains("image2map:quick_place") && !player.isCreative()) {
cir.setReturnValue(false);
cir.cancel();
Image2Map.destroyBundleOnEmpty(bundle);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,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));
}
Expand Down