From 99c4586f4d61b17a0a67a3da7c61d8d58fdfbd44 Mon Sep 17 00:00:00 2001 From: ohnoey Date: Tue, 27 Aug 2024 21:37:55 -0700 Subject: [PATCH] Converge rotation and translation to transformations --- .../craft/movement/MovementController.java | 28 +-- .../countercraft/movecraft/WorldHandler.java | 30 ++- .../movecraft/util/AffineTransformation.java | 14 ++ .../movecraft/util/hitboxes/HitBox.java | 8 + .../movecraft/compat/v1_18/IWorldHandler.java | 194 ++++------------- .../movecraft/compat/v1_20/IWorldHandler.java | 205 ++++-------------- .../movecraft/compat/v1_21/IWorldHandler.java | 204 ++++------------- 7 files changed, 177 insertions(+), 506 deletions(-) create mode 100644 api/src/main/java/net/countercraft/movecraft/util/AffineTransformation.java diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/craft/movement/MovementController.java b/Movecraft/src/main/java/net/countercraft/movecraft/craft/movement/MovementController.java index 91f82872a..7aacc261a 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/craft/movement/MovementController.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/craft/movement/MovementController.java @@ -17,7 +17,7 @@ public class MovementController { private static final @NotNull MovecraftLocation ZERO = new MovecraftLocation(0,0,0); - private static final @NotNull MovementData DEFAULT = new MovementData(ZERO, MovecraftRotation.NONE, null); + private static final @NotNull MovementData DEFAULT = new MovementData(AffineTransformation.NONE, null); private final @NotNull CraftDataTagKey _dataTagKey; public MovementController(Plugin plugin) { @@ -28,8 +28,7 @@ public MovementController(Plugin plugin) { public void move(@NotNull Craft craft, @Nullable MovecraftLocation translation, @Nullable MovecraftRotation rotation, @Nullable MovecraftWorld changeWorld){ craft.computeDataTag(_dataTagKey, (data) -> data.merge(new MovementData( - translation == null ? ZERO : translation, - rotation == null ? MovecraftRotation.NONE : rotation, + AffineTransformation.of(translation).mult(AffineTransformation.of(rotation)), changeWorld))); } @@ -54,23 +53,20 @@ public MovementTask(@NotNull Craft craft){ } } - private record MovementData(@NotNull MovecraftLocation translation, @NotNull MovecraftRotation rotation, @Nullable MovecraftWorld world){ - - private static MovecraftRotation add(MovecraftRotation a, MovecraftRotation b){ - if(a == MovecraftRotation.NONE || a==b){ - return b; - } - if(b == MovecraftRotation.NONE){ - return a; - } - - return MovecraftRotation.NONE; - } + // TODO: Implement int matrix or find library (most are for floating point) + private record AffineTransformation(){ + public static @NotNull AffineTransformation NONE = null; + public static @NotNull AffineTransformation of(MovecraftLocation translation){ return null; } + public static @NotNull AffineTransformation of(MovecraftRotation rotation){ return null; } + public @NotNull AffineTransformation mult(AffineTransformation other){ return null; } + public @NotNull MovecraftLocation apply(MovecraftLocation location){ return location; } + } + private record MovementData(@NotNull AffineTransformation transformation, @Nullable MovecraftWorld world){ @Contract("_ -> new") public @NotNull MovementData merge(@NotNull MovementData other){ // TODO: Correct this - return new MovementData(translation.add(other.translation), add(rotation, other.rotation), this.world == null ? other.world : this.world); + return new MovementData(transformation.mult(other.transformation), this.world == null ? other.world : this.world); } } } diff --git a/api/src/main/java/net/countercraft/movecraft/WorldHandler.java b/api/src/main/java/net/countercraft/movecraft/WorldHandler.java index 3b72e569f..d61e301fd 100644 --- a/api/src/main/java/net/countercraft/movecraft/WorldHandler.java +++ b/api/src/main/java/net/countercraft/movecraft/WorldHandler.java @@ -1,6 +1,8 @@ package net.countercraft.movecraft; import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.util.AffineTransformation; +import net.countercraft.movecraft.util.hitboxes.HitBox; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; @@ -11,15 +13,33 @@ import org.jetbrains.annotations.Nullable; public abstract class WorldHandler { - public abstract void rotateCraft(@NotNull Craft craft, @NotNull MovecraftLocation originLocation, @NotNull MovecraftRotation rotation); - public abstract void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation newLocation, @NotNull World world); + @Deprecated(forRemoval = true) + public void rotateCraft(@NotNull Craft craft, @NotNull MovecraftLocation originLocation, @NotNull MovecraftRotation rotation){ + transformHitBox( + craft.getHitBox(), + AffineTransformation.of(originLocation) + .mult(AffineTransformation.of(rotation)) + .mult(AffineTransformation.of(originLocation.scalarMultiply(-1))), + craft.getWorld(), + craft.getWorld()); + } + + @Deprecated(forRemoval = true) + public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation displacement, @NotNull World world){ + transformHitBox(craft.getHitBox(), AffineTransformation.of(displacement), craft.getWorld(), world); + } + public abstract void transformHitBox(@NotNull HitBox hitbox, @NotNull AffineTransformation transformation, @NotNull World originWorld, @NotNull World destinationWorld); public abstract void setBlockFast(@NotNull Location location, @NotNull BlockData data); public abstract void setBlockFast(@NotNull Location location, @NotNull MovecraftRotation rotation, @NotNull BlockData data); @Deprecated(forRemoval = true) - public abstract @Nullable Location getAccessLocation(@NotNull InventoryView inventoryView); // Not needed for 1.20+, remove when dropping support for 1.18.2 + public @Nullable Location getAccessLocation(@NotNull InventoryView inventoryView){ + // Not needed for 1.20+, remove when dropping support for 1.18.2 + return null; + } @Deprecated(forRemoval = true) - public abstract void setAccessLocation(@NotNull InventoryView inventoryView, @NotNull Location location); // Not needed for 1.20+, remove when dropping support for 1.18.2 - + public void setAccessLocation(@NotNull InventoryView inventoryView, @NotNull Location location){ + // Not needed for 1.20+, remove when dropping support for 1.18.2 + } public static @NotNull String getPackageName(@NotNull String minecraftVersion) { String[] parts = minecraftVersion.split("\\."); if (parts.length < 2) diff --git a/api/src/main/java/net/countercraft/movecraft/util/AffineTransformation.java b/api/src/main/java/net/countercraft/movecraft/util/AffineTransformation.java new file mode 100644 index 000000000..10c3a7c6b --- /dev/null +++ b/api/src/main/java/net/countercraft/movecraft/util/AffineTransformation.java @@ -0,0 +1,14 @@ +package net.countercraft.movecraft.util; + +import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.MovecraftRotation; +import org.jetbrains.annotations.NotNull; + +// TODO: Implement with 4d matrix +public record AffineTransformation(){ + public static @NotNull AffineTransformation NONE = null; + public static @NotNull AffineTransformation of(MovecraftLocation translation){ return null; } + public static @NotNull AffineTransformation of(MovecraftRotation rotation){ return null; } + public @NotNull AffineTransformation mult(AffineTransformation other){ return null; } + public @NotNull MovecraftLocation apply(MovecraftLocation location){ return location; } +} diff --git a/api/src/main/java/net/countercraft/movecraft/util/hitboxes/HitBox.java b/api/src/main/java/net/countercraft/movecraft/util/hitboxes/HitBox.java index 14ba27760..25a7fbfca 100644 --- a/api/src/main/java/net/countercraft/movecraft/util/hitboxes/HitBox.java +++ b/api/src/main/java/net/countercraft/movecraft/util/hitboxes/HitBox.java @@ -4,12 +4,14 @@ import com.google.common.collect.UnmodifiableIterator; import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.exception.EmptyHitBoxException; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import java.util.AbstractSet; import java.util.Collection; import java.util.Iterator; import java.util.Set; +import java.util.stream.Stream; public interface HitBox extends Iterable{ int getMinX(); @@ -94,6 +96,12 @@ default Set asSet(){ return new HitBoxSetView(this); } + @NotNull + @Contract(pure = true) + default Stream stream(){ + return asSet().stream(); + } + @NotNull HitBox difference(HitBox other); diff --git a/v1_18/src/main/java/net/countercraft/movecraft/compat/v1_18/IWorldHandler.java b/v1_18/src/main/java/net/countercraft/movecraft/compat/v1_18/IWorldHandler.java index 5babf0fe9..fb45a4e0b 100644 --- a/v1_18/src/main/java/net/countercraft/movecraft/compat/v1_18/IWorldHandler.java +++ b/v1_18/src/main/java/net/countercraft/movecraft/compat/v1_18/IWorldHandler.java @@ -3,10 +3,11 @@ import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.MovecraftRotation; import net.countercraft.movecraft.WorldHandler; -import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.util.AffineTransformation; import net.countercraft.movecraft.util.CollectionUtils; import net.countercraft.movecraft.util.MathUtils; import net.countercraft.movecraft.util.UnsafeUtils; +import net.countercraft.movecraft.util.hitboxes.HitBox; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.inventory.AbstractContainerMenu; @@ -23,21 +24,23 @@ import net.minecraft.world.ticks.ScheduledTick; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.v1_18_R2.CraftWorld; import org.bukkit.craftbukkit.v1_18_R2.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView; import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.reflect.Field; import java.util.*; -import java.util.function.Predicate; +import java.util.stream.Collectors; -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "ForLoopReplaceableByForEach"}) public class IWorldHandler extends WorldHandler { - private static final Rotation ROTATION[]; + private static final Rotation[] ROTATION; static { ROTATION = new Rotation[3]; @@ -55,90 +58,17 @@ public IWorldHandler() { } @Override - public void rotateCraft(@NotNull Craft craft, @NotNull MovecraftLocation originPoint, @NotNull MovecraftRotation rotation) { + public void transformHitBox(@NotNull HitBox hitbox, @NotNull AffineTransformation transformation, @NotNull World originWorld, @NotNull World destinationWorld) { //******************************************* //* Step one: Convert to Positions * //******************************************* - HashMap rotatedPositions = new HashMap<>(); - MovecraftRotation counterRotation = rotation == MovecraftRotation.CLOCKWISE ? MovecraftRotation.ANTICLOCKWISE : MovecraftRotation.CLOCKWISE; - for (MovecraftLocation newLocation : craft.getHitBox()) { - rotatedPositions.put(locationToPosition(MathUtils.rotateVec(counterRotation, newLocation.subtract(originPoint)).add(originPoint)), locationToPosition(newLocation)); - } - //******************************************* - //* Step two: Get the tiles * - //******************************************* - ServerLevel nativeWorld = ((CraftWorld) craft.getWorld()).getHandle(); - List tiles = new ArrayList<>(); - List ticks = new ArrayList<>(); - - //get the tiles - for (BlockPos position : rotatedPositions.keySet()) { - - BlockEntity tile = removeBlockEntity(nativeWorld, position); - if (tile != null) - tiles.add(new TileHolder(tile, position)); - - //get the nextTick to move with the tile - ScheduledTick tickHere = tickProvider.getNextTick(nativeWorld, position); - if (tickHere != null) { - ((LevelChunkTicks) nativeWorld.getChunkAt(position).getBlockTicks()).removeIf( - (Predicate) scheduledTick -> scheduledTick.equals(tickHere)); - ticks.add(new TickHolder(tickHere, position)); - } - } - - //******************************************* - //* Step three: Translate all the blocks * - //******************************************* - // blockedByWater=false means an ocean-going vessel - //TODO: Simplify - //TODO: go by chunks - //TODO: Don't move unnecessary blocks - //get the blocks and rotate them - HashMap blockData = new HashMap<>(); - for (BlockPos position : rotatedPositions.keySet()) { - blockData.put(position, nativeWorld.getBlockState(position).rotate(ROTATION[rotation.ordinal()])); - } - //create the new block - for (Map.Entry entry : blockData.entrySet()) { - setBlockFast(nativeWorld, rotatedPositions.get(entry.getKey()), entry.getValue()); - } - - - //******************************************* - //* Step four: replace all the tiles * - //******************************************* - //TODO: go by chunks - for (TileHolder tileHolder : tiles) { - moveBlockEntity(nativeWorld, rotatedPositions.get(tileHolder.getTilePosition()), tileHolder.getTile()); - } - for (TickHolder tickHolder : ticks) { - final long currentTime = nativeWorld.serverLevelData.getGameTime(); - nativeWorld.getBlockTicks().schedule(new ScheduledTick<>((Block) tickHolder.getTick().type(), rotatedPositions.get(tickHolder.getTick().pos()), tickHolder.getTick().triggerTick() - currentTime, tickHolder.getTick().priority(), tickHolder.getTick().subTickOrder())); - } - - //******************************************* - //* Step five: Destroy the leftovers * - //******************************************* - //TODO: add support for pass-through - Collection deletePositions = CollectionUtils.filter(rotatedPositions.keySet(), rotatedPositions.values()); - for (BlockPos position : deletePositions) { - setBlockFast(nativeWorld, position, Blocks.AIR.defaultBlockState()); - } - } - - @Override - public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation displacement, @NotNull org.bukkit.World world) { - //TODO: Add support for rotations - //A craftTranslateCommand should only occur if the craft is moving to a valid position - //******************************************* - //* Step one: Convert to Positions * - //******************************************* - BlockPos translateVector = locationToPosition(displacement); - List positions = new ArrayList<>(craft.getHitBox().size()); - craft.getHitBox().forEach((movecraftLocation) -> positions.add(locationToPosition((movecraftLocation)).subtract(translateVector))); - ServerLevel oldNativeWorld = ((CraftWorld) craft.getWorld()).getHandle(); - ServerLevel nativeWorld = ((CraftWorld) world).getHandle(); + List positions = hitbox + .stream() + .map(transformation::apply) + .map(this::locationToPosition) + .collect(Collectors.toList()); + ServerLevel originLevel = ((CraftWorld) originWorld).getHandle(); + ServerLevel destinationLevel = ((CraftWorld) destinationWorld).getHandle(); //******************************************* //* Step two: Get the tiles * //******************************************* @@ -147,26 +77,23 @@ public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation disp //get the tiles for (int i = 0, positionsSize = positions.size(); i < positionsSize; i++) { BlockPos position = positions.get(i); - if (oldNativeWorld.getBlockState(position) == Blocks.AIR.defaultBlockState()) + if (originLevel.getBlockState(position) == Blocks.AIR.defaultBlockState()) continue; - //BlockEntity tile = nativeWorld.removeBlockEntity(position); - BlockEntity tile = removeBlockEntity(oldNativeWorld, position); + BlockEntity tile = removeBlockEntity(originLevel, position); if (tile != null) - tiles.add(new TileHolder(tile, position)); + tiles.add(new TileHolder(tile,position)); //get the nextTick to move with the tile - ScheduledTick tickHere = tickProvider.getNextTick(oldNativeWorld, position); + ScheduledTick tickHere = tickProvider.getNextTick(destinationLevel, position); if (tickHere != null) { - ((LevelChunkTicks) nativeWorld.getChunkAt(position).getBlockTicks()).removeIf( - (Predicate) scheduledTick -> scheduledTick.equals(tickHere)); + var levelTicks = ((LevelChunkTicks) destinationLevel.getChunkAt(position).getBlockTicks()); + levelTicks.removeIf(tickHere::equals); ticks.add(new TickHolder(tickHere, position)); } - } //******************************************* - //* Step three: Translate all the blocks * + //* Step three: Transform all the blocks * //******************************************* - // blockedByWater=false means an ocean-going vessel //TODO: Simplify //TODO: go by chunks //TODO: Don't move unnecessary blocks @@ -175,35 +102,32 @@ public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation disp List newPositions = new ArrayList<>(); for (int i = 0, positionsSize = positions.size(); i < positionsSize; i++) { BlockPos position = positions.get(i); - blockData.add(oldNativeWorld.getBlockState(position)); - newPositions.add(position.offset(translateVector)); + blockData.add(originLevel.getBlockState(position)); + newPositions.add(transformPosition(transformation, position)); } //create the new block for (int i = 0, positionSize = newPositions.size(); i < positionSize; i++) { - setBlockFast(nativeWorld, newPositions.get(i), blockData.get(i)); + setBlockFast(destinationLevel, newPositions.get(i), blockData.get(i)); } //******************************************* //* Step four: replace all the tiles * //******************************************* //TODO: go by chunks - for (int i = 0, tilesSize = tiles.size(); i < tilesSize; i++) { - TileHolder tileHolder = tiles.get(i); - moveBlockEntity(nativeWorld, tileHolder.getTilePosition().offset(translateVector), tileHolder.getTile()); - } - for (int i = 0, tickSize = ticks.size(); i < tickSize; i++) { - TickHolder tickHolder = ticks.get(i); - final long currentTime = nativeWorld.getGameTime(); - nativeWorld.getBlockTicks().schedule(new ScheduledTick<>((Block) tickHolder.getTick().type(), tickHolder.getTickPosition().offset(translateVector), tickHolder.getTick().triggerTick() - currentTime, tickHolder.getTick().priority(), tickHolder.getTick().subTickOrder())); + for (TileHolder tileHolder : tiles) + moveBlockEntity(destinationLevel, transformPosition(transformation, tileHolder.tilePosition()), tileHolder.tile()); + for (TickHolder tickHolder : ticks) { + final long currentTime = destinationLevel.getGameTime(); + destinationLevel.getBlockTicks().schedule(new ScheduledTick<>((Block) tickHolder.tick().type(), transformPosition(transformation, tickHolder.tickPosition()), tickHolder.tick().triggerTick() - currentTime, tickHolder.tick().priority(), tickHolder.tick().subTickOrder())); } //******************************************* //* Step five: Destroy the leftovers * //******************************************* List deletePositions = positions; - if (oldNativeWorld == nativeWorld) + if (originLevel == destinationLevel) deletePositions = CollectionUtils.filter(positions, newPositions); for (int i = 0, deletePositionsSize = deletePositions.size(); i < deletePositionsSize; i++) { BlockPos position = deletePositions.get(i); - setBlockFast(oldNativeWorld, position, Blocks.AIR.defaultBlockState()); + setBlockFast(originLevel, position, Blocks.AIR.defaultBlockState()); } } @@ -217,6 +141,11 @@ private BlockPos locationToPosition(@NotNull MovecraftLocation loc) { return new BlockPos(loc.getX(), loc.getY(), loc.getZ()); } + @NotNull @Contract(pure = true) + private BlockPos transformPosition(@NotNull AffineTransformation transformation, @NotNull BlockPos pos){ + return locationToPosition(transformation.apply(new MovecraftLocation(pos.getX(), pos.getY(), pos.getZ()))); + } + private void setBlockFast(@NotNull Level world, @NotNull BlockPos position, @NotNull BlockState data) { LevelChunk chunk = world.getChunkAt(position); int chunkSection = (position.getY() >> 4) - chunk.getMinSection(); @@ -256,6 +185,7 @@ public void setBlockFast(@NotNull Location location, @NotNull MovecraftRotation setBlockFast(world, BlockPos, blockData); } + @SuppressWarnings("removal") @Override public @Nullable Location getAccessLocation(@NotNull InventoryView inventoryView) { AbstractContainerMenu menu = ((CraftInventoryView) inventoryView).getHandle(); @@ -271,6 +201,7 @@ public void setBlockFast(@NotNull Location location, @NotNull MovecraftRotation return null; } + @SuppressWarnings("removal") @Override public void setAccessLocation(@NotNull InventoryView inventoryView, @NotNull Location location) { if (location.getWorld() == null) @@ -286,6 +217,7 @@ public void setAccessLocation(@NotNull InventoryView inventoryView, @NotNull Loc private void moveBlockEntity(@NotNull Level nativeWorld, @NotNull BlockPos newPosition, @NotNull BlockEntity tile) { LevelChunk chunk = nativeWorld.getChunkAt(newPosition); try { + //noinspection JavaReflectionMemberAccess var positionField = BlockEntity.class.getDeclaredField("o"); // o is obfuscated worldPosition UnsafeUtils.setField(positionField, tile, newPosition); } @@ -302,49 +234,7 @@ private void moveBlockEntity(@NotNull Level nativeWorld, @NotNull BlockPos newPo chunk.blockEntities.put(newPosition, tile); } - private static class TileHolder { - @NotNull - private final BlockEntity tile; - @NotNull - private final BlockPos tilePosition; + private record TileHolder(@NotNull BlockEntity tile, @NotNull BlockPos tilePosition) { } - public TileHolder(@NotNull BlockEntity tile, @NotNull BlockPos tilePosition) { - this.tile = tile; - this.tilePosition = tilePosition; - } - - - @NotNull - public BlockEntity getTile() { - return tile; - } - - @NotNull - public BlockPos getTilePosition() { - return tilePosition; - } - } - - private static class TickHolder { - @NotNull - private final ScheduledTick tick; - @NotNull - private final BlockPos tickPosition; - - public TickHolder(@NotNull ScheduledTick tick, @NotNull BlockPos tilePosition) { - this.tick = tick; - this.tickPosition = tilePosition; - } - - - @NotNull - public ScheduledTick getTick() { - return tick; - } - - @NotNull - public BlockPos getTickPosition() { - return tickPosition; - } - } + private record TickHolder(@NotNull ScheduledTick tick, @NotNull BlockPos tickPosition) { } } \ No newline at end of file diff --git a/v1_20/src/main/java/net/countercraft/movecraft/compat/v1_20/IWorldHandler.java b/v1_20/src/main/java/net/countercraft/movecraft/compat/v1_20/IWorldHandler.java index cd2a5565a..5e0783b1b 100644 --- a/v1_20/src/main/java/net/countercraft/movecraft/compat/v1_20/IWorldHandler.java +++ b/v1_20/src/main/java/net/countercraft/movecraft/compat/v1_20/IWorldHandler.java @@ -3,20 +3,18 @@ import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.MovecraftRotation; import net.countercraft.movecraft.WorldHandler; -import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.util.AffineTransformation; import net.countercraft.movecraft.util.CollectionUtils; import net.countercraft.movecraft.util.MathUtils; import net.countercraft.movecraft.util.UnsafeUtils; +import net.countercraft.movecraft.util.hitboxes.HitBox; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.inventory.ContainerLevelAccess; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; @@ -24,25 +22,21 @@ import net.minecraft.world.ticks.ScheduledTick; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.data.CraftBlockData; -import org.bukkit.craftbukkit.inventory.CraftInventoryView; -import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.function.Predicate; +import java.util.stream.Collectors; -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "ForLoopReplaceableByForEach"}) public class IWorldHandler extends WorldHandler { - private static final Rotation ROTATION[]; + private static final Rotation[] ROTATION; static { ROTATION = new Rotation[3]; @@ -60,92 +54,17 @@ public IWorldHandler() { } @Override - public void rotateCraft(@NotNull Craft craft, @NotNull MovecraftLocation originPoint, @NotNull MovecraftRotation rotation) { + public void transformHitBox(@NotNull HitBox hitbox, @NotNull AffineTransformation transformation, @NotNull World originWorld, @NotNull World destinationWorld) { //******************************************* //* Step one: Convert to Positions * //******************************************* - HashMap rotatedPositions = new HashMap<>(); - MovecraftRotation counterRotation = rotation == MovecraftRotation.CLOCKWISE ? MovecraftRotation.ANTICLOCKWISE : MovecraftRotation.CLOCKWISE; - for (MovecraftLocation newLocation : craft.getHitBox()) { - rotatedPositions.put(locationToPosition(MathUtils.rotateVec(counterRotation, newLocation.subtract(originPoint)).add(originPoint)), locationToPosition(newLocation)); - } - //******************************************* - //* Step two: Get the tiles * - //******************************************* - ServerLevel nativeWorld = ((CraftWorld) craft.getWorld()).getHandle(); - List tiles = new ArrayList<>(); - List ticks = new ArrayList<>(); - //get the tiles - for (BlockPos position : rotatedPositions.keySet()) { - BlockEntity tile = removeBlockEntity(nativeWorld, position); - if (tile != null) - tiles.add(new TileHolder(tile, position)); - - //get the nextTick to move with the tile - ScheduledTick tickHere = tickProvider.getNextTick(nativeWorld, position); - if (tickHere != null) { - ((LevelChunkTicks) nativeWorld.getChunkAt(position).getBlockTicks()).removeIf( - (Predicate) scheduledTick -> scheduledTick.equals(tickHere)); - ticks.add(new TickHolder(tickHere, position)); - } - } - - //******************************************* - //* Step three: Translate all the blocks * - //******************************************* - // blockedByWater=false means an ocean-going vessel - //TODO: Simplify - //TODO: go by chunks - //TODO: Don't move unnecessary blocks - //get the blocks and rotate them - HashMap blockData = new HashMap<>(); - for (BlockPos position : rotatedPositions.keySet()) { - blockData.put(position, nativeWorld.getBlockState(position).rotate(ROTATION[rotation.ordinal()])); - } - //create the new block - for (Map.Entry entry : blockData.entrySet()) { - setBlockFast(nativeWorld, rotatedPositions.get(entry.getKey()), entry.getValue()); - } - - - //******************************************* - //* Step four: replace all the tiles * - //******************************************* - //TODO: go by chunks - for (TileHolder tileHolder : tiles) - moveBlockEntity(nativeWorld, rotatedPositions.get(tileHolder.getTilePosition()), tileHolder.getTile()); - for (TickHolder tickHolder : ticks) { - final long currentTime = nativeWorld.serverLevelData.getGameTime(); - nativeWorld.getBlockTicks().schedule(new ScheduledTick<>( - (Block) tickHolder.getTick().type(), - rotatedPositions.get(tickHolder.getTick().pos()), - tickHolder.getTick().triggerTick() - currentTime, - tickHolder.getTick().priority(), - tickHolder.getTick().subTickOrder())); - } - - //******************************************* - //* Step five: Destroy the leftovers * - //******************************************* - //TODO: add support for pass-through - Collection deletePositions = CollectionUtils.filter(rotatedPositions.keySet(), rotatedPositions.values()); - for (BlockPos position : deletePositions) { - setBlockFast(nativeWorld, position, Blocks.AIR.defaultBlockState()); - } - } - - @Override - public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation displacement, @NotNull org.bukkit.World world) { - //TODO: Add support for rotations - //A craftTranslateCommand should only occur if the craft is moving to a valid position - //******************************************* - //* Step one: Convert to Positions * - //******************************************* - BlockPos translateVector = locationToPosition(displacement); - List positions = new ArrayList<>(craft.getHitBox().size()); - craft.getHitBox().forEach((movecraftLocation) -> positions.add(locationToPosition((movecraftLocation)).subtract(translateVector))); - ServerLevel oldNativeWorld = ((CraftWorld) craft.getWorld()).getHandle(); - ServerLevel nativeWorld = ((CraftWorld) world).getHandle(); + List positions = hitbox + .stream() + .map(transformation::apply) + .map(this::locationToPosition) + .collect(Collectors.toList()); + ServerLevel originLevel = ((CraftWorld) originWorld).getHandle(); + ServerLevel destinationLevel = ((CraftWorld) destinationWorld).getHandle(); //******************************************* //* Step two: Get the tiles * //******************************************* @@ -154,26 +73,23 @@ public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation disp //get the tiles for (int i = 0, positionsSize = positions.size(); i < positionsSize; i++) { BlockPos position = positions.get(i); - if (oldNativeWorld.getBlockState(position) == Blocks.AIR.defaultBlockState()) + if (originLevel.getBlockState(position) == Blocks.AIR.defaultBlockState()) continue; - - BlockEntity tile = removeBlockEntity(oldNativeWorld, position); + BlockEntity tile = removeBlockEntity(originLevel, position); if (tile != null) tiles.add(new TileHolder(tile,position)); //get the nextTick to move with the tile - ScheduledTick tickHere = tickProvider.getNextTick(nativeWorld, position); + ScheduledTick tickHere = tickProvider.getNextTick(destinationLevel, position); if (tickHere != null) { - ((LevelChunkTicks) nativeWorld.getChunkAt(position).getBlockTicks()).removeIf( - (Predicate) scheduledTick -> scheduledTick.equals(tickHere)); + var levelTicks = ((LevelChunkTicks) destinationLevel.getChunkAt(position).getBlockTicks()); + levelTicks.removeIf(tickHere::equals); ticks.add(new TickHolder(tickHere, position)); } - } //******************************************* - //* Step three: Translate all the blocks * + //* Step three: Transform all the blocks * //******************************************* - // blockedByWater=false means an ocean-going vessel //TODO: Simplify //TODO: go by chunks //TODO: Don't move unnecessary blocks @@ -182,32 +98,32 @@ public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation disp List newPositions = new ArrayList<>(); for (int i = 0, positionsSize = positions.size(); i < positionsSize; i++) { BlockPos position = positions.get(i); - blockData.add(oldNativeWorld.getBlockState(position)); - newPositions.add(position.offset(translateVector)); + blockData.add(originLevel.getBlockState(position)); + newPositions.add(transformPosition(transformation, position)); } //create the new block for (int i = 0, positionSize = newPositions.size(); i < positionSize; i++) { - setBlockFast(nativeWorld, newPositions.get(i), blockData.get(i)); + setBlockFast(destinationLevel, newPositions.get(i), blockData.get(i)); } //******************************************* //* Step four: replace all the tiles * //******************************************* //TODO: go by chunks for (TileHolder tileHolder : tiles) - moveBlockEntity(nativeWorld, tileHolder.getTilePosition().offset(translateVector), tileHolder.getTile()); + moveBlockEntity(destinationLevel, transformPosition(transformation, tileHolder.tilePosition()), tileHolder.tile()); for (TickHolder tickHolder : ticks) { - final long currentTime = nativeWorld.getGameTime(); - nativeWorld.getBlockTicks().schedule(new ScheduledTick<>((Block) tickHolder.getTick().type(), tickHolder.getTickPosition().offset(translateVector), tickHolder.getTick().triggerTick() - currentTime, tickHolder.getTick().priority(), tickHolder.getTick().subTickOrder())); + final long currentTime = destinationLevel.getGameTime(); + destinationLevel.getBlockTicks().schedule(new ScheduledTick<>((Block) tickHolder.tick().type(), transformPosition(transformation, tickHolder.tickPosition()), tickHolder.tick().triggerTick() - currentTime, tickHolder.tick().priority(), tickHolder.tick().subTickOrder())); } //******************************************* //* Step five: Destroy the leftovers * //******************************************* List deletePositions = positions; - if (oldNativeWorld == nativeWorld) + if (originLevel == destinationLevel) deletePositions = CollectionUtils.filter(positions, newPositions); for (int i = 0, deletePositionsSize = deletePositions.size(); i < deletePositionsSize; i++) { BlockPos position = deletePositions.get(i); - setBlockFast(oldNativeWorld, position, Blocks.AIR.defaultBlockState()); + setBlockFast(originLevel, position, Blocks.AIR.defaultBlockState()); } } @@ -221,6 +137,11 @@ private BlockPos locationToPosition(@NotNull MovecraftLocation loc) { return new BlockPos(loc.getX(), loc.getY(), loc.getZ()); } + @NotNull @Contract(pure = true) + private BlockPos transformPosition(@NotNull AffineTransformation transformation, @NotNull BlockPos pos){ + return locationToPosition(transformation.apply(new MovecraftLocation(pos.getX(), pos.getY(), pos.getZ()))); + } + private void setBlockFast(@NotNull Level world, @NotNull BlockPos position, @NotNull BlockState data) { LevelChunk chunk = world.getChunkAt(position); int chunkSection = (position.getY() >> 4) - chunk.getMinSection(); @@ -260,20 +181,10 @@ public void setBlockFast(@NotNull Location location, @NotNull MovecraftRotation setBlockFast(world, BlockPos, blockData); } - @Override - public @Nullable Location getAccessLocation(@NotNull InventoryView inventoryView) { - // Not needed for 1.20+, remove when dropping support for 1.18.2 - return null; - } - - @Override - public void setAccessLocation(@NotNull InventoryView inventoryView, @NotNull Location location) { - // Not needed for 1.20+, remove when dropping support for 1.18.2 - } - private void moveBlockEntity(@NotNull Level nativeWorld, @NotNull BlockPos newPosition, @NotNull BlockEntity tile) { LevelChunk chunk = nativeWorld.getChunkAt(newPosition); try { + //noinspection JavaReflectionMemberAccess var positionField = BlockEntity.class.getDeclaredField("o"); // o is obfuscated worldPosition UnsafeUtils.setField(positionField, tile, newPosition); } @@ -290,49 +201,7 @@ private void moveBlockEntity(@NotNull Level nativeWorld, @NotNull BlockPos newPo chunk.blockEntities.put(newPosition, tile); } - private static class TileHolder { - @NotNull - private final BlockEntity tile; - @NotNull - private final BlockPos tilePosition; - - public TileHolder(@NotNull BlockEntity tile, @NotNull BlockPos tilePosition) { - this.tile = tile; - this.tilePosition = tilePosition; - } - - - @NotNull - public BlockEntity getTile() { - return tile; - } - - @NotNull - public BlockPos getTilePosition() { - return tilePosition; - } - } + private record TileHolder(@NotNull BlockEntity tile, @NotNull BlockPos tilePosition) { } - private static class TickHolder { - @NotNull - private final ScheduledTick tick; - @NotNull - private final BlockPos tickPosition; - - public TickHolder(@NotNull ScheduledTick tick, @NotNull BlockPos tilePosition) { - this.tick = tick; - this.tickPosition = tilePosition; - } - - - @NotNull - public ScheduledTick getTick() { - return tick; - } - - @NotNull - public BlockPos getTickPosition() { - return tickPosition; - } - } + private record TickHolder(@NotNull ScheduledTick tick, @NotNull BlockPos tickPosition) { } } \ No newline at end of file diff --git a/v1_21/src/main/java/net/countercraft/movecraft/compat/v1_21/IWorldHandler.java b/v1_21/src/main/java/net/countercraft/movecraft/compat/v1_21/IWorldHandler.java index 30f1e8182..eaec12eba 100644 --- a/v1_21/src/main/java/net/countercraft/movecraft/compat/v1_21/IWorldHandler.java +++ b/v1_21/src/main/java/net/countercraft/movecraft/compat/v1_21/IWorldHandler.java @@ -3,14 +3,13 @@ import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.MovecraftRotation; import net.countercraft.movecraft.WorldHandler; -import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.util.AffineTransformation; import net.countercraft.movecraft.util.CollectionUtils; import net.countercraft.movecraft.util.MathUtils; import net.countercraft.movecraft.util.UnsafeUtils; +import net.countercraft.movecraft.util.hitboxes.HitBox; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.inventory.ContainerLevelAccess; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; @@ -23,21 +22,21 @@ import net.minecraft.world.ticks.ScheduledTick; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.data.CraftBlockData; -import org.bukkit.craftbukkit.inventory.CraftInventoryView; -import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.Field; -import java.util.*; -import java.util.function.Predicate; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "ForLoopReplaceableByForEach"}) public class IWorldHandler extends WorldHandler { - private static final Rotation ROTATION[]; + private static final Rotation[] ROTATION; static { ROTATION = new Rotation[3]; @@ -55,93 +54,17 @@ public IWorldHandler() { } @Override - public void rotateCraft(@NotNull Craft craft, @NotNull MovecraftLocation originPoint, @NotNull MovecraftRotation rotation) { + public void transformHitBox(@NotNull HitBox hitbox, @NotNull AffineTransformation transformation, @NotNull World originWorld, @NotNull World destinationWorld) { //******************************************* //* Step one: Convert to Positions * //******************************************* - HashMap rotatedPositions = new HashMap<>(); - MovecraftRotation counterRotation = rotation == MovecraftRotation.CLOCKWISE ? MovecraftRotation.ANTICLOCKWISE : MovecraftRotation.CLOCKWISE; - for (MovecraftLocation newLocation : craft.getHitBox()) { - rotatedPositions.put(locationToPosition(MathUtils.rotateVec(counterRotation, newLocation.subtract(originPoint)).add(originPoint)), locationToPosition(newLocation)); - } - //******************************************* - //* Step two: Get the tiles * - //******************************************* - ServerLevel nativeWorld = ((CraftWorld) craft.getWorld()).getHandle(); - List tiles = new ArrayList<>(); - List ticks = new ArrayList<>(); - //get the tiles - for (BlockPos position : rotatedPositions.keySet()) { - //BlockEntity tile = nativeWorld.removeBlockEntity(position); - BlockEntity tile = removeBlockEntity(nativeWorld, position); - if (tile != null) - tiles.add(new TileHolder(tile, position)); - - //get the nextTick to move with the tile - ScheduledTick tickHere = tickProvider.getNextTick(nativeWorld, position); - if (tickHere != null) { - ((LevelChunkTicks) nativeWorld.getChunkAt(position).getBlockTicks()).removeIf( - (Predicate) scheduledTick -> scheduledTick.equals(tickHere)); - ticks.add(new TickHolder(tickHere, position)); - } - } - - //******************************************* - //* Step three: Translate all the blocks * - //******************************************* - // blockedByWater=false means an ocean-going vessel - //TODO: Simplify - //TODO: go by chunks - //TODO: Don't move unnecessary blocks - //get the blocks and rotate them - HashMap blockData = new HashMap<>(); - for (BlockPos position : rotatedPositions.keySet()) { - blockData.put(position, nativeWorld.getBlockState(position).rotate(ROTATION[rotation.ordinal()])); - } - //create the new block - for (Map.Entry entry : blockData.entrySet()) { - setBlockFast(nativeWorld, rotatedPositions.get(entry.getKey()), entry.getValue()); - } - - - //******************************************* - //* Step four: replace all the tiles * - //******************************************* - //TODO: go by chunks - for (TileHolder tileHolder : tiles) - moveBlockEntity(nativeWorld, rotatedPositions.get(tileHolder.getTilePosition()), tileHolder.getTile()); - for (TickHolder tickHolder : ticks) { - final long currentTime = nativeWorld.serverLevelData.getGameTime(); - nativeWorld.getBlockTicks().schedule(new ScheduledTick<>( - (Block) tickHolder.getTick().type(), - rotatedPositions.get(tickHolder.getTick().pos()), - tickHolder.getTick().triggerTick() - currentTime, - tickHolder.getTick().priority(), - tickHolder.getTick().subTickOrder())); - } - - //******************************************* - //* Step five: Destroy the leftovers * - //******************************************* - //TODO: add support for pass-through - Collection deletePositions = CollectionUtils.filter(rotatedPositions.keySet(), rotatedPositions.values()); - for (BlockPos position : deletePositions) { - setBlockFast(nativeWorld, position, Blocks.AIR.defaultBlockState()); - } - } - - @Override - public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation displacement, @NotNull org.bukkit.World world) { - //TODO: Add support for rotations - //A craftTranslateCommand should only occur if the craft is moving to a valid position - //******************************************* - //* Step one: Convert to Positions * - //******************************************* - BlockPos translateVector = locationToPosition(displacement); - List positions = new ArrayList<>(craft.getHitBox().size()); - craft.getHitBox().forEach((movecraftLocation) -> positions.add(locationToPosition((movecraftLocation)).subtract(translateVector))); - ServerLevel oldNativeWorld = ((CraftWorld) craft.getWorld()).getHandle(); - ServerLevel nativeWorld = ((CraftWorld) world).getHandle(); + List positions = hitbox + .stream() + .map(transformation::apply) + .map(this::locationToPosition) + .collect(Collectors.toList()); + ServerLevel originLevel = ((CraftWorld) originWorld).getHandle(); + ServerLevel destinationLevel = ((CraftWorld) destinationWorld).getHandle(); //******************************************* //* Step two: Get the tiles * //******************************************* @@ -150,25 +73,23 @@ public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation disp //get the tiles for (int i = 0, positionsSize = positions.size(); i < positionsSize; i++) { BlockPos position = positions.get(i); - if (oldNativeWorld.getBlockState(position) == Blocks.AIR.defaultBlockState()) + if (originLevel.getBlockState(position) == Blocks.AIR.defaultBlockState()) continue; - //BlockEntity tile = nativeWorld.removeBlockEntity(position); - BlockEntity tile = removeBlockEntity(oldNativeWorld, position); + BlockEntity tile = removeBlockEntity(originLevel, position); if (tile != null) tiles.add(new TileHolder(tile,position)); //get the nextTick to move with the tile - ScheduledTick tickHere = tickProvider.getNextTick(nativeWorld, position); + ScheduledTick tickHere = tickProvider.getNextTick(destinationLevel, position); if (tickHere != null) { - ((LevelChunkTicks) nativeWorld.getChunkAt(position).getBlockTicks()).removeIf( - (Predicate) scheduledTick -> scheduledTick.equals(tickHere)); + var levelTicks = ((LevelChunkTicks) destinationLevel.getChunkAt(position).getBlockTicks()); + levelTicks.removeIf(tickHere::equals); ticks.add(new TickHolder(tickHere, position)); } } //******************************************* - //* Step three: Translate all the blocks * + //* Step three: Transform all the blocks * //******************************************* - // blockedByWater=false means an ocean-going vessel //TODO: Simplify //TODO: go by chunks //TODO: Don't move unnecessary blocks @@ -177,32 +98,32 @@ public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation disp List newPositions = new ArrayList<>(); for (int i = 0, positionsSize = positions.size(); i < positionsSize; i++) { BlockPos position = positions.get(i); - blockData.add(oldNativeWorld.getBlockState(position)); - newPositions.add(position.offset(translateVector)); + blockData.add(originLevel.getBlockState(position)); + newPositions.add(transformPosition(transformation, position)); } //create the new block for (int i = 0, positionSize = newPositions.size(); i < positionSize; i++) { - setBlockFast(nativeWorld, newPositions.get(i), blockData.get(i)); + setBlockFast(destinationLevel, newPositions.get(i), blockData.get(i)); } //******************************************* //* Step four: replace all the tiles * //******************************************* //TODO: go by chunks for (TileHolder tileHolder : tiles) - moveBlockEntity(nativeWorld, tileHolder.getTilePosition().offset(translateVector), tileHolder.getTile()); + moveBlockEntity(destinationLevel, transformPosition(transformation, tileHolder.tilePosition()), tileHolder.tile()); for (TickHolder tickHolder : ticks) { - final long currentTime = nativeWorld.getGameTime(); - nativeWorld.getBlockTicks().schedule(new ScheduledTick<>((Block) tickHolder.getTick().type(), tickHolder.getTickPosition().offset(translateVector), tickHolder.getTick().triggerTick() - currentTime, tickHolder.getTick().priority(), tickHolder.getTick().subTickOrder())); + final long currentTime = destinationLevel.getGameTime(); + destinationLevel.getBlockTicks().schedule(new ScheduledTick<>((Block) tickHolder.tick().type(), transformPosition(transformation, tickHolder.tickPosition()), tickHolder.tick().triggerTick() - currentTime, tickHolder.tick().priority(), tickHolder.tick().subTickOrder())); } //******************************************* //* Step five: Destroy the leftovers * //******************************************* List deletePositions = positions; - if (oldNativeWorld == nativeWorld) + if (originLevel == destinationLevel) deletePositions = CollectionUtils.filter(positions, newPositions); for (int i = 0, deletePositionsSize = deletePositions.size(); i < deletePositionsSize; i++) { BlockPos position = deletePositions.get(i); - setBlockFast(oldNativeWorld, position, Blocks.AIR.defaultBlockState()); + setBlockFast(originLevel, position, Blocks.AIR.defaultBlockState()); } } @@ -216,6 +137,11 @@ private BlockPos locationToPosition(@NotNull MovecraftLocation loc) { return new BlockPos(loc.getX(), loc.getY(), loc.getZ()); } + @NotNull @Contract(pure = true) + private BlockPos transformPosition(@NotNull AffineTransformation transformation, @NotNull BlockPos pos){ + return locationToPosition(transformation.apply(new MovecraftLocation(pos.getX(), pos.getY(), pos.getZ()))); + } + private void setBlockFast(@NotNull Level world, @NotNull BlockPos position, @NotNull BlockState data) { LevelChunk chunk = world.getChunkAt(position); int chunkSection = (position.getY() >> 4) - chunk.getMinSection(); @@ -255,20 +181,10 @@ public void setBlockFast(@NotNull Location location, @NotNull MovecraftRotation setBlockFast(world, BlockPos, blockData); } - @Override - public @Nullable Location getAccessLocation(@NotNull InventoryView inventoryView) { - // Not needed for 1.20+, remove when dropping support for 1.18.2 - return null; - } - - @Override - public void setAccessLocation(@NotNull InventoryView inventoryView, @NotNull Location location) { - // Not needed for 1.20+, remove when dropping support for 1.18.2 - } - private void moveBlockEntity(@NotNull Level nativeWorld, @NotNull BlockPos newPosition, @NotNull BlockEntity tile) { LevelChunk chunk = nativeWorld.getChunkAt(newPosition); try { + //noinspection JavaReflectionMemberAccess var positionField = BlockEntity.class.getDeclaredField("o"); // o is obfuscated worldPosition UnsafeUtils.setField(positionField, tile, newPosition); } @@ -285,49 +201,7 @@ private void moveBlockEntity(@NotNull Level nativeWorld, @NotNull BlockPos newPo chunk.blockEntities.put(newPosition, tile); } - private static class TileHolder { - @NotNull - private final BlockEntity tile; - @NotNull - private final BlockPos tilePosition; + private record TileHolder(@NotNull BlockEntity tile, @NotNull BlockPos tilePosition) { } - public TileHolder(@NotNull BlockEntity tile, @NotNull BlockPos tilePosition) { - this.tile = tile; - this.tilePosition = tilePosition; - } - - - @NotNull - public BlockEntity getTile() { - return tile; - } - - @NotNull - public BlockPos getTilePosition() { - return tilePosition; - } - } - - private static class TickHolder { - @NotNull - private final ScheduledTick tick; - @NotNull - private final BlockPos tickPosition; - - public TickHolder(@NotNull ScheduledTick tick, @NotNull BlockPos tilePosition) { - this.tick = tick; - this.tickPosition = tilePosition; - } - - - @NotNull - public ScheduledTick getTick() { - return tick; - } - - @NotNull - public BlockPos getTickPosition() { - return tickPosition; - } - } + private record TickHolder(@NotNull ScheduledTick tick, @NotNull BlockPos tickPosition) { } } \ No newline at end of file