From 749694acf2ebb906d2121608c0dcaf6bc21e6cb0 Mon Sep 17 00:00:00 2001 From: Estecka Date: Thu, 22 Jun 2023 23:08:43 +0200 Subject: [PATCH] Smart-placing (#5) Placing a variant-locked painting in a space obstructed on one side will attempt to offset the painting, within reason, until it finds a suitable spot. --- .../estecka/invarpaint/PaintEntityPlacer.java | 145 ++++++++++++++++++ .../invarpaint/mixin/DecorationItemMixin.java | 10 +- 2 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 src/main/java/tk/estecka/invarpaint/PaintEntityPlacer.java diff --git a/src/main/java/tk/estecka/invarpaint/PaintEntityPlacer.java b/src/main/java/tk/estecka/invarpaint/PaintEntityPlacer.java new file mode 100644 index 0000000..56e2a25 --- /dev/null +++ b/src/main/java/tk/estecka/invarpaint/PaintEntityPlacer.java @@ -0,0 +1,145 @@ +package tk.estecka.invarpaint; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Optional; +import java.util.Queue; + +import org.joml.Vector2i; + +import net.minecraft.entity.decoration.painting.PaintingEntity; +import net.minecraft.entity.decoration.painting.PaintingVariant; +import net.minecraft.registry.Registries; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; + +public class PaintEntityPlacer +{ + /** + * Iterates over the possible positions a paintings could placed on, while still covering the block targeted by the player. + * Iteration is done in a ring-like pattern, so the positions closest to the targeted block are evaluated first. + * + * Internally, all positions are relative to the bottom-left corner of the maximum scanned surface, + * but the values returned by the iterator are relative to the targeted block. + */ + static class SurfaceIterator implements Iterator + { + private final int width, height; + /** + * The position of the targeted block within the scannable surface. + */ + private final Vector2i targetBlock; + /** + * Keeps track of which blocks have already been iterated on. + */ + private final boolean[][] checkList; + private Queue currentRing, nextRing; + + public SurfaceIterator (int variantWidth, int variantHeight){ + this.width = variantWidth; + this.height = variantHeight; + this.targetBlock = new Vector2i(variantWidth/2, variantHeight/2); + this.checkList = new boolean[variantWidth][variantHeight]; + this.currentRing = new LinkedList(); + this.nextRing = new LinkedList(); + + + boolean evenW = (variantWidth %2) == 0; + boolean evenH = (variantHeight%2) == 0; + + currentRing.add(new Vector2i(targetBlock)); + if (evenW) + currentRing.add(new Vector2i(targetBlock.x-1, targetBlock.y )); + if (evenH) + currentRing.add(new Vector2i(targetBlock.x, targetBlock.y-1)); + if (evenW && evenH) + currentRing.add(new Vector2i(targetBlock.x-1, targetBlock.y-1)); + + for (Vector2i p : currentRing) + checkList[p.x][p.y] = true; + } + + /** + * Returns positions adjacent to this one. + * Elements are ordered so as to start from the right, and circle counter-clockwise around the center. + */ + private Vector2i[] GetAdjacents(Vector2i v){ + Vector2i[] r = new Vector2i[4]; + for (int i=0; i(); + } + + Vector2i pos = currentRing.remove(); + + for (Vector2i adj : GetAdjacents(pos)) + if (ShouldEvaluate(adj)){ + nextRing.add(adj); + checkList[adj.x][adj.y] = true; + } + + Vector2i offset = pos; + offset.x -= this.targetBlock.x; + offset.y -= this.targetBlock.y; + return offset; + } + + } + + static public Optional PlaceLockedPainting(World world, BlockPos targetPos, Direction facing, PaintingVariant variant){ + PaintingEntity entity = new PaintingEntity(world, targetPos, facing, Registries.PAINTING_VARIANT.getEntry(variant)); + if (entity.canStayAttached()) + return Optional.of(entity); + + // The direction of the horizontal axis of the wall + Vec3i right = facing.rotateYCounterclockwise().getVector(); + + SurfaceIterator surface = new SurfaceIterator(variant.getWidth()/16, variant.getHeight()/16); + surface.next(); // Skip the targeted position, which was already tested. + while (surface.hasNext()) { + Vector2i planeOffset = surface.next(); + Vec3i worldOffset = right.multiply(planeOffset.x).add(0, planeOffset.y, 0); + + entity.setPosition( + targetPos.getX() + worldOffset.getX(), + targetPos.getY() + worldOffset.getY(), + targetPos.getZ() + worldOffset.getZ() + ); + if (entity.canStayAttached()) + return Optional.of(entity); + } + + return Optional.empty(); + } +} diff --git a/src/main/java/tk/estecka/invarpaint/mixin/DecorationItemMixin.java b/src/main/java/tk/estecka/invarpaint/mixin/DecorationItemMixin.java index cba1d60..7bc5cef 100644 --- a/src/main/java/tk/estecka/invarpaint/mixin/DecorationItemMixin.java +++ b/src/main/java/tk/estecka/invarpaint/mixin/DecorationItemMixin.java @@ -30,6 +30,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import tk.estecka.invarpaint.InvariablePaintings; +import tk.estecka.invarpaint.PaintEntityPlacer; import tk.estecka.invarpaint.PaintStackCreator; import java.util.List; @@ -75,13 +76,8 @@ private Optional filterPlacedPainting(World world, BlockPos pos, String variantId = PaintStackCreator.GetVariantId(this.itemStack); PaintingVariant itemVariant = (variantId==null) ? null : Registries.PAINTING_VARIANT.get(new Identifier(variantId)); - if (itemVariant != null) { - PaintingEntity entity = new PaintingEntity(world, pos, facing, Registries.PAINTING_VARIANT.getEntry(itemVariant)); - if (entity.canStayAttached()) - return Optional.of(entity); - else - return Optional.empty(); - } + if (itemVariant != null) + return PaintEntityPlacer.PlaceLockedPainting(world, pos, facing, itemVariant); else { if (variantId != null) InvariablePaintings.LOGGER.warn("Unknown painting id: {}", variantId);