diff --git a/modules/Movecraft/pom.xml b/modules/Movecraft/pom.xml index 448f18ce6..dc9f29f22 100644 --- a/modules/Movecraft/pom.xml +++ b/modules/Movecraft/pom.xml @@ -33,6 +33,12 @@ v1_14_R1 jar + + net.countercraft + movecraft-v1_15_R1 + v1_15_R1 + jar + net.countercraft movecraft-v1_16_R1 diff --git a/modules/v1_15_R1/pom.xml b/modules/v1_15_R1/pom.xml new file mode 100644 index 000000000..ac7eb6b50 --- /dev/null +++ b/modules/v1_15_R1/pom.xml @@ -0,0 +1,61 @@ + + + + movecraft-parent + net.countercraft + parent + ../../pom.xml + + 4.0.0 + + movecraft-v1_15_R1 + Movecraft-v1_15_R1 + v1_15_R1 + jar + + + org.bukkit + craftbukkit + 1.15.2-R0.1-SNAPSHOT + provided + + + net.countercraft + movecraft-api + API + jar + + + + src/main/java + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + net/countercraft/movecraft/compat/v1_15_R1/** + net/countercraft/movecraft/support/v1_15_R1/** + + 11 + 11 + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + net/countercraft/movecraft/compat/v1_15_R1/** + net/countercraft/movecraft/support/v1_15_R1/** + + + + + + + \ No newline at end of file diff --git a/modules/v1_15_R1/src/main/java/net/countercraft/movecraft/compat/v1_15_R1/IWorldHandler.java b/modules/v1_15_R1/src/main/java/net/countercraft/movecraft/compat/v1_15_R1/IWorldHandler.java new file mode 100644 index 000000000..64d737e09 --- /dev/null +++ b/modules/v1_15_R1/src/main/java/net/countercraft/movecraft/compat/v1_15_R1/IWorldHandler.java @@ -0,0 +1,320 @@ +package net.countercraft.movecraft.compat.v1_15_R1; + +import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.Rotation; +import net.countercraft.movecraft.WorldHandler; +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.util.CollectionUtils; +import net.countercraft.movecraft.util.MathUtils; +import net.minecraft.server.v1_15_R1.Block; +import net.minecraft.server.v1_15_R1.BlockPosition; +import net.minecraft.server.v1_15_R1.Blocks; +import net.minecraft.server.v1_15_R1.Chunk; +import net.minecraft.server.v1_15_R1.ChunkSection; +import net.minecraft.server.v1_15_R1.EntityPlayer; +import net.minecraft.server.v1_15_R1.EnumBlockRotation; +import net.minecraft.server.v1_15_R1.IBlockData; +import net.minecraft.server.v1_15_R1.NextTickListEntry; +import net.minecraft.server.v1_15_R1.PacketPlayOutPosition; +import net.minecraft.server.v1_15_R1.PlayerConnection; +import net.minecraft.server.v1_15_R1.TileEntity; +import net.minecraft.server.v1_15_R1.World; +import net.minecraft.server.v1_15_R1.WorldServer; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.v1_15_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_15_R1.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@SuppressWarnings("unused") +public class IWorldHandler extends WorldHandler { + private static final EnumBlockRotation ROTATION[]; + static { + ROTATION = new EnumBlockRotation[3]; + ROTATION[Rotation.NONE.ordinal()] = EnumBlockRotation.NONE; + ROTATION[Rotation.CLOCKWISE.ordinal()] = EnumBlockRotation.CLOCKWISE_90; + ROTATION[Rotation.ANTICLOCKWISE.ordinal()] = EnumBlockRotation.COUNTERCLOCKWISE_90; + } + private final NextTickProvider tickProvider = new NextTickProvider(); + private MethodHandle internalTeleportMH; + + public IWorldHandler() { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + Method teleport = null; + try { + teleport = PlayerConnection.class.getDeclaredMethod("a", double.class, double.class, double.class, float.class, float.class, Set.class); + teleport.setAccessible(true); + internalTeleportMH = lookup.unreflect(teleport); + + } catch (NoSuchMethodException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + @Override + public void addPlayerLocation(Player player, double x, double y, double z, float yaw, float pitch){ + EntityPlayer ePlayer = ((CraftPlayer) player).getHandle(); + if(internalTeleportMH == null) { + //something went wrong + super.addPlayerLocation(player, x, y, z, yaw, pitch); + return; + } + try { + internalTeleportMH.invoke(ePlayer.playerConnection, x, y, z, yaw, pitch, EnumSet.allOf(PacketPlayOutPosition.EnumPlayerTeleportFlags.class)); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + } + + @Override + public void rotateCraft(@NotNull Craft craft, @NotNull MovecraftLocation originPoint, @NotNull Rotation rotation) { + //******************************************* + //* Step one: Convert to Positions * + //******************************************* + HashMap rotatedPositions = new HashMap<>(); + Rotation counterRotation = rotation == Rotation.CLOCKWISE ? Rotation.ANTICLOCKWISE : Rotation.CLOCKWISE; + for(MovecraftLocation newLocation : craft.getHitBox()){ + rotatedPositions.put(locationToPosition(MathUtils.rotateVec(counterRotation, newLocation.subtract(originPoint)).add(originPoint)),locationToPosition(newLocation)); + } + //******************************************* + //* Step two: Get the tiles * + //******************************************* + WorldServer nativeWorld = ((CraftWorld) craft.getWorld()).getHandle(); + List tiles = new ArrayList<>(); + //get the tiles + for(BlockPosition position : rotatedPositions.keySet()){ + //TileEntity tile = nativeWorld.removeTileEntity(position); + TileEntity tile = removeTileEntity(nativeWorld,position); + if(tile == null) + continue; + tile.a(ROTATION[rotation.ordinal()]); + //get the nextTick to move with the tile + tiles.add(new TileHolder(tile, tickProvider.getNextTick(nativeWorld,position), 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(BlockPosition position : rotatedPositions.keySet()){ + blockData.put(position,nativeWorld.getType(position).a(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){ + moveTileEntity(nativeWorld, rotatedPositions.get(tileHolder.getTilePosition()),tileHolder.getTile()); + if(tileHolder.getNextTick()==null) + continue; + final long currentTime = nativeWorld.worldData.getTime(); + nativeWorld.getBlockTickList().a(rotatedPositions.get(tileHolder.getNextTick().a), (Block)tileHolder.getNextTick().b(), (int) (tileHolder.getNextTick().b - currentTime), tileHolder.getNextTick().c); + } + + //******************************************* + //* Step five: Destroy the leftovers * + //******************************************* + //TODO: add support for pass-through + Collection deletePositions = CollectionUtils.filter(rotatedPositions.keySet(),rotatedPositions.values()); + for(BlockPosition position : deletePositions){ + setBlockFast(nativeWorld, position, Blocks.AIR.getBlockData()); + } + } + + @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 * + //******************************************* + BlockPosition translateVector = locationToPosition(displacement); + List positions = new ArrayList<>(craft.getHitBox().size()); + craft.getHitBox().forEach((movecraftLocation) -> positions.add(locationToPosition((movecraftLocation)).b(translateVector))); + WorldServer oldNativeWorld = ((CraftWorld) craft.getWorld()).getHandle(); + World nativeWorld = ((CraftWorld) world).getHandle(); + //******************************************* + //* Step two: Get the tiles * + //******************************************* + List tiles = new ArrayList<>(); + //get the tiles + for (int i = 0, positionsSize = positions.size(); i < positionsSize; i++) { + BlockPosition position = positions.get(i); + if (oldNativeWorld.getType(position) == Blocks.AIR.getBlockData()) + continue; + //TileEntity tile = nativeWorld.removeTileEntity(position); + TileEntity tile = removeTileEntity(oldNativeWorld, position); + if (tile == null) + continue; + //get the nextTick to move with the tile + + //nativeWorld.capturedTileEntities.remove(position); + //nativeWorld.getChunkAtWorldCoords(position).getTileEntities().remove(position); + tiles.add(new TileHolder(tile, tickProvider.getNextTick(oldNativeWorld, position), 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 translate the positions + List blockData = new ArrayList<>(); + List newPositions = new ArrayList<>(); + for (int i = 0, positionsSize = positions.size(); i < positionsSize; i++) { + BlockPosition position = positions.get(i); + blockData.add(oldNativeWorld.getType(position)); + newPositions.add(position.a(translateVector)); + } + //create the new block + for(int i = 0, positionSize = newPositions.size(); i deletePositions = positions; + if (oldNativeWorld == nativeWorld) deletePositions = CollectionUtils.filter(positions,newPositions); + for (int i = 0, deletePositionsSize = deletePositions.size(); i < deletePositionsSize; i++) { + BlockPosition position = deletePositions.get(i); + setBlockFast(oldNativeWorld, position, Blocks.AIR.getBlockData()); + } + } + + @Nullable + private TileEntity removeTileEntity(@NotNull World world, @NotNull BlockPosition position){ + return world.getChunkAtWorldCoords(position).tileEntities.remove(position); + } + + @NotNull + private BlockPosition locationToPosition(@NotNull MovecraftLocation loc) { + return new BlockPosition(loc.getX(), loc.getY(), loc.getZ()); + } + + private void setBlockFast(@NotNull World world, @NotNull BlockPosition position,@NotNull IBlockData data) { + Chunk chunk = world.getChunkAtWorldCoords(position); + ChunkSection chunkSection = chunk.getSections()[position.getY()>>4]; + if (chunkSection == null) { + // Put a GLASS block to initialize the section. It will be replaced next with the real block. + chunk.setType(position, Blocks.GLASS.getBlockData(), false); + chunkSection = chunk.getSections()[position.getY() >> 4]; + } + if(chunkSection.getType(position.getX()&15, position.getY()&15, position.getZ()&15).equals(data)){ + //Block is already of correct type and data, don't overwrite + return; + } + + chunkSection.setType(position.getX()&15, position.getY()&15, position.getZ()&15, data); + world.notify(position, data, data, 3); + chunk.markDirty(); + } + + @Override + public void setBlockFast(@NotNull Location location, @NotNull BlockData data){ + setBlockFast(location, Rotation.NONE, data); + } + + @Override + public void setBlockFast(@NotNull Location location, @NotNull Rotation rotation, @NotNull BlockData data) { + IBlockData blockData; + if(data instanceof CraftBlockData){ + blockData = ((CraftBlockData) data).getState(); + } else { + blockData = (IBlockData) data; + } + blockData = blockData.a(ROTATION[rotation.ordinal()]); + World world = ((CraftWorld)(location.getWorld())).getHandle(); + BlockPosition blockPosition = locationToPosition(bukkit2MovecraftLoc(location)); + setBlockFast(world,blockPosition,blockData); + } + + @Override + public void disableShadow(@NotNull Material type) { + // Disabled + } + + private static MovecraftLocation bukkit2MovecraftLoc(Location l) { + return new MovecraftLocation(l.getBlockX(), l.getBlockY(), l.getBlockZ()); + } + + private void moveTileEntity(@NotNull World nativeWorld, @NotNull BlockPosition newPosition, @NotNull TileEntity tile){ + Chunk chunk = nativeWorld.getChunkAtWorldCoords(newPosition); + tile.invalidateBlockCache(); + tile.setLocation(nativeWorld, newPosition); + if(nativeWorld.captureBlockStates) { + tile.setLocation(nativeWorld, newPosition); + nativeWorld.capturedTileEntities.put(newPosition, tile); + return; + } + chunk.tileEntities.put(newPosition, tile); + } + + private class TileHolder{ + @NotNull private final TileEntity tile; + @Nullable + private final NextTickListEntry nextTick; + @NotNull private final BlockPosition tilePosition; + + public TileHolder(@NotNull TileEntity tile, @Nullable NextTickListEntry nextTick, @NotNull BlockPosition tilePosition){ + this.tile = tile; + this.nextTick = nextTick; + this.tilePosition = tilePosition; + } + + + @NotNull + public TileEntity getTile() { + return tile; + } + + @Nullable + public NextTickListEntry getNextTick() { + return nextTick; + } + + @NotNull + public BlockPosition getTilePosition() { + return tilePosition; + } + } +} \ No newline at end of file diff --git a/modules/v1_15_R1/src/main/java/net/countercraft/movecraft/compat/v1_15_R1/NextTickProvider.java b/modules/v1_15_R1/src/main/java/net/countercraft/movecraft/compat/v1_15_R1/NextTickProvider.java new file mode 100644 index 000000000..62f15bfdd --- /dev/null +++ b/modules/v1_15_R1/src/main/java/net/countercraft/movecraft/compat/v1_15_R1/NextTickProvider.java @@ -0,0 +1,31 @@ +package net.countercraft.movecraft.compat.v1_15_R1; + +import net.minecraft.server.v1_15_R1.BlockPosition; +import net.minecraft.server.v1_15_R1.NextTickListEntry; +import net.minecraft.server.v1_15_R1.WorldServer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class NextTickProvider { + + @Nullable + public NextTickListEntry getNextTick(@NotNull WorldServer world,@NotNull BlockPosition position){ + return null; + } + @NotNull + public Object fakeEntry(@NotNull BlockPosition position){ + return new Object(){ + @Override + public int hashCode() { + return position.hashCode(); + } + @Override + public boolean equals(Object other){ + if (!(other instanceof NextTickListEntry)) { + return false; + } + return position.equals(((NextTickListEntry)other).a); + } + }; + } +} diff --git a/modules/v1_15_R1/src/main/java/net/countercraft/movecraft/support/v1_15_R1/IAsyncChunk.java b/modules/v1_15_R1/src/main/java/net/countercraft/movecraft/support/v1_15_R1/IAsyncChunk.java new file mode 100644 index 000000000..187945867 --- /dev/null +++ b/modules/v1_15_R1/src/main/java/net/countercraft/movecraft/support/v1_15_R1/IAsyncChunk.java @@ -0,0 +1,49 @@ +package net.countercraft.movecraft.support.v1_15_R1; + +import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.processing.WorldManager; +import net.countercraft.movecraft.support.AsyncChunk; +import net.minecraft.server.v1_15_R1.BlockPosition; +import net.minecraft.server.v1_15_R1.IBlockData; +import org.bukkit.Chunk; +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.v1_15_R1.CraftChunk; +import org.bukkit.craftbukkit.v1_15_R1.block.data.CraftBlockData; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public class IAsyncChunk extends AsyncChunk { + + public IAsyncChunk(@NotNull Chunk chunk) { + super(chunk); + } + + @NotNull + @Override + protected CraftChunk adapt(@NotNull org.bukkit.Chunk chunk) { + return (CraftChunk) chunk; + } + + @NotNull + @Override + public BlockState getState(@NotNull MovecraftLocation location) { + var block = chunk.getBlock(location.getX(), location.getY(), location.getZ()); + return WorldManager.INSTANCE.executeMain(block::getState); + } + + @Override + @NotNull + public Material getType(@NotNull MovecraftLocation location){ + return CraftBlockData.fromData(chunk.getHandle().getType(new BlockPosition(location.getX(), location.getY(), location.getZ()))).getMaterial(); + } + + @Override + @NotNull + public BlockData getData(@NotNull MovecraftLocation location){ + IBlockData data = chunk.getHandle().getType(new BlockPosition(location.getX(), location.getY(), location.getZ())); + return CraftBlockData.fromData(data); + } + +} diff --git a/pom.xml b/pom.xml index 4cefb6de6..55988ea5c 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ modules/api modules/v1_14_R1 + modules/v1_15_R1 modules/v1_16_R1 modules/v1_16_R3 modules/datapack