diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e20309259..4c055d332 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -35,7 +35,7 @@ jobs: run: ./gradlew clean shadowJar --parallel - name: Publish to GitHub Packages - if: ${{ github.event_name == 'release' }} + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') run: ./gradlew publish --parallel env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Movecraft/build.gradle.kts b/Movecraft/build.gradle.kts index 7d261568d..683722e23 100644 --- a/Movecraft/build.gradle.kts +++ b/Movecraft/build.gradle.kts @@ -68,7 +68,7 @@ hangarPublish { platforms { register(io.papermc.hangarpublishplugin.model.Platforms.PAPER) { jar.set(tasks.shadowJar.flatMap { it.archiveFile }) - platformVersions.set(listOf("1.18.2", "1.20.6", "1.21")) + platformVersions.set(listOf("1.18.2", "1.20.6", "1.21.1")) } } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/Movecraft.java b/Movecraft/src/main/java/net/countercraft/movecraft/Movecraft.java index d5f6eabbb..fe69859c1 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/Movecraft.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/Movecraft.java @@ -27,6 +27,7 @@ import net.countercraft.movecraft.features.contacts.ContactsCommand; import net.countercraft.movecraft.features.contacts.ContactsManager; import net.countercraft.movecraft.features.contacts.ContactsSign; +import net.countercraft.movecraft.features.fading.WreckManager; import net.countercraft.movecraft.features.status.StatusManager; import net.countercraft.movecraft.features.status.StatusSign; import net.countercraft.movecraft.listener.*; @@ -57,6 +58,7 @@ public class Movecraft extends JavaPlugin { private WorldHandler worldHandler; private SmoothTeleport smoothTeleport; private AsyncManager asyncManager; + private WreckManager wreckManager; public static synchronized Movecraft getInstance() { return instance; @@ -189,8 +191,10 @@ public void onEnable() { asyncManager.runTaskTimer(this, 0, 1); MapUpdateManager.getInstance().runTaskTimer(this, 0, 1); + CraftManager.initialize(datapackInitialized); Bukkit.getScheduler().runTaskTimer(this, WorldManager.INSTANCE::run, 0,1); + wreckManager = new WreckManager(WorldManager.INSTANCE); getServer().getPluginManager().registerEvents(new InteractListener(), this); @@ -332,4 +336,8 @@ public SmoothTeleport getSmoothTeleport() { public AsyncManager getAsyncManager() { return asyncManager; } + + public @NotNull WreckManager getWreckManager(){ + return wreckManager; + } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/async/AsyncManager.java b/Movecraft/src/main/java/net/countercraft/movecraft/async/AsyncManager.java index fc2cfe889..7825b86e2 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/async/AsyncManager.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/async/AsyncManager.java @@ -23,24 +23,26 @@ import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.async.rotation.RotationTask; import net.countercraft.movecraft.async.translation.TranslationTask; -import net.countercraft.movecraft.config.Settings; -import net.countercraft.movecraft.craft.*; +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.craft.CraftManager; +import net.countercraft.movecraft.craft.PilotedCraft; +import net.countercraft.movecraft.craft.PlayerCraft; +import net.countercraft.movecraft.craft.SinkingCraft; import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.events.CraftReleaseEvent; import net.countercraft.movecraft.mapUpdater.MapUpdateManager; -import net.countercraft.movecraft.mapUpdater.update.BlockCreateCommand; -import net.countercraft.movecraft.mapUpdater.update.UpdateCommand; -import net.countercraft.movecraft.util.hitboxes.HitBox; import net.kyori.adventure.text.Component; -import org.bukkit.Location; -import org.bukkit.Material; import org.bukkit.World; -import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import org.jetbrains.annotations.NotNull; -import java.util.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -49,14 +51,8 @@ public class AsyncManager extends BukkitRunnable { private final Map ownershipMap = new HashMap<>(); private final BlockingQueue finishedAlgorithms = new LinkedBlockingQueue<>(); private final Set clearanceSet = new HashSet<>(); - private final Map wrecks = new HashMap<>(); - private final Map wreckWorlds = new HashMap<>(); - private final Map> wreckPhases = new HashMap<>(); - private final Map> processedFadeLocs = new HashMap<>(); private final Map cooldownCache = new WeakHashMap<>(); - private long lastFadeCheck = 0; - public AsyncManager() {} public void submitTask(AsyncTask task, Craft c) { @@ -71,15 +67,6 @@ public void submitCompletedTask(AsyncTask task) { finishedAlgorithms.add(task); } - public void addWreck(Craft craft){ - if(craft.getCollapsedHitBox().isEmpty() || Settings.FadeWrecksAfter == 0){ - return; - } - wrecks.put(craft.getCollapsedHitBox(), System.currentTimeMillis()); - wreckWorlds.put(craft.getCollapsedHitBox(), craft.getWorld()); - wreckPhases.put(craft.getCollapsedHitBox(), craft.getPhaseBlocks()); - } - private void processAlgorithmQueue() { int runLength = 10; int queueLength = finishedAlgorithms.size(); @@ -325,68 +312,11 @@ private void processSinking() { } } - private void processFadingBlocks() { - if (Settings.FadeWrecksAfter == 0) - return; - long ticksElapsed = (System.currentTimeMillis() - lastFadeCheck) / 50; - if (ticksElapsed <= Settings.FadeTickCooldown) - return; - - List processed = new ArrayList<>(); - for(Map.Entry entry : wrecks.entrySet()){ - if (Settings.FadeWrecksAfter * 1000L > System.currentTimeMillis() - entry.getValue()) - continue; - - final HitBox hitBox = entry.getKey(); - final Map phaseBlocks = wreckPhases.get(hitBox); - final World world = wreckWorlds.get(hitBox); - List commands = new ArrayList<>(); - int fadedBlocks = 0; - if (!processedFadeLocs.containsKey(world)) - processedFadeLocs.put(world, new HashSet<>()); - - int maxFadeBlocks = (int) (hitBox.size() * (Settings.FadePercentageOfWreckPerCycle / 100.0)); - //Iterate hitbox as a set to get more random locations - for (MovecraftLocation location : hitBox.asSet()){ - if (processedFadeLocs.get(world).contains(location)) - continue; - - if (fadedBlocks >= maxFadeBlocks) - break; - - final Location bLoc = location.toBukkit(world); - if ((Settings.FadeWrecksAfter - + Settings.ExtraFadeTimePerBlock.getOrDefault(bLoc.getBlock().getType(), 0)) - * 1000L > System.currentTimeMillis() - entry.getValue()) - continue; - - fadedBlocks++; - processedFadeLocs.get(world).add(location); - BlockData phaseBlock = phaseBlocks.getOrDefault(bLoc, Material.AIR.createBlockData()); - commands.add(new BlockCreateCommand(world, location, phaseBlock)); - } - MapUpdateManager.getInstance().scheduleUpdates(commands); - if (!processedFadeLocs.get(world).containsAll(hitBox.asSet())) - continue; - - processed.add(hitBox); - processedFadeLocs.get(world).removeAll(hitBox.asSet()); - } - for(HitBox hitBox : processed) { - wrecks.remove(hitBox); - wreckPhases.remove(hitBox); - wreckWorlds.remove(hitBox); - } - - lastFadeCheck = System.currentTimeMillis(); - } - public void run() { clearAll(); processCruise(); processSinking(); - processFadingBlocks(); processAlgorithmQueue(); // now cleanup craft that are bugged and have not moved in the past 60 seconds, diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/async/rotation/RotationTask.java b/Movecraft/src/main/java/net/countercraft/movecraft/async/rotation/RotationTask.java index 9e9453f11..327e9e86c 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/async/rotation/RotationTask.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/async/rotation/RotationTask.java @@ -48,12 +48,11 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.HumanEntity; -import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.InventoryView; import java.util.HashSet; -import java.util.Iterator; +import java.util.List; import java.util.Set; import static net.countercraft.movecraft.util.MathUtils.withinWorldBorder; @@ -195,150 +194,142 @@ protected void execute() { tOP.setX(tOP.getBlockX() + 0.5); tOP.setZ(tOP.getBlockZ() + 0.5); - if (!(craft instanceof SinkingCraft && craft.getType().getBoolProperty(CraftType.ONLY_MOVE_PLAYERS)) - && craft.getType().getBoolProperty(CraftType.MOVE_ENTITIES)) { - Location midpoint = new Location( - craft.getWorld(), - (oldHitBox.getMaxX() + oldHitBox.getMinX())/2.0, - (oldHitBox.getMaxY() + oldHitBox.getMinY())/2.0, - (oldHitBox.getMaxZ() + oldHitBox.getMinZ())/2.0); - for(Entity entity : craft.getWorld().getNearbyEntities(midpoint, - oldHitBox.getXLength() / 2.0 + 1, - oldHitBox.getYLength() / 2.0 + 2, - oldHitBox.getZLength() / 2.0 + 1)) { - - if (entity instanceof HumanEntity) { - InventoryView inventoryView = ((HumanEntity) entity).getOpenInventory(); - if (inventoryView.getType() != InventoryType.CRAFTING) { - Location l = Movecraft.getInstance().getWorldHandler().getAccessLocation(inventoryView); - if (l != null) { - MovecraftLocation location = new MovecraftLocation(l.getBlockX(), l.getBlockY(), l.getBlockZ()); - if (oldHitBox.contains(location)) { - location = MathUtils.rotateVec(rotation, location.subtract(originPoint)).add(originPoint); - updates.add(new AccessLocationUpdateCommand(inventoryView, location.toBukkit(w))); - } - } - } - } + rotateEntitiesOnCraft(tOP); - if (!craft.getType().getBoolProperty(CraftType.ONLY_MOVE_PLAYERS) || ( - (entity.getType() == EntityType.PLAYER || entity.getType() == EntityType.PRIMED_TNT) - && !(craft instanceof SinkingCraft) - )) { - // Player is onboard this craft - - Location adjustedPLoc = entity.getLocation().subtract(tOP); - - double[] rotatedCoords = MathUtils.rotateVecNoRound(rotation, - adjustedPLoc.getX(), adjustedPLoc.getZ()); - float newYaw = rotation == MovecraftRotation.CLOCKWISE ? 90F : -90F; - - CraftTeleportEntityEvent e = new CraftTeleportEntityEvent(craft, entity); - Bukkit.getServer().getPluginManager().callEvent(e); - if (e.isCancelled()) - continue; - - EntityUpdateCommand eUp = new EntityUpdateCommand(entity, - rotatedCoords[0] + tOP.getX() - entity.getLocation().getX(), - 0, - rotatedCoords[1] + tOP.getZ() - entity.getLocation().getZ(), - newYaw, - 0 - ); - updates.add(eUp); - } - } + Craft craft1 = getCraft(); + if (craft1.getCruising()) { + CruiseDirection direction = craft1.getCruiseDirection(); + craft1.setCruiseDirection(direction.getRotated(rotation)); } - if (getCraft().getCruising()) { - if (rotation == MovecraftRotation.ANTICLOCKWISE) { - // ship faces west - switch (getCraft().getCruiseDirection()) { - case WEST: - getCraft().setCruiseDirection(CruiseDirection.SOUTH); - break; - // ship faces east - case EAST: - getCraft().setCruiseDirection(CruiseDirection.NORTH); - break; - // ship faces north - case SOUTH: - getCraft().setCruiseDirection(CruiseDirection.EAST); - break; - // ship faces south - case NORTH: - getCraft().setCruiseDirection(CruiseDirection.WEST); - break; - } - } else if (rotation == MovecraftRotation.CLOCKWISE) { - // ship faces west - switch (getCraft().getCruiseDirection()) { - case WEST: - getCraft().setCruiseDirection(CruiseDirection.NORTH); - break; - // ship faces east - case EAST: - getCraft().setCruiseDirection(CruiseDirection.SOUTH); - break; - // ship faces north - case SOUTH: - getCraft().setCruiseDirection(CruiseDirection.WEST); - break; - // ship faces south - case NORTH: - getCraft().setCruiseDirection(CruiseDirection.EAST); - break; - } - } + // if you rotated a subcraft, update the parent with the new blocks + if (!this.isSubCraft) { + return; } + // also find the furthest extent from center and notify the player of the new direction + int farthestX = 0; + int farthestZ = 0; + for (MovecraftLocation loc : newHitBox) { + if (Math.abs(loc.getX() - originPoint.getX()) > Math.abs(farthestX)) + farthestX = loc.getX() - originPoint.getX(); + if (Math.abs(loc.getZ() - originPoint.getZ()) > Math.abs(farthestZ)) + farthestZ = loc.getZ() - originPoint.getZ(); + } + Component faceMessage = I18nSupport.getInternationalisedComponent("Rotation - Farthest Extent Facing") + .append(Component.text(" ")); - // if you rotated a subcraft, update the parent with the new blocks - if (this.isSubCraft) { - // also find the furthest extent from center and notify the player of the new direction - int farthestX = 0; - int farthestZ = 0; - for (MovecraftLocation loc : newHitBox) { - if (Math.abs(loc.getX() - originPoint.getX()) > Math.abs(farthestX)) - farthestX = loc.getX() - originPoint.getX(); - if (Math.abs(loc.getZ() - originPoint.getZ()) > Math.abs(farthestZ)) - farthestZ = loc.getZ() - originPoint.getZ(); + faceMessage = faceMessage.append(getRotationMessage(farthestX, farthestZ)); + craft1.getAudience().sendMessage(faceMessage); + + craftsInWorld = CraftManager.getInstance().getCraftsInWorld(craft1.getWorld()); + for (Craft craft : craftsInWorld) { + if (newHitBox.intersection(craft.getHitBox()).isEmpty() || craft == craft1) { + continue; } - Component faceMessage = I18nSupport.getInternationalisedComponent("Rotation - Farthest Extent Facing") - .append(Component.text(" ")); - if (Math.abs(farthestX) > Math.abs(farthestZ)) { - if (farthestX > 0) { - faceMessage = faceMessage.append(I18nSupport.getInternationalisedComponent("Contact/Subcraft Rotate - East")); - } else { - faceMessage = faceMessage.append(I18nSupport.getInternationalisedComponent("Contact/Subcraft Rotate - West")); - } + //newHitBox.addAll(CollectionUtils.filter(craft.getHitBox(),newHitBox)); + //craft.setHitBox(newHitBox); + if (Settings.Debug) { + Bukkit.broadcastMessage(String.format("Size of %s hitbox: %d, Size of %s hitbox: %d", this.craft.getType().getStringProperty(CraftType.NAME), newHitBox.size(), craft.getType().getStringProperty(CraftType.NAME), craft.getHitBox().size())); + } + craft.setHitBox(craft.getHitBox().difference(oldHitBox).union(newHitBox)); + if (Settings.Debug){ + Bukkit.broadcastMessage(String.format("Hitbox of craft %s intersects hitbox of craft %s", this.craft.getType().getStringProperty(CraftType.NAME), craft.getType().getStringProperty(CraftType.NAME))); + Bukkit.broadcastMessage(String.format("Size of %s hitbox: %d, Size of %s hitbox: %d", this.craft.getType().getStringProperty(CraftType.NAME), newHitBox.size(), craft.getType().getStringProperty(CraftType.NAME), craft.getHitBox().size())); + } + break; + } + + } + + private Component getRotationMessage(int farthestX, int farthestZ) { + if (Math.abs(farthestX) > Math.abs(farthestZ)) { + if (farthestX > 0) { + return I18nSupport.getInternationalisedComponent("Contact/Subcraft Rotate - East"); } else { - if (farthestZ > 0) { - faceMessage = faceMessage.append(I18nSupport.getInternationalisedComponent("Contact/Subcraft Rotate - South")); - } else { - faceMessage = faceMessage.append(I18nSupport.getInternationalisedComponent("Contact/Subcraft Rotate - North")); - } + return I18nSupport.getInternationalisedComponent("Contact/Subcraft Rotate - West"); } - getCraft().getAudience().sendMessage(faceMessage); - - craftsInWorld = CraftManager.getInstance().getCraftsInWorld(getCraft().getWorld()); - for (Craft craft : craftsInWorld) { - if (!newHitBox.intersection(craft.getHitBox()).isEmpty() && craft != getCraft()) { - //newHitBox.addAll(CollectionUtils.filter(craft.getHitBox(),newHitBox)); - //craft.setHitBox(newHitBox); - if (Settings.Debug) { - Bukkit.broadcastMessage(String.format("Size of %s hitbox: %d, Size of %s hitbox: %d", this.craft.getType().getStringProperty(CraftType.NAME), newHitBox.size(), craft.getType().getStringProperty(CraftType.NAME), craft.getHitBox().size())); - } - craft.setHitBox(craft.getHitBox().difference(oldHitBox).union(newHitBox)); - if (Settings.Debug){ - Bukkit.broadcastMessage(String.format("Hitbox of craft %s intersects hitbox of craft %s", this.craft.getType().getStringProperty(CraftType.NAME), craft.getType().getStringProperty(CraftType.NAME))); - Bukkit.broadcastMessage(String.format("Size of %s hitbox: %d, Size of %s hitbox: %d", this.craft.getType().getStringProperty(CraftType.NAME), newHitBox.size(), craft.getType().getStringProperty(CraftType.NAME), craft.getHitBox().size())); - } - break; - } + } else { + if (farthestZ > 0) { + return I18nSupport.getInternationalisedComponent("Contact/Subcraft Rotate - South"); + } else { + return I18nSupport.getInternationalisedComponent("Contact/Subcraft Rotate - North"); } } + } + private void rotateEntitiesOnCraft(Location tOP) { + if (!craft.getType().getBoolProperty(CraftType.MOVE_ENTITIES) + || (craft instanceof SinkingCraft + && craft.getType().getBoolProperty(CraftType.ONLY_MOVE_PLAYERS))) { + return; + } + + Location midpoint = new Location( + craft.getWorld(), + (oldHitBox.getMaxX() + oldHitBox.getMinX())/2.0, + (oldHitBox.getMaxY() + oldHitBox.getMinY())/2.0, + (oldHitBox.getMaxZ() + oldHitBox.getMinZ())/2.0); + + List entityList = List.of(EntityType.PLAYER, EntityType.PRIMED_TNT); + for(Entity entity : craft.getWorld().getNearbyEntities(midpoint, + oldHitBox.getXLength() / 2.0 + 1, + oldHitBox.getYLength() / 2.0 + 2, + oldHitBox.getZLength() / 2.0 + 1)) { + + rotateHumanEntity(entity); + + if (craft.getType().getBoolProperty(CraftType.ONLY_MOVE_PLAYERS) + && (!entityList.contains(entity.getType()) + || craft instanceof SinkingCraft)) { + continue; + }// Player is onboard this craft + Location adjustedPLoc = entity.getLocation().subtract(tOP); + + double[] rotatedCoords = MathUtils.rotateVecNoRound(rotation, + adjustedPLoc.getX(), adjustedPLoc.getZ()); + float newYaw = rotation == MovecraftRotation.CLOCKWISE ? 90F : -90F; + + CraftTeleportEntityEvent e = new CraftTeleportEntityEvent(craft, entity); + Bukkit.getServer().getPluginManager().callEvent(e); + if (e.isCancelled()) + continue; + + EntityUpdateCommand eUp = new EntityUpdateCommand(entity, + rotatedCoords[0] + tOP.getX() - entity.getLocation().getX(), + 0, + rotatedCoords[1] + tOP.getZ() - entity.getLocation().getZ(), + newYaw, + 0 + ); + updates.add(eUp); + + + } + } + + private void rotateHumanEntity(Entity entity) { + if (!(entity instanceof HumanEntity)) { + return; + } + + InventoryView inventoryView = ((HumanEntity) entity).getOpenInventory(); + if (inventoryView.getType() == InventoryType.CRAFTING) { + return; + } + + Location l = Movecraft.getInstance().getWorldHandler().getAccessLocation(inventoryView); + if (l == null) { + return; + } + + MovecraftLocation location = new MovecraftLocation(l.getBlockX(), l.getBlockY(), l.getBlockZ()); + if (!oldHitBox.contains(location)) { + return; + } + + location = MathUtils.rotateVec(rotation, location.subtract(originPoint)).add(originPoint); + updates.add(new AccessLocationUpdateCommand(inventoryView, location.toBukkit(w))); } public MovecraftLocation getOriginPoint() { @@ -368,37 +359,32 @@ public boolean getIsSubCraft() { private boolean checkChests(Material mBlock, MovecraftLocation newLoc) { Material testMaterial; MovecraftLocation aroundNewLoc; + final World world = craft.getWorld(); aroundNewLoc = newLoc.translate(1, 0, 0); - testMaterial = craft.getWorld().getBlockAt(aroundNewLoc.getX(), aroundNewLoc.getY(), aroundNewLoc.getZ()).getType(); - if (testMaterial.equals(mBlock)) { - if (!oldHitBox.contains(aroundNewLoc)) { - return false; - } - } + testMaterial = world.getBlockAt(aroundNewLoc.getX(), aroundNewLoc.getY(), aroundNewLoc.getZ()).getType(); + if (checkOldHitBox(testMaterial, mBlock, aroundNewLoc)) + return false; aroundNewLoc = newLoc.translate(-1, 0, 0); - testMaterial = craft.getWorld().getBlockAt(aroundNewLoc.getX(), aroundNewLoc.getY(), aroundNewLoc.getZ()).getType(); - if (testMaterial.equals(mBlock)) { - if (!oldHitBox.contains(aroundNewLoc)) { - return false; - } - } + testMaterial = world.getBlockAt(aroundNewLoc.getX(), aroundNewLoc.getY(), aroundNewLoc.getZ()).getType(); + if (checkOldHitBox(testMaterial, mBlock, aroundNewLoc)) + return false; aroundNewLoc = newLoc.translate(0, 0, 1); - testMaterial = craft.getWorld().getBlockAt(aroundNewLoc.getX(), aroundNewLoc.getY(), aroundNewLoc.getZ()).getType(); - if (testMaterial.equals(mBlock)) { - if (!oldHitBox.contains(aroundNewLoc)) { - return false; - } - } + testMaterial = world.getBlockAt(aroundNewLoc.getX(), aroundNewLoc.getY(), aroundNewLoc.getZ()).getType(); + + if (checkOldHitBox(testMaterial, mBlock, aroundNewLoc)) + return false; aroundNewLoc = newLoc.translate(0, 0, -1); - testMaterial = craft.getWorld().getBlockAt(aroundNewLoc.getX(), aroundNewLoc.getY(), aroundNewLoc.getZ()).getType(); - return !testMaterial.equals(mBlock) || oldHitBox.contains(aroundNewLoc); + testMaterial = world.getBlockAt(aroundNewLoc.getX(), aroundNewLoc.getY(), aroundNewLoc.getZ()).getType(); + return !checkOldHitBox(testMaterial, mBlock, aroundNewLoc); } - + private boolean checkOldHitBox(Material testMaterial, Material mBlock, MovecraftLocation aroundNewLoc) { + return testMaterial.equals(mBlock) && !oldHitBox.contains(aroundNewLoc); + } public MutableHitBox getNewHitBox() { return newHitBox; diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/async/translation/TranslationTask.java b/Movecraft/src/main/java/net/countercraft/movecraft/async/translation/TranslationTask.java index ed5112695..d1d23459f 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/async/translation/TranslationTask.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/async/translation/TranslationTask.java @@ -5,10 +5,7 @@ import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.async.AsyncTask; import net.countercraft.movecraft.config.Settings; -import net.countercraft.movecraft.craft.ChunkManager; -import net.countercraft.movecraft.craft.Craft; -import net.countercraft.movecraft.craft.CraftManager; -import net.countercraft.movecraft.craft.SinkingCraft; +import net.countercraft.movecraft.craft.*; import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.events.CraftCollisionEvent; import net.countercraft.movecraft.events.CraftCollisionExplosionEvent; @@ -109,45 +106,18 @@ protected void execute() throws InterruptedException, ExecutionException { fail(preTranslateEvent.getFailMessage(), preTranslateEvent.isPlayingFailSound()); return; } - if (dx != preTranslateEvent.getDx()) - dx = preTranslateEvent.getDx(); - if (dy != preTranslateEvent.getDy()) - dy = preTranslateEvent.getDy(); - if (dz != preTranslateEvent.getDz()) - dz = preTranslateEvent.getDz(); + + dx = preTranslateEvent.getDx(); + dy = preTranslateEvent.getDy(); + dz = preTranslateEvent.getDz(); + world = preTranslateEvent.getWorld(); final int minY = oldHitBox.getMinY(); final int maxY = oldHitBox.getMaxY(); // proccess nether portals - if (Settings.CraftsUseNetherPortals && craft.getWorld().getEnvironment() != Environment.THE_END - && world.equals(craft.getWorld())) { - - // ensure chunks are loaded for portal checking only if change in location is - // large - Set chunksToLoad = ChunkManager.getChunks(oldHitBox, world, dx, dy, dz); - MovecraftChunk.addSurroundingChunks(chunksToLoad, 2); - ChunkManager.checkChunks(chunksToLoad); - if (!chunksToLoad.isEmpty()) - ChunkManager.syncLoadChunks(chunksToLoad).get(); - - for (MovecraftLocation oldLocation : oldHitBox) { - - Location location = oldLocation.translate(dx, dy, dz).toBukkit(craft.getWorld()); - Block block = craft.getWorld().getBlockAt(location); - if (block.getType() == Material.NETHER_PORTAL) { - if (processNetherPortal(block)) { - sound = Sound.BLOCK_PORTAL_TRAVEL; - volume = 0.25f; - break; - } - - } - - } - - } + processAllNetherPortals(); // ensure chunks are loaded only if world is different or change in location is // large @@ -160,52 +130,11 @@ protected void execute() throws InterruptedException, ExecutionException { // Only modify dy when not switching worlds //Check if the craft is too high - if (world.equals(craft.getWorld()) - && (int) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_MAX_HEIGHT_LIMIT, craft.getWorld()) - < craft.getHitBox().getMinY()) - dy = Math.min(dy, -1); - else if (world.equals(craft.getWorld()) - && (int) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_MAX_HEIGHT_ABOVE_GROUND, - craft.getWorld()) > 0) { - final MovecraftLocation middle = oldHitBox.getMidPoint(); - int testY = minY; - while (testY > 0) { - testY--; - if (!craft.getWorld().getBlockAt(middle.getX(), testY, middle.getZ()).getType().isAir()) - break; - } - if (maxY - testY - > (int) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_MAX_HEIGHT_ABOVE_GROUND, world)) - dy = Math.min(dy, -1); - } - //Process gravity - if (world.equals(craft.getWorld()) && craft.getType().getBoolProperty(CraftType.USE_GRAVITY) - && !(craft instanceof SinkingCraft)) { - int incline = inclineCraft(oldHitBox); - if (incline > 0) { - boolean tooSteep = craft.getType().getIntProperty(CraftType.GRAVITY_INCLINE_DISTANCE) > -1 - && incline > craft.getType().getIntProperty(CraftType.GRAVITY_INCLINE_DISTANCE); - if (tooSteep && craft.getType().getFloatProperty(CraftType.COLLISION_EXPLOSION) <= 0F) { - fail(I18nSupport.getInternationalisedString("Translation - Failed Incline too steep")); - return; - } - dy = tooSteep ? 0 : incline; - } else if (!isOnGround(oldHitBox) && craft.getType().getBoolProperty(CraftType.CAN_HOVER)) { - MovecraftLocation midPoint = oldHitBox.getMidPoint(); - int centreMinY = oldHitBox.getMinYAt(midPoint.getX(), midPoint.getZ()); - int groundY = centreMinY; - World w = craft.getWorld(); - while (groundY - 1 >= w.getMinHeight() - && (w.getBlockAt(midPoint.getX(), groundY - 1, midPoint.getZ()).getType().isAir() - || craft.getType().getMaterialSetProperty(CraftType.PASSTHROUGH_BLOCKS).contains( - w.getBlockAt(midPoint.getX(), groundY - 1, midPoint.getZ()).getType()))) { - groundY--; - } - if (centreMinY - groundY > craft.getType().getIntProperty(CraftType.HOVER_LIMIT)) - dy = -1; - } else if (!isOnGround(oldHitBox)) - dy = dropDistance(oldHitBox); - } + processHighCraft(minY, maxY); + //Process gravity (must be same world too) + if (processGravity()) + return; + //Fail the movement if the craft is too high and if the craft is not explosive int maxHeightLimit = (int) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_MAX_HEIGHT_LIMIT, world); int minHeightLimit = (int) craft.getType().getPerWorldProperty(CraftType.PER_WORLD_MIN_HEIGHT_LIMIT, world); @@ -332,17 +261,15 @@ else if (world.equals(craft.getWorld()) } newHitBox.removeAll(air); for (MovecraftLocation location : collisionBox) { - CraftType type = craft.getType(); - - if (type.getFloatProperty(CraftType.EXPLODE_ON_CRASH) > 0F) { + if (craft.getType().getFloatProperty(CraftType.EXPLODE_ON_CRASH) > 0F) { if (System.currentTimeMillis() - craft.getOrigPilotTime() <= 1000) { continue; } Location loc = location.toBukkit(craft.getWorld()); if (!loc.getBlock().getType().isAir() && ThreadLocalRandom.current().nextDouble(1) < .05) { updates.add(new ExplosionUpdateCommand(loc, - type.getFloatProperty(CraftType.EXPLODE_ON_CRASH), - type.getBoolProperty(CraftType.INCENDIARY_ON_CRASH))); + craft.getType().getFloatProperty(CraftType.EXPLODE_ON_CRASH), + craft.getType().getBoolProperty(CraftType.INCENDIARY_ON_CRASH))); collisionExplosion = true; } } @@ -357,16 +284,27 @@ else if (world.equals(craft.getWorld()) } } else if ((craft.getType().getFloatProperty(CraftType.COLLISION_EXPLOSION) > 0F) && System.currentTimeMillis() - craft.getOrigPilotTime() > craft.getType().getIntProperty(CraftType.EXPLOSION_ARMING_TIME)) { + Craft parentCraft = null; + if (craft instanceof SubCraft) { + parentCraft = ((SubCraft) craft).getParent(); + } for (MovecraftLocation location : collisionBox) { - float explosionForce = craft.getType().getFloatProperty(CraftType.COLLISION_EXPLOSION); + if (parentCraft != null && parentCraft.getHitBox().contains(location) + && !parentCraft.getType().getBoolProperty(CraftType.ALLOW_INTERNAL_COLLISION_EXPLOSION)) { + //Prevents CollisionExplosion crafts from exploding inside the craft. + break; + } + float explosionForce; + if (location.getY() < craft.getWaterLine()) { + explosionForce = craft.getType().getFloatProperty(CraftType.UNDERWATER_COLLISION_EXPLOSION); + } + else { + explosionForce = craft.getType().getFloatProperty(CraftType.COLLISION_EXPLOSION); + } boolean incendiary = craft.getType().getBoolProperty(CraftType.INCENDIARY_ON_CRASH); if (craft.getType().getBoolProperty(CraftType.FOCUSED_EXPLOSION)) { explosionForce *= Math.min(oldHitBox.size(), craft.getType().getIntProperty(CraftType.MAX_SIZE)); } - //TODO: Account for underwater explosions - /*if (location.getY() < waterLine) { // underwater explosions require more force to do anything - explosionForce += 25;//TODO: find the correct amount - }*/ Location oldLocation = location.translate(-dx, -dy, -dz).toBukkit(craft.getWorld()); Location newLocation = location.toBukkit(world); if (!oldLocation.getBlock().getType().isAir()) { @@ -402,60 +340,197 @@ else if (world.equals(craft.getWorld()) updates.add(new CraftTranslateCommand(craft, new MovecraftLocation(dx, dy, dz), world)); //prevents torpedo and rocket pilots - if (!(craft instanceof SinkingCraft && craft.getType().getBoolProperty(CraftType.ONLY_MOVE_PLAYERS)) - && craft.getType().getBoolProperty(CraftType.MOVE_ENTITIES)) { - Location midpoint = new Location( - craft.getWorld(), - (oldHitBox.getMaxX() + oldHitBox.getMinX()) / 2.0, - (oldHitBox.getMaxY() + oldHitBox.getMinY()) / 2.0, - (oldHitBox.getMaxZ() + oldHitBox.getMinZ()) / 2.0); - for (Entity entity : craft.getWorld().getNearbyEntities(midpoint, - oldHitBox.getXLength() / 2.0 + 1, - oldHitBox.getYLength() / 2.0 + 2, - oldHitBox.getZLength() / 2.0 + 1 - )) { - - if (entity instanceof HumanEntity) { - InventoryView inventoryView = ((HumanEntity) entity).getOpenInventory(); - if (inventoryView.getType() != InventoryType.CRAFTING) { - Location l = Movecraft.getInstance().getWorldHandler().getAccessLocation(inventoryView); - if (l != null) { - MovecraftLocation location = new MovecraftLocation(l.getBlockX(), l.getBlockY(), l.getBlockZ()); - if (oldHitBox.contains(location)) { - location = location.translate(dx, dy, dz); - updates.add(new AccessLocationUpdateCommand(inventoryView, location.toBukkit(world))); - } - } - } - } - - if ((entity.getType() == EntityType.PLAYER && !(craft instanceof SinkingCraft))) { - CraftTeleportEntityEvent e = new CraftTeleportEntityEvent(craft, entity); - Bukkit.getServer().getPluginManager().callEvent(e); - if (e.isCancelled()) - continue; - - EntityUpdateCommand eUp = new EntityUpdateCommand(entity, dx, dy, dz, 0, 0, - world, sound, volume); - updates.add(eUp); - } else if (!craft.getType().getBoolProperty(CraftType.ONLY_MOVE_PLAYERS) - || entity.getType() == EntityType.PRIMED_TNT) { - CraftTeleportEntityEvent e = new CraftTeleportEntityEvent(craft, entity); - Bukkit.getServer().getPluginManager().callEvent(e); - if (e.isCancelled()) - continue; + preventsTorpedoRocketsPilots(); + captureYield(harvestedBlocks); + } - EntityUpdateCommand eUp = new EntityUpdateCommand(entity, dx, dy, dz, 0, 0, world); - updates.add(eUp); - } - } - } else { + private void preventsTorpedoRocketsPilots() { + if (!craft.getType().getBoolProperty(CraftType.MOVE_ENTITIES) || + (craft instanceof SinkingCraft + && craft.getType().getBoolProperty(CraftType.ONLY_MOVE_PLAYERS))) { // add releaseTask without playermove to manager if (!craft.getType().getBoolProperty(CraftType.CRUISE_ON_PILOT) && !(craft instanceof SinkingCraft)) // not necessary to release cruiseonpilot crafts, because they will already be released CraftManager.getInstance().addReleaseTask(craft); + return; } - captureYield(harvestedBlocks); + + Location midpoint = new Location( + craft.getWorld(), + (oldHitBox.getMaxX() + oldHitBox.getMinX()) / 2.0, + (oldHitBox.getMaxY() + oldHitBox.getMinY()) / 2.0, + (oldHitBox.getMaxZ() + oldHitBox.getMinZ()) / 2.0); + for (Entity entity : craft.getWorld().getNearbyEntities(midpoint, + oldHitBox.getXLength() / 2.0 + 1, + oldHitBox.getYLength() / 2.0 + 2, + oldHitBox.getZLength() / 2.0 + 1 + )) { + + processHumanEntity(entity); + + if ((entity.getType() == EntityType.PLAYER && !(craft instanceof SinkingCraft))) { + CraftTeleportEntityEvent e = new CraftTeleportEntityEvent(craft, entity); + Bukkit.getServer().getPluginManager().callEvent(e); + if (e.isCancelled()) + continue; + + EntityUpdateCommand eUp = new EntityUpdateCommand(entity, dx, dy, dz, 0, 0, + world, sound, volume); + updates.add(eUp); + continue; + } + + if (craft.getType().getBoolProperty(CraftType.ONLY_MOVE_PLAYERS) + && entity.getType() != EntityType.PRIMED_TNT) { + continue; + } + + CraftTeleportEntityEvent e = new CraftTeleportEntityEvent(craft, entity); + Bukkit.getServer().getPluginManager().callEvent(e); + if (e.isCancelled()) + continue; + + EntityUpdateCommand eUp = new EntityUpdateCommand(entity, dx, dy, dz, 0, 0, world); + updates.add(eUp); + } + } + + //this part looks similar to rotationTask + //maybe can be thrown in a util class? + private void processHumanEntity(Entity entity) { + if (!(entity instanceof HumanEntity)) { + return; + } + + InventoryView inventoryView = ((HumanEntity) entity).getOpenInventory(); + if (inventoryView.getType() == InventoryType.CRAFTING) { + return; + } + + Location l = Movecraft.getInstance().getWorldHandler().getAccessLocation(inventoryView); + if (l == null) { + return; + } + + MovecraftLocation location = new MovecraftLocation(l.getBlockX(), l.getBlockY(), l.getBlockZ()); + if (oldHitBox.contains(location)) { + location = location.translate(dx, dy, dz); + updates.add(new AccessLocationUpdateCommand(inventoryView, location.toBukkit(world))); + } + } + + /** + * + * @return True if failed, false otherwise + */ + private boolean processGravity() { + //Process gravity (must be same world too) + if (!world.equals(craft.getWorld()) || !craft.getType().getBoolProperty(CraftType.USE_GRAVITY) + || craft instanceof SinkingCraft) { + return false; + } + + int incline = inclineCraft(oldHitBox); + if (incline > 0) { + boolean tooSteep = craft.getType().getIntProperty(CraftType.GRAVITY_INCLINE_DISTANCE) > -1 + && incline > craft.getType().getIntProperty(CraftType.GRAVITY_INCLINE_DISTANCE); + if (tooSteep && craft.getType().getFloatProperty(CraftType.COLLISION_EXPLOSION) <= 0F) { + fail(I18nSupport.getInternationalisedString("Translation - Failed Incline too steep")); + return true; + } + dy = tooSteep ? 0 : incline; + return false; + } + + if (isOnGround(oldHitBox)) { + return false; + } + + if (craft.getType().getBoolProperty(CraftType.CAN_HOVER)) { + MovecraftLocation midPoint = oldHitBox.getMidPoint(); + int centreMinY = oldHitBox.getMinYAt(midPoint.getX(), midPoint.getZ()); + int groundY = centreMinY; + World w = craft.getWorld(); + + while (groundY - 1 >= w.getMinHeight() + && (w.getBlockAt(midPoint.getX(), groundY - 1, midPoint.getZ()).getType().isAir() + || craft.getType().getMaterialSetProperty(CraftType.PASSTHROUGH_BLOCKS).contains( + w.getBlockAt(midPoint.getX(), groundY - 1, midPoint.getZ()).getType()))) { + groundY--; + } + + if (centreMinY - groundY > craft.getType().getIntProperty(CraftType.HOVER_LIMIT)) + dy = -1; + return false; + } + + dy = dropDistance(oldHitBox); + return false; + } + + private void processAllNetherPortals() throws ExecutionException, InterruptedException { + // proccess nether portals + if (!Settings.CraftsUseNetherPortals + || craft.getWorld().getEnvironment() == Environment.THE_END + || !world.equals(craft.getWorld())) { + return; + } + + // ensure chunks are loaded for portal checking only if change in location is + // large + Set chunksToLoad = ChunkManager.getChunks(oldHitBox, world, dx, dy, dz); + MovecraftChunk.addSurroundingChunks(chunksToLoad, 2); + ChunkManager.checkChunks(chunksToLoad); + if (!chunksToLoad.isEmpty()) + ChunkManager.syncLoadChunks(chunksToLoad).get(); + + for (MovecraftLocation oldLocation : oldHitBox) { + Location location = oldLocation.translate(dx, dy, dz).toBukkit(craft.getWorld()); + Block block = craft.getWorld().getBlockAt(location); + + if (block.getType() != Material.NETHER_PORTAL) { + continue; + } + + if (processNetherPortal(block)) { + sound = Sound.BLOCK_PORTAL_TRAVEL; + volume = 0.25f; + break; + } + + } + } + + private void processHighCraft(int minY, int maxY) { + // Only modify dy when not switching worlds + // Check if the craft is too high + boolean sameWorld = world.equals(craft.getWorld()); + CraftType craftType = craft.getType(); + if (!sameWorld) { + return; + } + + if ((int) craftType.getPerWorldProperty(CraftType.PER_WORLD_MAX_HEIGHT_LIMIT, craft.getWorld()) < craft.getHitBox().getMinY()) { + dy = Math.min(dy, -1); + return; + } + final int perWorldMaxHeighAboveGround = (int) craftType.getPerWorldProperty(CraftType.PER_WORLD_MAX_HEIGHT_ABOVE_GROUND, world); + // world == craft.world + if (perWorldMaxHeighAboveGround <= 0) { + return; + } + + final MovecraftLocation middle = oldHitBox.getMidPoint(); + int testY; + + for (testY = minY; testY > world.getMinHeight(); testY--) { + if (!craft.getWorld().getBlockAt(middle.getX(), testY - 1, middle.getZ()).getType().isAir()) { + break; + } + } + + if (maxY - testY > perWorldMaxHeighAboveGround) + dy = Math.min(dy, -1); } private void fail(@NotNull String failMessage) { diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/commands/ManOverboardCommand.java b/Movecraft/src/main/java/net/countercraft/movecraft/commands/ManOverboardCommand.java index e3118c255..98882f49c 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/commands/ManOverboardCommand.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/commands/ManOverboardCommand.java @@ -8,7 +8,6 @@ import net.countercraft.movecraft.events.ManOverboardEvent; import net.countercraft.movecraft.localisation.I18nSupport; import net.countercraft.movecraft.util.MathUtils; -import net.countercraft.movecraft.util.ReflectUtils; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.command.Command; @@ -71,9 +70,11 @@ public boolean onCommand(CommandSender commandSender, Command command, String s, ManOverboardEvent event = new ManOverboardEvent(craft, telPoint); Bukkit.getServer().getPluginManager().callEvent(event); + telPoint.setYaw(player.getLocation().getYaw()); + telPoint.setPitch(player.getLocation().getPitch()); player.setVelocity(new Vector(0, 0, 0)); player.setFallDistance(0); - Movecraft.getInstance().getSmoothTeleport().teleport(player, telPoint, 0, 0); + Movecraft.getInstance().getSmoothTeleport().teleport(player, telPoint); return true; } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/craft/CraftManager.java b/Movecraft/src/main/java/net/countercraft/movecraft/craft/CraftManager.java index ec586acd2..1db3db008 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/craft/CraftManager.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/craft/CraftManager.java @@ -244,7 +244,7 @@ public void release(@NotNull Craft craft, @NotNull CraftReleaseEvent.Reason reas craft.getHitBox().getMinZ()) ); } - Movecraft.getInstance().getAsyncManager().addWreck(craft); + Movecraft.getInstance().getWreckManager().queueWreck(craft); } //region Craft management diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/features/fading/FadeTask.java b/Movecraft/src/main/java/net/countercraft/movecraft/features/fading/FadeTask.java new file mode 100644 index 000000000..a8ae5f1b8 --- /dev/null +++ b/Movecraft/src/main/java/net/countercraft/movecraft/features/fading/FadeTask.java @@ -0,0 +1,42 @@ +package net.countercraft.movecraft.features.fading; + +import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.processing.MovecraftWorld; +import net.countercraft.movecraft.processing.WorldManager; +import net.countercraft.movecraft.processing.effects.Effect; +import net.countercraft.movecraft.processing.effects.SetBlockEffect; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.function.Supplier; + +/** + * Fades a block if the data for the intended block has not been mutated since creation. + */ +public class FadeTask implements Supplier { + private final @NotNull BlockData compareData; + private final @NotNull BlockData setData; + private final @NotNull MovecraftWorld world; + private final @NotNull MovecraftLocation location; + + public FadeTask(@NotNull BlockData compareData, @NotNull BlockData setData, @NotNull MovecraftWorld world, @NotNull MovecraftLocation location){ + this.compareData = compareData; + this.setData = setData; + this.world = world; + this.location = location; + } + + @Override + public Effect get() { + var testData = world.getData(location); + + return () -> Objects.requireNonNull(Bukkit.getWorld(world.getWorldUUID())) + .getChunkAtAsync(location.toBukkit(null)) + .thenRunAsync(() -> WorldManager.INSTANCE.submit(() -> testData.equals(compareData) + ? new SetBlockEffect(world, location, setData) + : null)); + } +} diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/features/fading/WreckManager.java b/Movecraft/src/main/java/net/countercraft/movecraft/features/fading/WreckManager.java new file mode 100644 index 000000000..ee12262df --- /dev/null +++ b/Movecraft/src/main/java/net/countercraft/movecraft/features/fading/WreckManager.java @@ -0,0 +1,45 @@ +package net.countercraft.movecraft.features.fading; + +import net.countercraft.movecraft.config.Settings; +import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.processing.WorldManager; +import net.countercraft.movecraft.util.MathUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Singleton for handling wreck disposal + */ +public class WreckManager { + private final @NotNull WorldManager worldManager; + + public WreckManager(@NotNull WorldManager worldManager){ + this.worldManager = Objects.requireNonNull(worldManager); + } + + /** + * Queue a wreck to be considered terminally destroyed, and hence appropriate for systems such as fading. + * + * @param craft the craft to handle as a wreck + */ + public void queueWreck(@NotNull Craft craft){ + if(craft.getCollapsedHitBox().isEmpty() || Settings.FadeWrecksAfter == 0){ + return; + } + + worldManager.submit(new WreckTask( + craft.getCollapsedHitBox(), + craft.getMovecraftWorld(), + craft + .getPhaseBlocks() + .entrySet() + .stream() + .collect(Collectors.toMap( + entry -> MathUtils.bukkit2MovecraftLoc(entry.getKey()), + Map.Entry::getValue + )))); + } +} diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/features/fading/WreckTask.java b/Movecraft/src/main/java/net/countercraft/movecraft/features/fading/WreckTask.java new file mode 100644 index 000000000..5df495527 --- /dev/null +++ b/Movecraft/src/main/java/net/countercraft/movecraft/features/fading/WreckTask.java @@ -0,0 +1,73 @@ +package net.countercraft.movecraft.features.fading; + +import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.config.Settings; +import net.countercraft.movecraft.processing.MovecraftWorld; +import net.countercraft.movecraft.processing.WorldManager; +import net.countercraft.movecraft.processing.effects.DeferredEffect; +import net.countercraft.movecraft.processing.effects.Effect; +import net.countercraft.movecraft.util.CollectorUtils; +import net.countercraft.movecraft.util.hitboxes.HitBox; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ForkJoinTask; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class WreckTask implements Supplier { + + private final @NotNull HitBox hitBox; + private final @NotNull Map phaseBlocks; + private final @NotNull MovecraftWorld world; + private final int fadeDelayTicks; + private final int maximumFadeDurationTicks; + + public WreckTask(@NotNull HitBox wreck, @NotNull MovecraftWorld world, @NotNull Map phaseBlocks){ + this.hitBox = Objects.requireNonNull(wreck); + this.phaseBlocks = Objects.requireNonNull(phaseBlocks); + this.world = Objects.requireNonNull(world); + this.fadeDelayTicks = Settings.FadeWrecksAfter * 20; + this.maximumFadeDurationTicks = (int) (Settings.FadeTickCooldown * (100.0 / Settings.FadePercentageOfWreckPerCycle)); + } + + @Override + public Effect get() { + var updates = hitBox + .asSet() + .stream() + .collect(Collectors.groupingBy(location -> location.scalarDivide(16).hadamardProduct(1,0,1), CollectorUtils.toHitBox())) + .values() + .stream() + .map(slice -> ForkJoinTask.adapt(() -> partialUpdate(slice))) + .toList(); + + return ForkJoinTask + .invokeAll(updates) + .stream() + .map(ForkJoinTask::join) + .reduce(Effect.NONE, Effect::andThen); + } + + private @NotNull Effect partialUpdate(@NotNull HitBox slice){ + Effect accumulator = Effect.NONE; + for (MovecraftLocation location : slice){ + // Get the existing data + final BlockData data = world.getData(location); + // Determine the replacement data + BlockData replacementData = phaseBlocks.getOrDefault(location, Material.AIR.createBlockData()); + // Calculate ticks until replacement + long fadeTicks = this.fadeDelayTicks; + fadeTicks += (int) (Math.random() * maximumFadeDurationTicks); + fadeTicks += 20L * Settings.ExtraFadeTimePerBlock.getOrDefault(data.getMaterial(), 0); + // Deffer replacement until time delay elapses + accumulator = accumulator.andThen(new DeferredEffect(fadeTicks, () -> WorldManager.INSTANCE.submit(new FadeTask(data, replacementData, world, location)))); + } + + // TODO: Determine if we need to reduce the spread of deferred effects due to runnable overhead + return accumulator; + } +} diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusManager.java b/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusManager.java index ed48e75d4..5a9068f01 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusManager.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusManager.java @@ -5,7 +5,6 @@ import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.CraftManager; import net.countercraft.movecraft.craft.SinkingCraft; -import net.countercraft.movecraft.craft.datatag.CraftDataTagContainer; import net.countercraft.movecraft.craft.datatag.CraftDataTagKey; import net.countercraft.movecraft.craft.datatag.CraftDataTagRegistry; import net.countercraft.movecraft.craft.type.CraftType; @@ -28,7 +27,6 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.scheduler.BukkitRunnable; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Map; import java.util.function.Supplier; @@ -68,11 +66,12 @@ private StatusUpdateTask(@NotNull Craft craft) { } @Override - public @Nullable Effect get() { + public @NotNull Effect get() { Counter materials = new Counter<>(); int nonNegligibleBlocks = 0; int nonNegligibleSolidBlocks = 0; double fuel = 0; + for (MovecraftLocation l : craft.getHitBox()) { Material type = craft.getMovecraftWorld().getMaterial(l); materials.add(type); @@ -94,8 +93,26 @@ private StatusUpdateTask(@NotNull Craft craft) { } } + Counter flyblocks = new Counter<>(); + Counter moveblocks = new Counter<>(); + for(Material material : materials.getKeySet()) { + for(RequiredBlockEntry entry : craft.getType().getRequiredBlockProperty(CraftType.FLY_BLOCKS)) { + if(entry.contains(material)) { + flyblocks.add(entry, materials.get(material) ); + } + } + + for(RequiredBlockEntry entry : craft.getType().getRequiredBlockProperty(CraftType.MOVE_BLOCKS)) { + if(entry.contains(material)) { + moveblocks.add(entry, materials.get(material) ); + } + } + } + craft.setDataTag(Craft.FUEL, fuel); craft.setDataTag(Craft.MATERIALS, materials); + craft.setDataTag(Craft.FLYBLOCKS, flyblocks); + craft.setDataTag(Craft.MOVEBLOCKS, moveblocks); craft.setDataTag(Craft.NON_NEGLIGIBLE_BLOCKS, nonNegligibleBlocks); craft.setDataTag(Craft.NON_NEGLIGIBLE_SOLID_BLOCKS, nonNegligibleSolidBlocks); craft.setDataTag(LAST_STATUS_CHECK, System.currentTimeMillis()); diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusSign.java index 1246c1381..d8a488576 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusSign.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusSign.java @@ -1,16 +1,11 @@ package net.countercraft.movecraft.features.status; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import net.countercraft.movecraft.Movecraft; import net.countercraft.movecraft.MovecraftLocation; -import net.countercraft.movecraft.craft.BaseCraft; import net.countercraft.movecraft.craft.Craft; import net.countercraft.movecraft.craft.type.CraftType; import net.countercraft.movecraft.craft.type.RequiredBlockEntry; import net.countercraft.movecraft.events.CraftDetectEvent; import net.countercraft.movecraft.events.SignTranslateEvent; -import net.countercraft.movecraft.features.status.StatusManager; import net.countercraft.movecraft.util.Counter; import net.countercraft.movecraft.util.Tags; import org.bukkit.ChatColor; @@ -25,16 +20,8 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; - public final class StatusSign implements Listener { @EventHandler @@ -82,28 +69,14 @@ public final void onSignTranslate(SignTranslateEvent event) { totalNonNegligibleWaterBlocks += add; } } - Object2IntMap displayBlocks = new Object2IntOpenHashMap<>(); - for (RequiredBlockEntry entry : craft.getType().getRequiredBlockProperty(CraftType.FLY_BLOCKS)) { - int total = 0; - for (Material material : entry.getMaterials()) { - if (materials.getKeySet().contains(material)) { - total += materials.get(material); - } - } - displayBlocks.putIfAbsent(entry, total); - } - for (RequiredBlockEntry entry : craft.getType().getRequiredBlockProperty(CraftType.MOVE_BLOCKS)) { - int total = 0; - for (Material material : entry.getMaterials()) { - if (materials.getKeySet().contains(material)) { - total += materials.get(material); - } - } - displayBlocks.putIfAbsent(entry, total); - } + + Counter displayBlocks = new Counter<>(); + displayBlocks.add(craft.getDataTag(Craft.FLYBLOCKS)); + displayBlocks.add(craft.getDataTag(Craft.MOVEBLOCKS)); + int signLine = 1; int signColumn = 0; - for (RequiredBlockEntry entry : displayBlocks.keySet()) { + for (RequiredBlockEntry entry : displayBlocks.getKeySet()) { if (entry.getMin() == 0.0) { continue; } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/mapUpdater/update/EntityUpdateCommand.java b/Movecraft/src/main/java/net/countercraft/movecraft/mapUpdater/update/EntityUpdateCommand.java index c334f4e5b..40f7ce2b8 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/mapUpdater/update/EntityUpdateCommand.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/mapUpdater/update/EntityUpdateCommand.java @@ -83,17 +83,21 @@ public Entity getEntity() { @Override public void doUpdate() { - Location playerLoc = entity.getLocation(); - // Use bukkit teleporting API for changing worlds because it won't be smooth anyway - if (!(entity instanceof Player) || !playerLoc.getWorld().equals(world)) { - entity.teleport(new Location(world, x + playerLoc.getX(),y + playerLoc.getY(),z + playerLoc.getZ(),yaw + playerLoc.getYaw(),pitch + playerLoc.getPitch())); - return; - } - Location location = new Location(world, playerLoc.getX() + x, playerLoc.getY() + y, playerLoc.getZ() + z); - Movecraft.getInstance().getSmoothTeleport().teleport((Player) entity, location, yaw, pitch); + Location location = entity.getLocation().add(x, y, z); + location.setYaw(location.getYaw() + yaw); + location.setPitch(location.getPitch() + pitch); + location.setWorld(world); if (sound != null) { ((Player) entity).playSound(location, sound, volume, 1.0f); } + + // Use bukkit teleporting API for changing worlds because it won't be smooth anyway + if (!(entity instanceof Player) || !entity.getLocation().getWorld().equals(world)) { + entity.teleport(location); + return; + } + + Movecraft.getInstance().getSmoothTeleport().teleport((Player) entity, location); } @Override diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/processing/effects/DeferredEffect.java b/Movecraft/src/main/java/net/countercraft/movecraft/processing/effects/DeferredEffect.java new file mode 100644 index 000000000..ee0864345 --- /dev/null +++ b/Movecraft/src/main/java/net/countercraft/movecraft/processing/effects/DeferredEffect.java @@ -0,0 +1,36 @@ +package net.countercraft.movecraft.processing.effects; + +import net.countercraft.movecraft.Movecraft; +import net.countercraft.movecraft.processing.WorldManager; +import org.bukkit.scheduler.BukkitRunnable; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * A wrapper effect that allows delaying the execution of a provided effect by a number of ticks. + */ +public class DeferredEffect implements Effect { + private final long delayTicks; + private final @NotNull Effect effect; + + public DeferredEffect(long delayTicks, @NotNull Effect effect){ + this.delayTicks = delayTicks; + this.effect = Objects.requireNonNull(effect); + } + + @Override + public void run() { + new BukkitRunnable(){ + @Override + public void run() { + WorldManager.INSTANCE.submit(() -> effect); + } + }.runTaskLaterAsynchronously(Movecraft.getInstance(), delayTicks); + } + + @Override + public boolean isAsync() { + return true; + } +} diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/processing/effects/SetBlockEffect.java b/Movecraft/src/main/java/net/countercraft/movecraft/processing/effects/SetBlockEffect.java index 82aca582c..c31e93944 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/processing/effects/SetBlockEffect.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/processing/effects/SetBlockEffect.java @@ -1,16 +1,36 @@ package net.countercraft.movecraft.processing.effects; import net.countercraft.movecraft.MovecraftLocation; -import org.bukkit.Material; +import net.countercraft.movecraft.mapUpdater.update.BlockCreateCommand; +import net.countercraft.movecraft.processing.MovecraftWorld; +import org.bukkit.Bukkit; import org.bukkit.World; +import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.NotNull; +import java.util.Objects; + +/** + * Sets a block based on the provided data. + */ public final class SetBlockEffect implements Effect { - public SetBlockEffect(World world, MovecraftLocation location, Material material){ + private final @NotNull MovecraftWorld world; + private final @NotNull MovecraftLocation location; + private final @NotNull BlockData data; + public SetBlockEffect(@NotNull MovecraftWorld world, @NotNull MovecraftLocation location, @NotNull BlockData data){ + this.world = world; + this.location = location; + this.data = data; } @Override public void run() { + World bukkitWorld = Objects.requireNonNull( + Bukkit.getWorld(world.getWorldUUID()), + "Failed to access base World from MovecraftWorld"); + // TODO: Reverse indirection + new BlockCreateCommand(bukkitWorld, location, data).doUpdate(); } } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/processing/tasks/detection/DetectionTask.java b/Movecraft/src/main/java/net/countercraft/movecraft/processing/tasks/detection/DetectionTask.java index 30efd8031..1cf1eff8c 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/processing/tasks/detection/DetectionTask.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/processing/tasks/detection/DetectionTask.java @@ -39,6 +39,8 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -48,9 +50,11 @@ import java.util.Collections; import java.util.Comparator; import java.util.Deque; +import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -304,7 +308,6 @@ private void frontier() { ConcurrentLinkedQueue nextFrontier = new ConcurrentLinkedQueue<>(); currentFrontier.add(startLocation); currentFrontier.addAll(Arrays.stream(SHIFTS).map(startLocation::add).collect(Collectors.toList())); - visited.addAll(currentFrontier); int threads = Runtime.getRuntime().availableProcessors(); while(!currentFrontier.isEmpty() && size.intValue() < type.getIntProperty(CraftType.MAX_SIZE) + threads) { List> tasks = new ArrayList<>(); @@ -330,6 +333,14 @@ public String toString(){ private class DetectAction implements Runnable { private final ConcurrentLinkedQueue currentFrontier; private final ConcurrentLinkedQueue nextFrontier; + private static DetectionPredicate chain; + + static { + chain = FORBIDDEN_BLOCK_VALIDATOR; + for(var validator : VALIDATORS) { + chain = chain.and(validator); + } + } private DetectAction(ConcurrentLinkedQueue currentFrontier, ConcurrentLinkedQueue nextFrontier) { this.currentFrontier = currentFrontier; @@ -339,31 +350,42 @@ private DetectAction(ConcurrentLinkedQueue currentFrontier, C @Override public void run() { MovecraftLocation probe; + EnumSet directionalDependent = type.getMaterialSetProperty(CraftType.DIRECTIONAL_DEPENDENT_MATERIALS); + while((probe = currentFrontier.poll()) != null) { - visitedMaterials.computeIfAbsent(movecraftWorld.getMaterial(probe), Functions.forSupplier(ConcurrentLinkedDeque::new)).add(probe); + BlockData blockData = movecraftWorld.getData(probe); + Material material = blockData.getMaterial(); + + Optional blockDataOptional = SupportUtils.getSupportFace(blockData, directionalDependent); + if (blockDataOptional.isPresent()) { + BlockFace facing = blockDataOptional.get(); + MovecraftLocation relativeLoc = probe.getRelative(facing); + + if (!legal.contains(relativeLoc)) + continue; + } + + if(!visited.add(probe)) + continue; + + visitedMaterials.computeIfAbsent(material, Functions.forSupplier(ConcurrentLinkedDeque::new)).add(probe); if(!ALLOWED_BLOCK_VALIDATOR.validate(probe, type, movecraftWorld, player).isSucess()) continue; - DetectionPredicate chain = FORBIDDEN_BLOCK_VALIDATOR; - for(var validator : VALIDATORS) { - chain = chain.and(validator); - } var result = chain.validate(probe, type, movecraftWorld, player); - if(result.isSucess()) { + if (result.isSucess()) { legal.add(probe); - if(Tags.FLUID.contains(movecraftWorld.getMaterial(probe))) + if (Tags.FLUID.contains(material)) fluid.add(probe); size.increment(); - materials.computeIfAbsent(movecraftWorld.getMaterial(probe), Functions.forSupplier(ConcurrentLinkedDeque::new)).add(probe); - for(MovecraftLocation shift : SHIFTS) { + materials.computeIfAbsent(material, Functions.forSupplier(ConcurrentLinkedDeque::new)).add(probe); + for (MovecraftLocation shift : SHIFTS) { var shifted = probe.add(shift); - if(visited.add(shifted)) - nextFrontier.add(shifted); + nextFrontier.add(shifted); } - } - else { + } else { illegal.add(probe); audience.sendMessage(Component.text(result.getMessage())); } diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/processing/tasks/detection/SupportUtils.java b/Movecraft/src/main/java/net/countercraft/movecraft/processing/tasks/detection/SupportUtils.java new file mode 100644 index 000000000..af2decf2e --- /dev/null +++ b/Movecraft/src/main/java/net/countercraft/movecraft/processing/tasks/detection/SupportUtils.java @@ -0,0 +1,47 @@ +package net.countercraft.movecraft.processing.tasks.detection; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.FaceAttachable; +import org.bukkit.block.data.type.Lantern; + +import java.util.EnumSet; +import java.util.Optional; + +/** + * @author Intybyte/Vaan1310 + * An util craft that uses block data to get its supporting block + */ + +public class SupportUtils { + public static Optional getSupportFace(BlockData data, EnumSet directionalDependent) { + + Material material = data.getMaterial(); + if (!directionalDependent.contains(material)) { + return Optional.empty(); + } + + //TODO: Use pattern matched switch statements once we update do Java 21 + //TODO: This should become Hangable instead when we drop support for 1.18 + if (data instanceof Lantern lantern) + return Optional.of(lantern.isHanging() ? BlockFace.UP : BlockFace.DOWN); + + if (data instanceof Directional directional) { + BlockFace normalCase = directional.getFacing().getOppositeFace(); + + if (data instanceof FaceAttachable faceAttachable) { + return switch (faceAttachable.getAttachedFace()) { + case FLOOR -> Optional.of(BlockFace.DOWN); + case WALL -> Optional.of(normalCase); + case CEILING -> Optional.of(BlockFace.UP); + }; + } + + return Optional.of(normalCase); + } + + return Optional.empty(); + } +} diff --git a/api/src/main/java/net/countercraft/movecraft/CruiseDirection.java b/api/src/main/java/net/countercraft/movecraft/CruiseDirection.java index 251ae2daa..b569903a3 100644 --- a/api/src/main/java/net/countercraft/movecraft/CruiseDirection.java +++ b/api/src/main/java/net/countercraft/movecraft/CruiseDirection.java @@ -38,20 +38,41 @@ else if(rawDirection == (byte) 0x43) } public static CruiseDirection fromBlockFace(BlockFace direction) { - if(direction.getOppositeFace() == BlockFace.NORTH) - return NORTH; - else if(direction.getOppositeFace() == BlockFace.SOUTH) - return SOUTH; - else if(direction.getOppositeFace() == BlockFace.EAST) - return EAST; - else if(direction.getOppositeFace() == BlockFace.WEST) - return WEST; - else if(direction.getOppositeFace() == BlockFace.UP) - return UP; - else if(direction.getOppositeFace() == BlockFace.DOWN) - return DOWN; - else - return NONE; + return switch (direction.getOppositeFace()) { + case NORTH -> NORTH; + case SOUTH -> SOUTH; + case EAST -> EAST; + case WEST -> WEST; + case UP -> UP; + case DOWN -> DOWN; + default -> NONE; + }; + } + + public CruiseDirection getOpposite() { + return switch (this) { + case NORTH -> SOUTH; + case SOUTH -> NORTH; + case EAST -> WEST; + case WEST -> EAST; + case UP -> DOWN; + case DOWN -> UP; + case NONE -> NONE; + }; + } + + public CruiseDirection getRotated(MovecraftRotation rotation) { + return switch(rotation) { + case CLOCKWISE -> switch (this) { + case NORTH -> EAST; + case SOUTH -> WEST; + case EAST -> SOUTH; + case WEST -> NORTH; + default -> this; + }; + case ANTICLOCKWISE -> getRotated(MovecraftRotation.CLOCKWISE).getOpposite(); + case NONE -> this; + }; } } diff --git a/api/src/main/java/net/countercraft/movecraft/MovecraftLocation.java b/api/src/main/java/net/countercraft/movecraft/MovecraftLocation.java index 945bbf771..1f3486f43 100644 --- a/api/src/main/java/net/countercraft/movecraft/MovecraftLocation.java +++ b/api/src/main/java/net/countercraft/movecraft/MovecraftLocation.java @@ -20,6 +20,7 @@ import com.google.common.primitives.UnsignedInteger; import org.bukkit.Location; import org.bukkit.World; +import org.bukkit.block.BlockFace; import org.jetbrains.annotations.NotNull; import static net.countercraft.movecraft.util.BitMath.mask; @@ -87,6 +88,26 @@ public MovecraftLocation subtract(MovecraftLocation l) { return new MovecraftLocation(getX() - l.getX(), getY() - l.getY(), getZ() - l.getZ()); } + public MovecraftLocation hadamardProduct(int x, int y, int z){ + return new MovecraftLocation(this.x*x, this.y*y, this.z*z); + } + + public MovecraftLocation hadamardProduct(MovecraftLocation location){ + return hadamardProduct(location.x, location.y, location.z); + } + + public MovecraftLocation scalarMultiply(int multiplier){ + return new MovecraftLocation(x * multiplier, y * multiplier, z * multiplier); + } + + public MovecraftLocation scalarDivide(int divisor){ + return new MovecraftLocation(x / divisor, y / divisor, z/divisor); + } + + public MovecraftLocation scalarMod(int modulus){ + return new MovecraftLocation(x % modulus, y & modulus, z % modulus); + } + /** * * Gives the euclidean distance between this MovecraftLocation and another MovecraftLocation @@ -154,4 +175,8 @@ public int compareTo(@NotNull MovecraftLocation other) { } return 0; } + + public MovecraftLocation getRelative(BlockFace facing) { + return this.translate(facing.getModX(), facing.getModY(), facing.getModZ()); + } } diff --git a/api/src/main/java/net/countercraft/movecraft/SmoothTeleport.java b/api/src/main/java/net/countercraft/movecraft/SmoothTeleport.java index f6e9e898c..97e7fbb31 100644 --- a/api/src/main/java/net/countercraft/movecraft/SmoothTeleport.java +++ b/api/src/main/java/net/countercraft/movecraft/SmoothTeleport.java @@ -5,5 +5,5 @@ import org.jetbrains.annotations.NotNull; public abstract class SmoothTeleport { - public abstract void teleport(Player player, @NotNull Location location, float yawChange, float pitchChange); + public abstract void teleport(@NotNull Player player, @NotNull Location location); } diff --git a/api/src/main/java/net/countercraft/movecraft/WorldHandler.java b/api/src/main/java/net/countercraft/movecraft/WorldHandler.java index bbf829daf..3b72e569f 100644 --- a/api/src/main/java/net/countercraft/movecraft/WorldHandler.java +++ b/api/src/main/java/net/countercraft/movecraft/WorldHandler.java @@ -15,8 +15,10 @@ public abstract class WorldHandler { public abstract void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation newLocation, @NotNull World world); public abstract void setBlockFast(@NotNull Location location, @NotNull BlockData data); public abstract void setBlockFast(@NotNull Location location, @NotNull MovecraftRotation rotation, @NotNull BlockData data); - public abstract @Nullable Location getAccessLocation(@NotNull InventoryView inventoryView); - public abstract void setAccessLocation(@NotNull InventoryView inventoryView, @NotNull Location location); + @Deprecated(forRemoval = true) + public abstract @Nullable Location getAccessLocation(@NotNull InventoryView inventoryView); // Not needed for 1.20+, remove when dropping support for 1.18.2 + @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 static @NotNull String getPackageName(@NotNull String minecraftVersion) { String[] parts = minecraftVersion.split("\\."); diff --git a/api/src/main/java/net/countercraft/movecraft/craft/Craft.java b/api/src/main/java/net/countercraft/movecraft/craft/Craft.java index deaad0c72..16f416a51 100644 --- a/api/src/main/java/net/countercraft/movecraft/craft/Craft.java +++ b/api/src/main/java/net/countercraft/movecraft/craft/Craft.java @@ -21,10 +21,10 @@ import net.countercraft.movecraft.MovecraftLocation; import net.countercraft.movecraft.MovecraftRotation; import net.countercraft.movecraft.TrackedLocation; -import net.countercraft.movecraft.craft.datatag.CraftDataTagContainer; import net.countercraft.movecraft.craft.datatag.CraftDataTagKey; import net.countercraft.movecraft.craft.datatag.CraftDataTagRegistry; import net.countercraft.movecraft.craft.type.CraftType; +import net.countercraft.movecraft.craft.type.RequiredBlockEntry; import net.countercraft.movecraft.processing.MovecraftWorld; import net.countercraft.movecraft.util.Counter; import net.countercraft.movecraft.util.MathUtils; @@ -48,6 +48,8 @@ public interface Craft { CraftDataTagKey> CONTACTS = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "contacts"), craft -> new ArrayList<>(0)); CraftDataTagKey FUEL = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "fuel"), craft -> 0D); CraftDataTagKey> MATERIALS = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "materials"), craft -> new Counter<>()); + CraftDataTagKey> FLYBLOCKS = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "flyblocks"), craft -> new Counter<>()); + CraftDataTagKey> MOVEBLOCKS = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "moveblocks"), craft -> new Counter<>()); CraftDataTagKey NON_NEGLIGIBLE_BLOCKS = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "non-negligible-blocks"), Craft::getOrigBlockCount); CraftDataTagKey NON_NEGLIGIBLE_SOLID_BLOCKS = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "non-negligible-solid-blocks"), Craft::getOrigBlockCount); diff --git a/api/src/main/java/net/countercraft/movecraft/craft/type/CraftType.java b/api/src/main/java/net/countercraft/movecraft/craft/type/CraftType.java index 53c08d139..d0f66b287 100644 --- a/api/src/main/java/net/countercraft/movecraft/craft/type/CraftType.java +++ b/api/src/main/java/net/countercraft/movecraft/craft/type/CraftType.java @@ -43,10 +43,7 @@ import net.countercraft.movecraft.util.Tags; import net.kyori.adventure.key.Key; import net.kyori.adventure.sound.Sound; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.World; +import org.bukkit.*; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -129,6 +126,7 @@ final public class CraftType { public static final NamespacedKey EXPLODE_ON_CRASH = buildKey("explode_on_crash"); public static final NamespacedKey INCENDIARY_ON_CRASH = buildKey("incendiary_on_crash"); public static final NamespacedKey COLLISION_EXPLOSION = buildKey("collision_explosion"); + public static final NamespacedKey UNDERWATER_COLLISION_EXPLOSION = buildKey("underwater_collision_explosion"); private static final NamespacedKey MIN_HEIGHT_LIMIT = buildKey("min_height_limit"); // Private key used as default for PER_WORLD_MIN_HEIGHT_LIMIT public static final NamespacedKey PER_WORLD_MIN_HEIGHT_LIMIT = buildKey("per_world_min_height_limit"); @@ -190,6 +188,8 @@ final public class CraftType { public static final NamespacedKey CRUISE_ON_PILOT_LIFETIME = buildKey("cruise_on_pilot_lifetime"); public static final NamespacedKey EXPLOSION_ARMING_TIME = buildKey("explosion_arming_time"); + public static final NamespacedKey DIRECTIONAL_DEPENDENT_MATERIALS = buildKey("directional_dependent_materials"); + public static final NamespacedKey ALLOW_INTERNAL_COLLISION_EXPLOSION = buildKey("allow_internal_collision_explosion"); //endregion @Contract("_ -> new") @@ -392,6 +392,14 @@ public static void registerTypeValidator(Predicate validator, String /* Optional properties */ registerProperty(new RequiredBlockProperty("flyblocks", FLY_BLOCKS, type -> new HashSet<>())); registerProperty(new RequiredBlockProperty("detectionblocks", DETECTION_BLOCKS, type -> new HashSet<>())); + registerProperty(new MaterialSetProperty("directionDependentMaterials", DIRECTIONAL_DEPENDENT_MATERIALS, type -> { + var set = EnumSet.of(Material.LADDER, Material.LEVER, Material.GRINDSTONE); + set.addAll(Tag.WALL_SIGNS.getValues()); + set.addAll(Tags.WALL_TORCHES); + set.addAll(Tags.LANTERNS); + return set; + })); + registerProperty(new ObjectPropertyImpl("forbiddenSignStrings", FORBIDDEN_SIGN_STRINGS, (data, type, fileKey, namespacedKey) -> data.getStringListOrEmpty(fileKey).stream().map( String::toLowerCase).collect(Collectors.toSet()), @@ -452,6 +460,7 @@ public static void registerTypeValidator(Predicate validator, String registerProperty(new FloatProperty("explodeOnCrash", EXPLODE_ON_CRASH, type -> 0F)); registerProperty(new BooleanProperty("incendiaryOnCrash", INCENDIARY_ON_CRASH, type -> false)); registerProperty(new FloatProperty("collisionExplosion", COLLISION_EXPLOSION, type -> 0F)); + registerProperty(new FloatProperty("underwaterCollisionExplosion", UNDERWATER_COLLISION_EXPLOSION, type -> type.getFloatProperty(COLLISION_EXPLOSION))); registerProperty(new IntegerProperty("minHeightLimit", MIN_HEIGHT_LIMIT, type -> Integer.MIN_VALUE)); registerProperty(new PerWorldProperty<>("perWorldMinHeightLimit", PER_WORLD_MIN_HEIGHT_LIMIT, (type, worldName) -> type.getIntProperty(MIN_HEIGHT_LIMIT))); @@ -566,6 +575,7 @@ else if (o instanceof Integer) registerProperty(new BooleanProperty("mergePistonExtensions", MERGE_PISTON_EXTENSIONS, type -> false)); registerProperty(new IntegerProperty("cruiseOnPilotLifetime", CRUISE_ON_PILOT_LIFETIME, type -> 15*20)); registerProperty(new IntegerProperty("explosionArmingTime", EXPLOSION_ARMING_TIME, type -> 1000)); + registerProperty(new BooleanProperty("allowInternalCollisionExplosion", ALLOW_INTERNAL_COLLISION_EXPLOSION, type -> false)); /* Craft type transforms */ // Convert speed to TICK_COOLDOWN diff --git a/api/src/main/java/net/countercraft/movecraft/processing/WorldManager.java b/api/src/main/java/net/countercraft/movecraft/processing/WorldManager.java index ecef2e3df..74378240d 100644 --- a/api/src/main/java/net/countercraft/movecraft/processing/WorldManager.java +++ b/api/src/main/java/net/countercraft/movecraft/processing/WorldManager.java @@ -4,6 +4,7 @@ import net.countercraft.movecraft.util.CompletableFutureTask; import org.bukkit.Bukkit; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -32,7 +33,7 @@ public String toString(){ }; private final ConcurrentLinkedQueue worldChanges = new ConcurrentLinkedQueue<>(); - private final ConcurrentLinkedQueue> tasks = new ConcurrentLinkedQueue<>(); + private final ConcurrentLinkedQueue> tasks = new ConcurrentLinkedQueue<>(); private final BlockingQueue currentTasks = new LinkedBlockingQueue<>(); private volatile boolean running = false; @@ -122,7 +123,7 @@ public void submit(Runnable task){ }); } - public void submit(Supplier task){ + public void submit(Supplier<@Nullable Effect> task){ tasks.add(task); } diff --git a/api/src/main/java/net/countercraft/movecraft/processing/effects/Effect.java b/api/src/main/java/net/countercraft/movecraft/processing/effects/Effect.java index 284ddbff4..f12d2f458 100644 --- a/api/src/main/java/net/countercraft/movecraft/processing/effects/Effect.java +++ b/api/src/main/java/net/countercraft/movecraft/processing/effects/Effect.java @@ -1,24 +1,79 @@ package net.countercraft.movecraft.processing.effects; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; + @FunctionalInterface public interface Effect { + /** + * A no-op effect for use in systems where a non-null effect is needed + */ + Effect NONE = new Effect() { + @Override + public void run() { + // No-op + } + + @Override + public boolean isAsync() { + return true; + } + + @Override + public @NotNull Effect andThen(@Nullable Effect chain){ + return chain == null ? this : chain; + } + }; + void run(); default boolean isAsync(){ return false; } - default @NotNull - Effect andThen(@Nullable Effect chain){ - if(chain == null){ + default @NotNull Effect andThen(@Nullable Effect chain){ + return new AndEffect(this, chain); + } + + class AndEffect implements Effect { + private final List effects = new ArrayList<>(); + + public AndEffect(Effect... effects){ + for (Effect effect : effects) { + andThen(effect); + } + } + + @Override + public void run() { + effects.forEach(Effect::run); + } + + @Override + public @NotNull Effect andThen(@Nullable Effect chain) { + if(this == chain){ + // copy if chaining to self to prevent concurrent modification + effects.addAll(effects.stream().toList()); + + return this; + } else if(chain instanceof AndEffect andChain){ + // Merge other AndChain instances + effects.addAll(andChain.effects); + + return this; + } else if(chain == NONE || chain == null){ + // Skip NONE + return this; + } + + // Otherwise add to current chain + effects.add(chain); + return this; } - return () -> { - this.run(); - chain.run(); - }; } } diff --git a/api/src/main/java/net/countercraft/movecraft/util/BukkitTeleport.java b/api/src/main/java/net/countercraft/movecraft/util/BukkitTeleport.java index 7bc69dc9e..702d3b26f 100644 --- a/api/src/main/java/net/countercraft/movecraft/util/BukkitTeleport.java +++ b/api/src/main/java/net/countercraft/movecraft/util/BukkitTeleport.java @@ -7,9 +7,7 @@ public class BukkitTeleport extends SmoothTeleport { @Override - public void teleport(Player player, @NotNull Location location, float yawChange, float pitchChange) { - location.setYaw(player.getLocation().getYaw() + yawChange); - location.setPitch(player.getLocation().getPitch() + pitchChange); + public void teleport(@NotNull Player player, @NotNull Location location) { player.teleport(location); } } diff --git a/api/src/main/java/net/countercraft/movecraft/util/CollectorUtils.java b/api/src/main/java/net/countercraft/movecraft/util/CollectorUtils.java new file mode 100644 index 000000000..f98c4c4da --- /dev/null +++ b/api/src/main/java/net/countercraft/movecraft/util/CollectorUtils.java @@ -0,0 +1,16 @@ +package net.countercraft.movecraft.util; + +import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.util.hitboxes.BitmapHitBox; + +import java.util.stream.Collector; + +public class CollectorUtils { + /** + * Provides a collector for reducing streams of MovecraftLocations to HitBox instances + * @return A HitBox containing all collected MovecraftLocations + */ + public static Collector toHitBox(){ + return Collector.of(BitmapHitBox::new, BitmapHitBox::add, BitmapHitBox::union, t->t); + } +} diff --git a/api/src/main/java/net/countercraft/movecraft/util/Tags.java b/api/src/main/java/net/countercraft/movecraft/util/Tags.java index 6e8a03257..399ebb92a 100644 --- a/api/src/main/java/net/countercraft/movecraft/util/Tags.java +++ b/api/src/main/java/net/countercraft/movecraft/util/Tags.java @@ -4,14 +4,11 @@ import org.bukkit.Keyed; import org.bukkit.Material; import org.bukkit.NamespacedKey; -import org.bukkit.Registry; import org.bukkit.Tag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.EnumSet; -import java.util.HashSet; -import java.util.Set; public class Tags { public static final EnumSet WATER = EnumSet.of(Material.WATER, Material.BUBBLE_COLUMN); @@ -22,6 +19,8 @@ public class Tags { public static final EnumSet FRAGILE_MATERIALS = EnumSet.noneOf(Material.class); public static final EnumSet FALL_THROUGH_BLOCKS = EnumSet.noneOf(Material.class); public static final EnumSet BUCKETS = EnumSet.of(Material.LAVA_BUCKET, Material.WATER_BUCKET, Material.MILK_BUCKET, Material.COD_BUCKET, Material.PUFFERFISH_BUCKET, Material.SALMON_BUCKET, Material.TROPICAL_FISH_BUCKET); + public static final EnumSet WALL_TORCHES = EnumSet.of(Material.WALL_TORCH, Material.SOUL_WALL_TORCH, Material.REDSTONE_WALL_TORCH); + public static final EnumSet LANTERNS = EnumSet.of(Material.LANTERN, Material.SOUL_LANTERN); static { FRAGILE_MATERIALS.add(Material.PISTON_HEAD); diff --git a/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts index 91e1dbe0f..8d453cbd9 100644 --- a/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts @@ -6,12 +6,11 @@ repositories { mavenLocal() maven("https://repo.maven.apache.org/maven2/") maven("https://oss.sonatype.org/content/repositories/snapshots/") - maven("https://repo.dmulloy2.net/nexus/repository/public/") maven("https://repo.papermc.io/repository/maven-public/") } group = "net.countercraft" -version = "8.0.0_beta-5_dev-1" +version = "8.0.0_beta-5" tasks.withType() { options.encoding = "UTF-8" 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 4db751f85..5babf0fe9 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 @@ -70,6 +70,7 @@ public void rotateCraft(@NotNull Craft craft, @NotNull MovecraftLocation originP ServerLevel nativeWorld = ((CraftWorld) craft.getWorld()).getHandle(); List tiles = new ArrayList<>(); List ticks = new ArrayList<>(); + //get the tiles for (BlockPos position : rotatedPositions.keySet()) { diff --git a/v1_18/src/main/java/net/countercraft/movecraft/support/v1_18/IAsyncChunk.java b/v1_18/src/main/java/net/countercraft/movecraft/support/v1_18/IAsyncChunk.java index 72d98448c..2cf850445 100644 --- a/v1_18/src/main/java/net/countercraft/movecraft/support/v1_18/IAsyncChunk.java +++ b/v1_18/src/main/java/net/countercraft/movecraft/support/v1_18/IAsyncChunk.java @@ -7,6 +7,7 @@ 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 org.bukkit.Chunk; import org.bukkit.Material; import org.bukkit.block.BlockState; @@ -26,8 +27,11 @@ public BlockState load(@NotNull MovecraftLocation movecraftLocation) { } }); + private final ChunkAccess handle; + public IAsyncChunk(@NotNull Chunk chunk) { super(chunk); + handle = this.chunk.getHandle(); } @NotNull @@ -51,7 +55,7 @@ public Material getType(@NotNull MovecraftLocation location){ @Override @NotNull public BlockData getData(@NotNull MovecraftLocation location){ - return CraftBlockData.fromData(chunk.getHandle().getBlockState(new BlockPos(location.getX(), location.getY(), location.getZ()))); + return CraftBlockData.fromData(handle.getBlockState(new BlockPos(location.getX(), location.getY(), location.getZ()))); } } diff --git a/v1_18/src/main/java/net/countercraft/movecraft/support/v1_18/ISmoothTeleport.java b/v1_18/src/main/java/net/countercraft/movecraft/support/v1_18/ISmoothTeleport.java index 5c8beb91f..be1b7b0be 100644 --- a/v1_18/src/main/java/net/countercraft/movecraft/support/v1_18/ISmoothTeleport.java +++ b/v1_18/src/main/java/net/countercraft/movecraft/support/v1_18/ISmoothTeleport.java @@ -75,10 +75,12 @@ public ISmoothTeleport() throws ClassNotFoundException, NoSuchMethodException, N pitchField = ReflectUtils.getField(entityClass, "aA"); // yRot } - public void teleport(Player player, @NotNull Location location, float yawChange, float pitchChange) { + public void teleport(@NotNull Player player, @NotNull Location location) { double x = location.getX(); double y = location.getY(); double z = location.getZ(); + float yawChange = location.getYaw() - player.getLocation().getYaw(); + float pitchChange = player.getLocation().getPitch() - location.getPitch(); Object handle = ReflectUtils.getHandle(player); try { positionMethod.invoke(handle, x, y, z, yawField.get(handle), pitchField.get(handle)); 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 019dd0ba8..cd2a5565a 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 @@ -16,9 +16,11 @@ 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; +import net.minecraft.world.ticks.LevelChunkTicks; import net.minecraft.world.ticks.ScheduledTick; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -31,7 +33,12 @@ import org.jetbrains.annotations.Nullable; import java.lang.reflect.Field; -import java.util.*; +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; @SuppressWarnings("unused") public class IWorldHandler extends WorldHandler { @@ -67,15 +74,20 @@ public void rotateCraft(@NotNull Craft craft, @NotNull MovecraftLocation originP //******************************************* 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) - continue; -// tile.a(ROTATION[rotation.ordinal()]); + if (tile != null) + tiles.add(new TileHolder(tile, position)); + //get the nextTick to move with the tile - tiles.add(new TileHolder(tile, tickProvider.getNextTick(nativeWorld, position), position)); + 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)); + } } //******************************************* @@ -100,12 +112,16 @@ public void rotateCraft(@NotNull Craft craft, @NotNull MovecraftLocation originP //* Step four: replace all the tiles * //******************************************* //TODO: go by chunks - for (TileHolder tileHolder : tiles) { + for (TileHolder tileHolder : tiles) moveBlockEntity(nativeWorld, rotatedPositions.get(tileHolder.getTilePosition()), tileHolder.getTile()); - if (tileHolder.getNextTick() == null) - continue; + for (TickHolder tickHolder : ticks) { 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())); + nativeWorld.getBlockTicks().schedule(new ScheduledTick<>( + (Block) tickHolder.getTick().type(), + rotatedPositions.get(tickHolder.getTick().pos()), + tickHolder.getTick().triggerTick() - currentTime, + tickHolder.getTick().priority(), + tickHolder.getTick().subTickOrder())); } //******************************************* @@ -134,20 +150,24 @@ public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation disp //* Step two: Get the tiles * //******************************************* List tiles = new ArrayList<>(); + List ticks = 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 + if (tile != null) + tiles.add(new TileHolder(tile,position)); - //nativeWorld.capturedTileEntities.remove(position); - //nativeWorld.getChunkAtWorldCoords(position).getTileEntities().remove(position); - tiles.add(new TileHolder(tile, tickProvider.getNextTick(oldNativeWorld, position), 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)); + } } //******************************************* @@ -173,13 +193,11 @@ public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation disp //* 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); + for (TileHolder tileHolder : tiles) moveBlockEntity(nativeWorld, tileHolder.getTilePosition().offset(translateVector), tileHolder.getTile()); - if (tileHolder.getNextTick() == null) - continue; + for (TickHolder tickHolder : ticks) { 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())); + nativeWorld.getBlockTicks().schedule(new ScheduledTick<>((Block) tickHolder.getTick().type(), tickHolder.getTickPosition().offset(translateVector), tickHolder.getTick().triggerTick() - currentTime, tickHolder.getTick().priority(), tickHolder.getTick().subTickOrder())); } //******************************************* //* Step five: Destroy the leftovers * @@ -244,29 +262,13 @@ public void setBlockFast(@NotNull Location location, @NotNull MovecraftRotation @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(); - } - } + // 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) { - 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); + // 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) { @@ -291,14 +293,11 @@ private void moveBlockEntity(@NotNull Level nativeWorld, @NotNull BlockPos newPo 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) { + public TileHolder(@NotNull BlockEntity tile, @NotNull BlockPos tilePosition) { this.tile = tile; - this.nextTick = nextTick; this.tilePosition = tilePosition; } @@ -308,14 +307,32 @@ public BlockEntity getTile() { return tile; } - @Nullable - public ScheduledTick getNextTick() { - return nextTick; - } - @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; + } + } } \ No newline at end of file diff --git a/v1_20/src/main/java/net/countercraft/movecraft/compat/v1_20/NextTickProvider.java b/v1_20/src/main/java/net/countercraft/movecraft/compat/v1_20/NextTickProvider.java index bceb388fc..1513e5ef4 100644 --- a/v1_20/src/main/java/net/countercraft/movecraft/compat/v1_20/NextTickProvider.java +++ b/v1_20/src/main/java/net/countercraft/movecraft/compat/v1_20/NextTickProvider.java @@ -4,6 +4,7 @@ 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.LevelChunkTicks; import net.minecraft.world.ticks.LevelTicks; import net.minecraft.world.ticks.ScheduledTick; import org.jetbrains.annotations.NotNull; @@ -12,31 +13,28 @@ import java.lang.reflect.Field; import java.util.List; import java.util.Queue; +import java.util.stream.Stream; public class NextTickProvider { @Nullable public ScheduledTick getNextTick(@NotNull ServerLevel world, @NotNull BlockPos position){ - LevelTicks tickList = world.getBlockTicks(); + LevelChunkTicks tickList = (LevelChunkTicks) world + .getChunk(position) + .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(); ) { + + Stream> ticks = tickList.getAll(); + + for (var iter = ticks.iterator(); iter.hasNext(); ) { var next = iter.next(); if (!next.pos().equals(position)) { continue; } - iter.remove(); return next; } return null; diff --git a/v1_20/src/main/java/net/countercraft/movecraft/support/v1_20/ISmoothTeleport.java b/v1_20/src/main/java/net/countercraft/movecraft/support/v1_20/ISmoothTeleport.java index f1224b7ea..ca4a4731c 100644 --- a/v1_20/src/main/java/net/countercraft/movecraft/support/v1_20/ISmoothTeleport.java +++ b/v1_20/src/main/java/net/countercraft/movecraft/support/v1_20/ISmoothTeleport.java @@ -1,57 +1,23 @@ package net.countercraft.movecraft.support.v1_20; +import io.papermc.paper.entity.TeleportFlag; 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.20.6 - */ 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(); - } + public void teleport(@NotNull Player player, @NotNull Location location) { + player.teleport( + location, + TeleportFlag.Relative.X, + TeleportFlag.Relative.Y, + TeleportFlag.Relative.Z, + TeleportFlag.Relative.PITCH, + TeleportFlag.Relative.YAW, + TeleportFlag.EntityState.RETAIN_OPEN_INVENTORY, + TeleportFlag.EntityState.RETAIN_VEHICLE, + TeleportFlag.EntityState.RETAIN_PASSENGERS + ); } } diff --git a/v1_21/build.gradle.kts b/v1_21/build.gradle.kts index 8d4c44fcd..d84125889 100644 --- a/v1_21/build.gradle.kts +++ b/v1_21/build.gradle.kts @@ -7,7 +7,7 @@ java.toolchain.languageVersion = JavaLanguageVersion.of(21) dependencies { api(project(":movecraft-api")) - paperweight.paperDevBundle("1.21-R0.1-SNAPSHOT") + paperweight.paperDevBundle("1.21.1-R0.1-SNAPSHOT") } description = "Movecraft-v1_21" 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 e2ed2fc0d..30f1e8182 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 @@ -19,6 +19,7 @@ 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.LevelChunkTicks; import net.minecraft.world.ticks.ScheduledTick; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -32,6 +33,7 @@ import java.lang.reflect.Field; import java.util.*; +import java.util.function.Predicate; @SuppressWarnings("unused") public class IWorldHandler extends WorldHandler { @@ -48,7 +50,7 @@ public class IWorldHandler extends WorldHandler { public IWorldHandler() { String version = Bukkit.getServer().getMinecraftVersion(); - if (!version.equals("1.21")) + if (!version.equals("1.21.1")) throw new IllegalStateException("Movecraft is not compatible with this version of Minecraft: " + version); } @@ -67,15 +69,21 @@ public void rotateCraft(@NotNull Craft craft, @NotNull MovecraftLocation originP //******************************************* 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) - continue; -// tile.a(ROTATION[rotation.ordinal()]); + if (tile != null) + tiles.add(new TileHolder(tile, position)); + //get the nextTick to move with the tile - tiles.add(new TileHolder(tile, tickProvider.getNextTick(nativeWorld, position), position)); + 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)); + } } //******************************************* @@ -100,12 +108,16 @@ public void rotateCraft(@NotNull Craft craft, @NotNull MovecraftLocation originP //* Step four: replace all the tiles * //******************************************* //TODO: go by chunks - for (TileHolder tileHolder : tiles) { + for (TileHolder tileHolder : tiles) moveBlockEntity(nativeWorld, rotatedPositions.get(tileHolder.getTilePosition()), tileHolder.getTile()); - if (tileHolder.getNextTick() == null) - continue; + for (TickHolder tickHolder : ticks) { 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())); + nativeWorld.getBlockTicks().schedule(new ScheduledTick<>( + (Block) tickHolder.getTick().type(), + rotatedPositions.get(tickHolder.getTick().pos()), + tickHolder.getTick().triggerTick() - currentTime, + tickHolder.getTick().priority(), + tickHolder.getTick().subTickOrder())); } //******************************************* @@ -134,6 +146,7 @@ public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation disp //* Step two: Get the tiles * //******************************************* List tiles = new ArrayList<>(); + List ticks = new ArrayList<>(); //get the tiles for (int i = 0, positionsSize = positions.size(); i < positionsSize; i++) { BlockPos position = positions.get(i); @@ -141,14 +154,16 @@ public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation disp 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)); + 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 * @@ -173,13 +188,11 @@ public void translateCraft(@NotNull Craft craft, @NotNull MovecraftLocation disp //* 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); + for (TileHolder tileHolder : tiles) moveBlockEntity(nativeWorld, tileHolder.getTilePosition().offset(translateVector), tileHolder.getTile()); - if (tileHolder.getNextTick() == null) - continue; + for (TickHolder tickHolder : ticks) { 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())); + nativeWorld.getBlockTicks().schedule(new ScheduledTick<>((Block) tickHolder.getTick().type(), tickHolder.getTickPosition().offset(translateVector), tickHolder.getTick().triggerTick() - currentTime, tickHolder.getTick().priority(), tickHolder.getTick().subTickOrder())); } //******************************************* //* Step five: Destroy the leftovers * @@ -244,29 +257,13 @@ public void setBlockFast(@NotNull Location location, @NotNull MovecraftRotation @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(); - } - } + // 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) { - 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); + // 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) { @@ -291,14 +288,11 @@ private void moveBlockEntity(@NotNull Level nativeWorld, @NotNull BlockPos newPo 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) { + public TileHolder(@NotNull BlockEntity tile, @NotNull BlockPos tilePosition) { this.tile = tile; - this.nextTick = nextTick; this.tilePosition = tilePosition; } @@ -308,14 +302,32 @@ public BlockEntity getTile() { return tile; } - @Nullable - public ScheduledTick getNextTick() { - return nextTick; - } - @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; + } + } } \ 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 index c29d9ddce..b5898f390 100644 --- 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 @@ -4,39 +4,34 @@ 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.LevelChunkTicks; 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; +import java.util.stream.Stream; public class NextTickProvider { @Nullable public ScheduledTick getNextTick(@NotNull ServerLevel world, @NotNull BlockPos position){ - LevelTicks tickList = world.getBlockTicks(); + LevelChunkTicks tickList = (LevelChunkTicks) world + .getChunk(position) + .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(); ) { + + Stream> ticks = tickList.getAll(); + + for (var iter = ticks.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/ISmoothTeleport.java b/v1_21/src/main/java/net/countercraft/movecraft/support/v1_21/ISmoothTeleport.java index f7bdfff99..edd20345f 100644 --- 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 @@ -1,57 +1,23 @@ package net.countercraft.movecraft.support.v1_21; +import io.papermc.paper.entity.TeleportFlag; 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(); - } + public void teleport(@NotNull Player player, @NotNull Location location) { + player.teleport( + location, + TeleportFlag.Relative.X, + TeleportFlag.Relative.Y, + TeleportFlag.Relative.Z, + TeleportFlag.Relative.PITCH, + TeleportFlag.Relative.YAW, + TeleportFlag.EntityState.RETAIN_OPEN_INVENTORY, + TeleportFlag.EntityState.RETAIN_VEHICLE, + TeleportFlag.EntityState.RETAIN_PASSENGERS + ); } }