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 586aea1a0..d7a0a318e 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 @@ -165,14 +165,18 @@ protected void execute() { } // Rotates the craft's tracked locations, and all parent craft's. + MovecraftLocation oldOrigin = craft.getCraftOrigin(); + MovecraftLocation vectorRotated = MathUtils.rotateVec(rotation, oldOrigin.subtract(originPoint)); + craft.setDataTag(Craft.CRAFT_ORIGIN, originPoint.add(vectorRotated)); Craft temp = craft; - do { - for (Set locations : craft.getTrackedLocations().values()) { + // recursion through all subcrafts is not necessary as the trackedlocations are transferred to the subcraft + //do { + for (Set locations : temp.getTrackedLocations().values()) { for (TrackedLocation location : locations) { - location.rotate(rotation, originPoint); + location.rotate(rotation); } } - } while (temp instanceof SubCraft && (temp = ((SubCraft) temp).getParent()) != null); + //} while (temp instanceof SubCraft && (temp = ((SubCraft) temp).getParent()) != null); updates.add(new CraftRotateCommand(getCraft(),originPoint, rotation)); //rotate entities in the craft 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 a1a9390b6..01218eb34 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 @@ -3,6 +3,7 @@ import net.countercraft.movecraft.Movecraft; import net.countercraft.movecraft.MovecraftChunk; import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.TrackedLocation; import net.countercraft.movecraft.async.AsyncTask; import net.countercraft.movecraft.config.Settings; import net.countercraft.movecraft.craft.ChunkManager; @@ -318,6 +319,9 @@ protected void execute() throws InterruptedException, ExecutionException { } } + // Update the reference location for trackedlocations + craft.setDataTag(Craft.CRAFT_ORIGIN, craft.getCraftOrigin().translate(dx, dy, dz)); + if (!collisionBox.isEmpty() && craft.getType().getBoolProperty(CraftType.CRUISE_ON_PILOT)) { CraftManager.getInstance().release(craft, CraftReleaseEvent.Reason.EMPTY, false); for (MovecraftLocation location : oldHitBox) { diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/craft/BaseCraft.java b/Movecraft/src/main/java/net/countercraft/movecraft/craft/BaseCraft.java index 2eec10dfd..bd3afab8c 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/craft/BaseCraft.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/craft/BaseCraft.java @@ -41,6 +41,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; @@ -79,7 +80,7 @@ public abstract class BaseCraft implements Craft { private String name = ""; @NotNull private MovecraftLocation lastTranslation = new MovecraftLocation(0, 0, 0); - private Map> trackedLocations = new HashMap<>(); + private Map> trackedLocations = new ConcurrentHashMap<>(); @NotNull private final CraftDataTagContainer dataTagContainer; diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/listener/CraftPilotListener.java b/Movecraft/src/main/java/net/countercraft/movecraft/listener/CraftPilotListener.java index 5901cdace..466556828 100644 --- a/Movecraft/src/main/java/net/countercraft/movecraft/listener/CraftPilotListener.java +++ b/Movecraft/src/main/java/net/countercraft/movecraft/listener/CraftPilotListener.java @@ -1,17 +1,27 @@ package net.countercraft.movecraft.listener; +import com.google.common.base.Predicates; import net.countercraft.movecraft.MovecraftLocation; +import net.countercraft.movecraft.TrackedLocation; import net.countercraft.movecraft.craft.Craft; +import net.countercraft.movecraft.craft.SubCraft; import net.countercraft.movecraft.events.CraftPilotEvent; +import net.countercraft.movecraft.events.CraftReleaseEvent; +import net.countercraft.movecraft.util.hitboxes.HitBox; +import org.bukkit.NamespacedKey; import org.bukkit.block.Block; import org.bukkit.block.Sign; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.jetbrains.annotations.NotNull; +import java.util.*; +import java.util.function.Predicate; + public class CraftPilotListener implements Listener { - @EventHandler(ignoreCancelled = true) + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onCraftPilot(@NotNull CraftPilotEvent event) { // Walk through all signs and set a UUID in there final Craft craft = event.getCraft(); @@ -30,6 +40,83 @@ public void onCraftPilot(@NotNull CraftPilotEvent event) { craft.markTileStateWithUUID(tile); tile.update(); } + + // Tracked locations => Modify correctly with subcrafts + if (craft instanceof SubCraft subCraft && subCraft.getParent() != null) { + final Craft parent = subCraft.getParent(); + if (parent.getWorld() != subCraft.getWorld()) { + return; + } + transferTrackedLocations(parent, subCraft, (trackedLocation) -> subCraft.getHitBox().inBounds(trackedLocation.getAbsoluteLocation()) && subCraft.getHitBox().contains(trackedLocation.getAbsoluteLocation()), true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onCraftRelease(@NotNull CraftReleaseEvent event) { + if (event.getReason() != CraftReleaseEvent.Reason.SUB_CRAFT) { + return; + } + Craft released = event.getCraft(); + if (!(released instanceof SubCraft)) { + return; + } + SubCraft subCraft = (SubCraft) released; + if (subCraft.getParent() == null) { + return; + } + + // Attention: SquadronCrafts are also subcrafts! We need to make sure that they are at least intersecting the parent when we add back the tracked locations + final HitBox parentHitBox = subCraft.getParent().getHitBox(); + final HitBox subCraftHitBox = subCraft.getHitBox(); + + if (!parentHitBox.inBounds(subCraftHitBox.getMinX(), subCraftHitBox.getMinY(), subCraftHitBox.getMinZ())) { + if (!parentHitBox.inBounds(subCraftHitBox.getMaxX(), subCraftHitBox.getMaxY(), subCraftHitBox.getMaxZ())) { + // Subcraft cant possible be within its parent anymore + return; + } + } + + transferTrackedLocations(subCraft, subCraft.getParent(), Predicates.alwaysTrue(), true); + } + + /* + * Transfers TrackedLocations from a craft A to a craft B with a optional filter. + * This MOVES the tracked locations, so keep that in mind + */ + private static void transferTrackedLocations(final Craft a, final Craft b, Predicate filterArgument, boolean move) { + final MovecraftLocation bMidPoint = b.getHitBox().getMidPoint(); + + for (Map.Entry> entry : a.getTrackedLocations().entrySet()) { + final Set bTrackedLocations = b.getTrackedLocations().computeIfAbsent(entry.getKey(), k -> new HashSet<>()); + final Set aTrackedLocations = entry.getValue(); + + if (aTrackedLocations.isEmpty()) { + continue; + } + + // Commented out code: previous attempt to actually transfer the tracked locations, which technically is unnecessary unless for subcrafts like squadrons that actually move! + List transferred = new ArrayList<>(); + aTrackedLocations.forEach(trackedLocation -> { + if (filterArgument.test(trackedLocation)) { + if (move) { + // Technically this (the reset call) is not necessary, but we will keep it here for potential extensions by third party addons + final MovecraftLocation absoluteLocation = trackedLocation.getAbsoluteLocation(); + trackedLocation.reset(b, absoluteLocation); + if (!(bTrackedLocations.add(trackedLocation))) { + trackedLocation.reset(a, absoluteLocation); + } else { + transferred.add(trackedLocation); + } + if (!absoluteLocation.equals(trackedLocation.getAbsoluteLocation())) { + throw new IllegalStateException("Somehow the previous and transferred absolute locations are NOT the same! This should NEVER happen!"); + } + } else { + bTrackedLocations.add(trackedLocation); + } + } + }); + aTrackedLocations.removeAll(transferred); + } } } diff --git a/api/src/main/java/net/countercraft/movecraft/TrackedLocation.java b/api/src/main/java/net/countercraft/movecraft/TrackedLocation.java index 8b395d0b2..d00157e93 100644 --- a/api/src/main/java/net/countercraft/movecraft/TrackedLocation.java +++ b/api/src/main/java/net/countercraft/movecraft/TrackedLocation.java @@ -5,22 +5,29 @@ import org.bukkit.NamespacedKey; import org.jetbrains.annotations.NotNull; +import java.lang.ref.WeakReference; import java.util.HashSet; public class TrackedLocation { - private MovecraftLocation offSet; - private final Craft craft; + + private MovecraftLocation vector; + + private WeakReference craft; /** * Creates a new TrackedLocation instance which tracks a location about a craft's midpoint. - * @param craft The craft that's that tied to the location. + * @param craft The craft this trackedlocation belongs to * @param location The absolute position to track. This location will be stored as a relative * location to the craft's central hitbox. */ public TrackedLocation(@NotNull Craft craft, @NotNull MovecraftLocation location) { - this.craft = craft; - MovecraftLocation midPoint = craft.getHitBox().getMidPoint(); - offSet = location.subtract(midPoint); + this.craft = new WeakReference<>(craft); + reinit(location); + } + + protected void reinit(@NotNull MovecraftLocation location) { + Craft craft = this.getCraft(); + this.vector = location.subtract(craft.getCraftOrigin()); } /** @@ -42,8 +49,8 @@ public TrackedLocation(@NotNull Craft craft, @NotNull MovecraftLocation location * Rotates the stored location. * @param rotation A clockwise or counter-clockwise direction to rotate. */ - public void rotate(MovecraftRotation rotation, MovecraftLocation origin) { - offSet = MathUtils.rotateVec(rotation, getAbsoluteLocation().subtract(origin)); + public void rotate(MovecraftRotation rotation) { + this.vector = MathUtils.rotateVec(rotation, this.vector); } /** @@ -51,16 +58,18 @@ public void rotate(MovecraftRotation rotation, MovecraftLocation origin) { * @return Returns the absolute location instead of a vector. */ public MovecraftLocation getAbsoluteLocation() { - MovecraftLocation midPoint = craft.getHitBox().getMidPoint(); - return offSet.add(midPoint); + Craft craft = this.getCraft(); + return this.vector.add(craft.getCraftOrigin()); } /** - * Gets the stored location as a position vector relative to the midpoint. - * @return Returns the absolute location instead of a vector. + * NEVER USE THIS UNLESS ABSOLUTELY NECESSARY + * @param craft + * @param location */ - public MovecraftLocation getOffSet() { - return offSet; + public void reset(@NotNull Craft craft, @NotNull MovecraftLocation location) { + this.craft = new WeakReference<>(craft); + reinit(location); } /** @@ -68,6 +77,9 @@ public MovecraftLocation getOffSet() { * @return Returns the craft. */ public Craft getCraft() { - return craft; + if (this.craft.get() == null) { + throw new RuntimeException("Craft of tracked location is null! This indicates that the craft object was destroyed but somehow the tracked location is still around!"); + } + return this.craft.get(); } } 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 a1a44ee69..b90272780 100644 --- a/api/src/main/java/net/countercraft/movecraft/craft/Craft.java +++ b/api/src/main/java/net/countercraft/movecraft/craft/Craft.java @@ -51,12 +51,14 @@ public interface Craft { 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); + CraftDataTagKey CRAFT_ORIGIN = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "craft-origin"), craft -> craft.getHitBox().getMidPoint()); // Java disallows private or protected fields in interfaces, this is a workaround class Hidden { // Concurrent so we don't have problems when accessing async (useful for addon plugins that want to do stuff async, for example NPC crafts with complex off-thread pathfinding) protected static final Map uuidToCraft = Collections.synchronizedMap(new WeakHashMap<>()); } + public static Craft getCraftByUUID(final UUID uuid) { return Hidden.uuidToCraft.getOrDefault(uuid, null); } @@ -293,4 +295,8 @@ public default void removeUUIDMarkFromTile(TileState tile) { } Map> getTrackedLocations(); + + public default MovecraftLocation getCraftOrigin() { + return this.getDataTag(CRAFT_ORIGIN); + } }