diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ec9a36f50..3e07872bf 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,94 +6,9 @@ on: types: [created, prereleased] jobs: - # Build 1.18.2 NMS - v1_18: - runs-on: ubuntu-latest - steps: - - name: Set up JDK 17 # 1.18.2 can only be built with Java 17 - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: '17' - - name: Cache 1.18.2 Maven package - id: cacheCaves - uses: actions/cache@v3 - with: - path: | - ~/.m2/repository/org/spigotmc/spigot/1.18.2-R0.1-SNAPSHOT/ - ~/.m2/repository/org/spigotmc/spigot-parent/ - ~/.m2/repository/org/spigotmc/minecraft-server/ - key: ${{ runner.os }}-v1_18 - restore-keys: ${{ runner.os }}-v1_18 - - name: Cache Maven packages - id: cacheMain - uses: actions/cache@v3 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-v1_18 - restore-keys: ${{ runner.os }}-m2-v1_18 - - - name: Setup BuildTools - run: mkdir BuildTools && wget -O BuildTools/BuildTools.jar https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar - - name: Check 1.18.2 Spigot - id: caves - run: test -f ~/.m2/repository/org/spigotmc/spigot/1.18.2-R0.1-SNAPSHOT/spigot-1.18.2-R0.1-SNAPSHOT.jar && echo "sucess=true" >> $GITHUB_OUTPUT || echo "sucess=false" >> $GITHUB_OUTPUT - - name: Check 1.18.2 Spigot (Mojang) - id: cavesMojang - run: test -f ~/.m2/repository/org/spigotmc/spigot/1.18.2-R0.1-SNAPSHOT/spigot-1.18.2-R0.1-SNAPSHOT-remapped-mojang.jar && echo "sucess=true" >> $GITHUB_OUTPUT || echo "sucess=false" >> $GITHUB_OUTPUT - - name: Check 1.18.2 Spigot (Obf) - id: cavesObf - run: test -f ~/.m2/repository/org/spigotmc/spigot/1.18.2-R0.1-SNAPSHOT/spigot-1.18.2-R0.1-SNAPSHOT-remapped-obf.jar && echo "sucess=true" >> $GITHUB_OUTPUT || echo "sucess=false" >> $GITHUB_OUTPUT - - name: Build 1.18.2 - if: steps.caves.outputs.sucess != 'true' || steps.cavesMojang.outputs.sucess != 'true' || steps.cavesObf.outputs.sucess != 'true' - run: cd BuildTools && java -jar BuildTools.jar --rev 1.18.2 --remapped - - # Build 1.20.6 NMS - v1_20: - runs-on: ubuntu-latest - steps: - - name: Set up JDK 21 # 1.20.6 can only be built with Java 21 - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: '21' - - name: Cache 1.20.6 Maven package - id: cacheWild_r2 - uses: actions/cache@v3 - with: - path: | - ~/.m2/repository/org/spigotmc/spigot/1.20.6-R0.1-SNAPSHOT/ - ~/.m2/repository/org/spigotmc/spigot-parent/ - ~/.m2/repository/org/spigotmc/minecraft-server/ - key: ${{ runner.os }}-v1_20 - restore-keys: ${{ runner.os }}-v1_20 - - name: Cache Maven packages - id: cacheMain - uses: actions/cache@v3 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-v1_20 - restore-keys: ${{ runner.os }}-m2-v1_20 - - - name: Setup BuildTools - run: mkdir BuildTools && wget -O BuildTools/BuildTools.jar https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar - - name: Check 1.20.6 Spigot - id: wild - run: test -f ~/.m2/repository/org/spigotmc/spigot/1.20.6-R0.1-SNAPSHOT/spigot-1.20.6-R0.1-SNAPSHOT.jar && echo "sucess=true" >> $GITHUB_OUTPUT || echo "sucess=false" >> $GITHUB_OUTPUT - - name: Check 1.20.6 Spigot (Mojang) - id: wildMojang - run: test -f ~/.m2/repository/org/spigotmc/spigot/1.20.6-R0.1-SNAPSHOT/spigot-1.20.6-R0.1-SNAPSHOT-remapped-mojang.jar && echo "sucess=true" >> $GITHUB_OUTPUT || echo "sucess=false" >> $GITHUB_OUTPUT - - name: Check 1.20.6 Spigot (Obf) - id: wildObf - run: test -f ~/.m2/repository/org/spigotmc/spigot/1.20.6-R0.1-SNAPSHOT/spigot-1.20.6-R0.1-SNAPSHOT-remapped-obf.jar && echo "sucess=true" >> $GITHUB_OUTPUT || echo "sucess=false" >> $GITHUB_OUTPUT - - name: Build 1.20.6 - if: steps.wild.outputs.sucess != 'true' || steps.wildMojang.outputs.sucess != 'true' || steps.wildObf.outputs.sucess != 'true' - run: cd BuildTools && java -jar BuildTools.jar --rev 1.20.6 --remapped - # Build Movecraft build: runs-on: ubuntu-latest - needs: [v1_18, v1_20] steps: - name: Checkout Movecraft @@ -109,24 +24,6 @@ jobs: path: ~/.m2 key: ${{ runner.os }}-m2 restore-keys: ${{ runner.os }}-m2 - - name: Cache 1.18.2 Maven package - uses: actions/cache@v3 - with: - path: | - ~/.m2/repository/org/spigotmc/spigot/1.18.2-R0.1-SNAPSHOT/ - ~/.m2/repository/org/spigotmc/spigot-parent/ - ~/.m2/repository/org/spigotmc/minecraft-server/ - key: ${{ runner.os }}-v1_18 - restore-keys: ${{ runner.os }}-v1_18 - - name: Cache 1.20.6 Maven package - uses: actions/cache@v3 - with: - path: | - ~/.m2/repository/org/spigotmc/spigot/1.20.6-R0.1-SNAPSHOT/ - ~/.m2/repository/org/spigotmc/spigot-parent/ - ~/.m2/repository/org/spigotmc/minecraft-server/ - key: ${{ runner.os }}-v1_20 - restore-keys: ${{ runner.os }}-v1_20 - name: Build with Maven run: mvn -T 1C -B package --file pom.xml diff --git a/Movecraft/build.gradle.kts b/Movecraft/build.gradle.kts index a6232319d..637c06337 100644 --- a/Movecraft/build.gradle.kts +++ b/Movecraft/build.gradle.kts @@ -8,6 +8,7 @@ java.toolchain.languageVersion = JavaLanguageVersion.of(17) dependencies { runtimeOnly(project(":movecraft-v1_18", "reobf")) runtimeOnly(project(":movecraft-v1_20", "reobf")) + runtimeOnly(project(":movecraft-v1_21", "reobf")) implementation(project(":movecraft-api")) compileOnly("org.yaml:snakeyaml:2.0") } @@ -21,6 +22,7 @@ tasks.shadowJar { include(project(":movecraft-api")) include(project(":movecraft-v1_18")) include(project(":movecraft-v1_20")) + include(project(":movecraft-v1_21")) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 2e45f7fa7..5a43a493e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,11 +5,13 @@ rootProject.name = "movecraft-parent" include(":movecraft-v1_18") include(":movecraft-v1_20") +include(":movecraft-v1_21") include(":movecraft-api") include(":movecraft-datapack") include(":movecraft") project(":movecraft-v1_18").projectDir = file("v1_18") project(":movecraft-v1_20").projectDir = file("v1_20") +project(":movecraft-v1_21").projectDir = file("v1_21") project(":movecraft-api").projectDir = file("api") project(":movecraft-datapack").projectDir = file("datapack") project(":movecraft").projectDir = file("Movecraft") diff --git a/v1_21/build.gradle.kts b/v1_21/build.gradle.kts new file mode 100644 index 000000000..8d4c44fcd --- /dev/null +++ b/v1_21/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("buildlogic.java-conventions") + id("io.papermc.paperweight.userdev") +} + +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + +dependencies { + api(project(":movecraft-api")) + paperweight.paperDevBundle("1.21-R0.1-SNAPSHOT") +} + +description = "Movecraft-v1_21" +paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION 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 new file mode 100644 index 000000000..722f1e027 --- /dev/null +++ b/v1_21/src/main/java/net/countercraft/movecraft/compat/v1_21/IWorldHandler.java @@ -0,0 +1,325 @@ +package net.countercraft.movecraft.compat.v1_21; + +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.CollectionUtils; +import net.countercraft.movecraft.util.MathUtils; +import net.countercraft.movecraft.util.UnsafeUtils; +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.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.ticks.ScheduledTick; +import org.bukkit.Location; +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.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.inventory.InventoryView; +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; + +@SuppressWarnings("unused") +public class IWorldHandler extends WorldHandler { + private static final Rotation ROTATION[]; + + static { + ROTATION = new Rotation[3]; + ROTATION[MovecraftRotation.NONE.ordinal()] = Rotation.NONE; + ROTATION[MovecraftRotation.CLOCKWISE.ordinal()] = Rotation.CLOCKWISE_90; + ROTATION[MovecraftRotation.ANTICLOCKWISE.ordinal()] = Rotation.COUNTERCLOCKWISE_90; + } + + private final NextTickProvider tickProvider = new NextTickProvider(); + + public IWorldHandler() { + String mappings = ((CraftMagicNumbers) CraftMagicNumbers.INSTANCE).getMappingsVersion(); + if (!mappings.equals("ee13f98a43b9c5abffdcc0bb24154460")) + throw new IllegalStateException("Movecraft is not compatible with this version of Minecraft 1.10: " + mappings); + } + + @Override + public void rotateCraft(@NotNull Craft craft, @NotNull MovecraftLocation originPoint, @NotNull MovecraftRotation rotation) { + //******************************************* + //* 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<>(); + //get the tiles + for (BlockPos position : rotatedPositions.keySet()) { + //BlockEntity tile = nativeWorld.removeBlockEntity(position); + BlockEntity tile = removeBlockEntity(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 (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()); + if (tileHolder.getNextTick() == null) + continue; + final long currentTime = nativeWorld.serverLevelData.getGameTime(); + nativeWorld.getBlockTicks().schedule(new ScheduledTick<>((Block) tileHolder.getNextTick().type(), rotatedPositions.get(tileHolder.getNextTick().pos()), tileHolder.getNextTick().triggerTick() - currentTime, tileHolder.getNextTick().priority(), tileHolder.getNextTick().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(); + //******************************************* + //* Step two: Get the tiles * + //******************************************* + List tiles = new ArrayList<>(); + //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()) + continue; + //BlockEntity tile = nativeWorld.removeBlockEntity(position); + BlockEntity tile = removeBlockEntity(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++) { + BlockPos position = positions.get(i); + blockData.add(oldNativeWorld.getBlockState(position)); + newPositions.add(position.offset(translateVector)); + } + //create the new block + for (int i = 0, positionSize = newPositions.size(); i < positionSize; i++) { + setBlockFast(nativeWorld, 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()); + if (tileHolder.getNextTick() == null) + continue; + final long currentTime = nativeWorld.getGameTime(); + nativeWorld.getBlockTicks().schedule(new ScheduledTick<>((Block) tileHolder.getNextTick().type(), tileHolder.getTilePosition().offset(translateVector), tileHolder.getNextTick().triggerTick() - currentTime, tileHolder.getNextTick().priority(), tileHolder.getNextTick().subTickOrder())); + } + //******************************************* + //* Step five: Destroy the leftovers * + //******************************************* + List deletePositions = positions; + if (oldNativeWorld == nativeWorld) + 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()); + } + } + + @Nullable + private BlockEntity removeBlockEntity(@NotNull Level world, @NotNull BlockPos position) { + return world.getChunkAt(position).blockEntities.remove(position); + } + + @NotNull + private BlockPos locationToPosition(@NotNull MovecraftLocation loc) { + return new BlockPos(loc.getX(), loc.getY(), loc.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(); + LevelChunkSection section = chunk.getSections()[chunkSection]; + if (section == null) { + // Put a GLASS block to initialize the section. It will be replaced next with the real block. + chunk.setBlockState(position, Blocks.GLASS.defaultBlockState(), false); + section = chunk.getSections()[chunkSection]; + } + if (section.getBlockState(position.getX() & 15, position.getY() & 15, position.getZ() & 15).equals(data)) { + //Block is already of correct type and data, don't overwrite + return; + } + section.setBlockState(position.getX() & 15, position.getY() & 15, position.getZ() & 15, data); + world.sendBlockUpdated(position, data, data, 3); + world.getLightEngine().checkBlock(position); // boolean corresponds to if chunk section empty + chunk.setUnsaved(true); + } + + @Override + public void setBlockFast(@NotNull Location location, @NotNull BlockData data) { + setBlockFast(location, MovecraftRotation.NONE, data); + } + + @Override + public void setBlockFast(@NotNull Location location, @NotNull MovecraftRotation rotation, @NotNull BlockData data) { + BlockState blockData; + if (data instanceof CraftBlockData) { + blockData = ((CraftBlockData) data).getState(); + } + else { + blockData = (BlockState) data; + } + blockData = blockData.rotate(ROTATION[rotation.ordinal()]); + Level world = ((CraftWorld) (location.getWorld())).getHandle(); + BlockPos BlockPos = locationToPosition(MathUtils.bukkit2MovecraftLoc(location)); + setBlockFast(world, BlockPos, blockData); + } + + @Override + public @Nullable Location getAccessLocation(@NotNull InventoryView inventoryView) { + AbstractContainerMenu menu = ((CraftInventoryView) inventoryView).getHandle(); + Field field = UnsafeUtils.getFieldOfType(ContainerLevelAccess.class, menu.getClass()); + if (field != null) { + try { + field.setAccessible(true); + return ((ContainerLevelAccess) field.get(menu)).getLocation(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + return null; + } + + @Override + public void setAccessLocation(@NotNull InventoryView inventoryView, @NotNull Location location) { + if (location.getWorld() == null) + return; + ServerLevel level = ((CraftWorld) location.getWorld()).getHandle(); + BlockPos position = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + ContainerLevelAccess access = ContainerLevelAccess.create(level, position); + + AbstractContainerMenu menu = ((CraftInventoryView) inventoryView).getHandle(); + UnsafeUtils.trySetFieldOfType(ContainerLevelAccess.class, menu, access); + } + + private void moveBlockEntity(@NotNull Level nativeWorld, @NotNull BlockPos newPosition, @NotNull BlockEntity tile) { + LevelChunk chunk = nativeWorld.getChunkAt(newPosition); + try { + var positionField = BlockEntity.class.getDeclaredField("o"); // o is obfuscated worldPosition + UnsafeUtils.setField(positionField, tile, newPosition); + } + catch (NoSuchFieldException e) { + e.printStackTrace(); + } + tile.setLevel(nativeWorld); + tile.clearRemoved(); + if (nativeWorld.captureBlockStates) { + nativeWorld.capturedTileEntities.put(newPosition, tile); + return; + } + chunk.setBlockEntity(tile); + chunk.blockEntities.put(newPosition, tile); + } + + private static class TileHolder { + @NotNull + private final BlockEntity tile; + @Nullable + private final ScheduledTick nextTick; + @NotNull + private final BlockPos tilePosition; + + public TileHolder(@NotNull BlockEntity tile, @Nullable ScheduledTick nextTick, @NotNull BlockPos tilePosition) { + this.tile = tile; + this.nextTick = nextTick; + this.tilePosition = tilePosition; + } + + + @NotNull + public BlockEntity getTile() { + return tile; + } + + @Nullable + public ScheduledTick getNextTick() { + return nextTick; + } + + @NotNull + public BlockPos getTilePosition() { + return tilePosition; + } + } +} \ No newline at end of file diff --git a/v1_21/src/main/java/net/countercraft/movecraft/compat/v1_21/NextTickProvider.java b/v1_21/src/main/java/net/countercraft/movecraft/compat/v1_21/NextTickProvider.java new file mode 100644 index 000000000..c29d9ddce --- /dev/null +++ b/v1_21/src/main/java/net/countercraft/movecraft/compat/v1_21/NextTickProvider.java @@ -0,0 +1,44 @@ +package net.countercraft.movecraft.compat.v1_21; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.ticks.LevelTicks; +import net.minecraft.world.ticks.ScheduledTick; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Queue; + +public class NextTickProvider { + + @Nullable + public ScheduledTick getNextTick(@NotNull ServerLevel world, @NotNull BlockPos position){ + LevelTicks tickList = world.getBlockTicks(); + var box = BoundingBox.encapsulatingPositions(List.of(position)); + if(box.isEmpty()){ + return null; + } + Queue> toRunThisTick; + try { + Field toRunThisTickField = LevelTicks.class.getDeclaredField("g"); // g is obfuscated toRunThisTick + toRunThisTickField.setAccessible(true); + toRunThisTick = (Queue>) toRunThisTickField.get(tickList); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + return null; + } + for (var iter = toRunThisTick.iterator(); iter.hasNext(); ) { + var next = iter.next(); + if (!next.pos().equals(position)) { + continue; + } + iter.remove(); + return next; + } + return null; + } +} diff --git a/v1_21/src/main/java/net/countercraft/movecraft/support/v1_21/IAsyncChunk.java b/v1_21/src/main/java/net/countercraft/movecraft/support/v1_21/IAsyncChunk.java new file mode 100644 index 000000000..ae9a32888 --- /dev/null +++ b/v1_21/src/main/java/net/countercraft/movecraft/support/v1_21/IAsyncChunk.java @@ -0,0 +1,61 @@ +package net.countercraft.movecraft.support.v1_21; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.processing.WorldManager; +import net.countercraft.movecraft.support.AsyncChunk; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.bukkit.Chunk; +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.CraftChunk; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public class IAsyncChunk extends AsyncChunk { + private final @NotNull LoadingCache stateCache = CacheBuilder.newBuilder().maximumSize(1000).build(new CacheLoader<>() { + @Override + public BlockState load(@NotNull MovecraftLocation movecraftLocation) { + var block = chunk.getBlock(movecraftLocation.getX(), movecraftLocation.getY(), movecraftLocation.getZ()); + return WorldManager.INSTANCE.executeMain(() -> block.getState()); + } + }); + + // getHandle needs to be access in the main thread as of 1.19.4 + private final ChunkAccess handle; + + public IAsyncChunk(@NotNull Chunk chunk) { + super(chunk); + handle = this.chunk.getHandle(ChunkStatus.FULL); + } + + @NotNull + @Override + protected CraftChunk adapt(@NotNull org.bukkit.Chunk chunk) { + return (CraftChunk) chunk; + } + + @NotNull + @Override + public BlockState getState(@NotNull MovecraftLocation location) { + return stateCache.getUnchecked(location); + } + + @Override + @NotNull + public Material getType(@NotNull MovecraftLocation location){ + return this.getData(location).getMaterial(); + } + + @Override + @NotNull + public BlockData getData(@NotNull MovecraftLocation location){ + return CraftBlockData.fromData(handle.getBlockState(new BlockPos(location.getX(), location.getY(), location.getZ()))); + } +} diff --git a/v1_21/src/main/java/net/countercraft/movecraft/support/v1_21/ISmoothTeleport.java b/v1_21/src/main/java/net/countercraft/movecraft/support/v1_21/ISmoothTeleport.java new file mode 100644 index 000000000..f7bdfff99 --- /dev/null +++ b/v1_21/src/main/java/net/countercraft/movecraft/support/v1_21/ISmoothTeleport.java @@ -0,0 +1,57 @@ +package net.countercraft.movecraft.support.v1_21; + +import net.countercraft.movecraft.SmoothTeleport; +import net.countercraft.movecraft.util.ReflectUtils; +import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.RelativeMovement; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Field; +import java.util.Set; + +/** + * Code derived from code taken with permission from MicleBrick + * https://www.spigotmc.org/threads/teleport-player-smoothly.317416/ + * Used for 1.21 + */ +public class ISmoothTeleport extends SmoothTeleport { + private final Field teleportPosField; + private final Field teleportAwaitField; + private final Field awaitingTeleportTimeField; + private final Field tickCountField; + + public ISmoothTeleport() throws NoSuchFieldException, ClassNotFoundException { + teleportPosField = ReflectUtils.getField(ServerGamePacketListenerImpl.class, "F"); // awaitingPositionFromClient + teleportAwaitField = ReflectUtils.getField(ServerGamePacketListenerImpl.class, "G"); // awaitingTeleport + awaitingTeleportTimeField = ReflectUtils.getField(ServerGamePacketListenerImpl.class, "H"); // awaitingTeleportTime + tickCountField = ReflectUtils.getField(ServerGamePacketListenerImpl.class, "o"); // tickCount + } + + public void teleport(Player player, @NotNull Location location, float yawChange, float pitchChange) { + double x = location.getX(); + double y = location.getY(); + double z = location.getZ(); + ServerPlayer handle = (ServerPlayer) ReflectUtils.getHandle(player); + + try { + handle.absMoveTo(x, y, z, handle.getXRot(), handle.getYRot()); + ServerGamePacketListenerImpl connection = handle.connection; + teleportPosField.set(connection, new Vec3(x, y, z)); + int teleportAwait = teleportAwaitField.getInt(connection) + 1; + if (teleportAwait == Integer.MAX_VALUE) + teleportAwait = 0; + teleportAwaitField.setInt(connection, teleportAwait); + awaitingTeleportTimeField.set(connection, tickCountField.get(connection)); + + ClientboundPlayerPositionPacket packet = new ClientboundPlayerPositionPacket(x, y, z, yawChange, pitchChange, Set.of(RelativeMovement.X_ROT, RelativeMovement.Y_ROT), teleportAwait); + connection.send(packet); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } +}