From 3183a3140de986edd84a447388353ef85301cf57 Mon Sep 17 00:00:00 2001
From: Tofaa <82680183+Tofaa2@users.noreply.github.com>
Date: Mon, 29 Jan 2024 13:19:45 +0400
Subject: [PATCH] let the entities be aware of their world

---
 .idea/workspace.xml                           |  20 +-
 .../java/me/tofaa/entitylib/WorldWrapper.java |   1 +
 .../entitylib/meta/CompatibilityIndex.java    |   9 +
 .../me/tofaa/entitylib/meta/UsedVersion.java  |   9 -
 .../entitylib/meta/VersionCompatCheck.java    |  15 -
 .../me/tofaa/entitylib/tick/Tickable.java     |  15 +
 .../entitylib/wrapper/WrapperEntity.java      | 259 ++++++++++++++++--
 .../ai/goals/RandomHeadMovementGoal.java      |   2 +-
 .../common/AbstractWorldWrapper.java          |   3 +-
 9 files changed, 280 insertions(+), 53 deletions(-)
 create mode 100644 api/src/main/java/me/tofaa/entitylib/meta/CompatibilityIndex.java
 delete mode 100644 api/src/main/java/me/tofaa/entitylib/meta/UsedVersion.java
 delete mode 100644 api/src/main/java/me/tofaa/entitylib/meta/VersionCompatCheck.java

diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index aa331ae..d31bff8 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,16 +5,15 @@
   </component>
   <component name="ChangeListManager">
     <list default="true" id="9d5d9b6f-43c8-41a4-bb42-a66ffc96c9b0" name="Changes" comment="">
+      <change afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/meta/CompatibilityIndex.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/APISettings.java" beforeDir="false" afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/APIConfig.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/EntityLib.java" beforeDir="false" afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/EntityLib.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/EntityLibAPI.java" beforeDir="false" afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/EntityLibAPI.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/Platform.java" beforeDir="false" afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/Platform.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/common/src/main/java/me/tofaa/entitylib/common/AbstractEntityLibAPI.java" beforeDir="false" afterPath="$PROJECT_DIR$/common/src/main/java/me/tofaa/entitylib/common/AbstractEntityLibAPI.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/common/src/main/java/me/tofaa/entitylib/common/AbstractPlatform.java" beforeDir="false" afterPath="$PROJECT_DIR$/common/src/main/java/me/tofaa/entitylib/common/AbstractPlatform.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibAPI.java" beforeDir="false" afterPath="$PROJECT_DIR$/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibAPI.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibPlatform.java" beforeDir="false" afterPath="$PROJECT_DIR$/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibPlatform.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/test-plugin/src/main/java/me/tofaa/testentitylib/TestEntityLibPlugin.java" beforeDir="false" afterPath="$PROJECT_DIR$/test-plugin/src/main/java/me/tofaa/testentitylib/TestEntityLibPlugin.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/WorldWrapper.java" beforeDir="false" afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/WorldWrapper.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/meta/UsedVersion.java" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/meta/VersionCompatCheck.java" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/tick/Tickable.java" beforeDir="false" afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/tick/Tickable.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntity.java" beforeDir="false" afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntity.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/wrapper/ai/goals/RandomHeadMovementGoal.java" beforeDir="false" afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/wrapper/ai/goals/RandomHeadMovementGoal.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/common/src/main/java/me/tofaa/entitylib/common/AbstractWorldWrapper.java" beforeDir="false" afterPath="$PROJECT_DIR$/common/src/main/java/me/tofaa/entitylib/common/AbstractWorldWrapper.java" afterDir="false" />
     </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -256,7 +255,8 @@
       <workItem from="1706248178234" duration="17096000" />
       <workItem from="1706284605696" duration="11691000" />
       <workItem from="1706371324325" duration="1187000" />
-      <workItem from="1706443875388" duration="3245000" />
+      <workItem from="1706443875388" duration="4827000" />
+      <workItem from="1706513591682" duration="2661000" />
     </task>
     <servers />
   </component>
diff --git a/api/src/main/java/me/tofaa/entitylib/WorldWrapper.java b/api/src/main/java/me/tofaa/entitylib/WorldWrapper.java
index 76d809a..d421c35 100644
--- a/api/src/main/java/me/tofaa/entitylib/WorldWrapper.java
+++ b/api/src/main/java/me/tofaa/entitylib/WorldWrapper.java
@@ -4,6 +4,7 @@ import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
 import com.github.retrooper.packetevents.protocol.world.Dimension;
 import com.github.retrooper.packetevents.protocol.world.Location;
 import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
+import me.tofaa.entitylib.tick.TickContainer;
 import me.tofaa.entitylib.wrapper.WrapperEntity;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
diff --git a/api/src/main/java/me/tofaa/entitylib/meta/CompatibilityIndex.java b/api/src/main/java/me/tofaa/entitylib/meta/CompatibilityIndex.java
new file mode 100644
index 0000000..2683859
--- /dev/null
+++ b/api/src/main/java/me/tofaa/entitylib/meta/CompatibilityIndex.java
@@ -0,0 +1,9 @@
+package me.tofaa.entitylib.meta;
+
+import com.github.retrooper.packetevents.protocol.entity.data.EntityDataType;
+
+public interface CompatibilityIndex {
+
+    byte getOffSet(byte latestOffset, EntityDataType<?> type);
+
+}
diff --git a/api/src/main/java/me/tofaa/entitylib/meta/UsedVersion.java b/api/src/main/java/me/tofaa/entitylib/meta/UsedVersion.java
deleted file mode 100644
index d0bbd32..0000000
--- a/api/src/main/java/me/tofaa/entitylib/meta/UsedVersion.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package me.tofaa.entitylib.meta;
-
-import com.github.retrooper.packetevents.manager.server.ServerVersion;
-
-public @interface UsedVersion {
-
-    ServerVersion[] value();
-
-}
diff --git a/api/src/main/java/me/tofaa/entitylib/meta/VersionCompatCheck.java b/api/src/main/java/me/tofaa/entitylib/meta/VersionCompatCheck.java
deleted file mode 100644
index 9bc5281..0000000
--- a/api/src/main/java/me/tofaa/entitylib/meta/VersionCompatCheck.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package me.tofaa.entitylib.meta;
-
-import com.github.retrooper.packetevents.manager.server.ServerVersion;
-import com.github.retrooper.packetevents.manager.server.VersionComparison;
-import me.tofaa.entitylib.EntityLib;
-
-public final class VersionCompatCheck  {
-
-    private VersionCompatCheck() {}
-
-    static boolean isVersion(ServerVersion version) {
-        return version.is(VersionComparison.EQUALS, EntityLib.getApi().getPacketEvents().getServerManager().getVersion());
-    }
-
-}
diff --git a/api/src/main/java/me/tofaa/entitylib/tick/Tickable.java b/api/src/main/java/me/tofaa/entitylib/tick/Tickable.java
index 11dd41b..4b38f89 100644
--- a/api/src/main/java/me/tofaa/entitylib/tick/Tickable.java
+++ b/api/src/main/java/me/tofaa/entitylib/tick/Tickable.java
@@ -2,6 +2,21 @@ package me.tofaa.entitylib.tick;
 
 public interface Tickable {
 
+    /**
+     * @return if the entity is ticking.
+     */
+    boolean isTicking();
+
+    /**
+     * Sets the entities ticking status, incase you want to stop ticking for a moment then continue
+     * @param ticking if the entity should tick.
+     */
+    void setTicking(boolean ticking);
+
+    /**
+     * Ticks this entity. This method will not be called if {@link #isTicking()} returns false.
+     * @param time the current time in milliseconds.
+     */
     void tick(long time);
 
 }
diff --git a/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntity.java b/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntity.java
index 32e33d6..8bf058e 100644
--- a/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntity.java
+++ b/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntity.java
@@ -2,52 +2,106 @@ package me.tofaa.entitylib.wrapper;
 
 import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
 import com.github.retrooper.packetevents.protocol.player.User;
+import com.github.retrooper.packetevents.protocol.world.BoundingBox;
 import com.github.retrooper.packetevents.protocol.world.Location;
 import com.github.retrooper.packetevents.util.Vector3d;
 import com.github.retrooper.packetevents.wrapper.PacketWrapper;
-import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerDestroyEntities;
-import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityRotation;
-import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityVelocity;
-import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSpawnEntity;
+import com.github.retrooper.packetevents.wrapper.play.server.*;
 import me.tofaa.entitylib.EntityLib;
+import me.tofaa.entitylib.WorldWrapper;
 import me.tofaa.entitylib.meta.EntityMeta;
+import me.tofaa.entitylib.meta.types.ObjectData;
 import me.tofaa.entitylib.tick.Tickable;
+import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import javax.swing.text.html.parser.Entity;
-import java.util.Collections;
-import java.util.Optional;
-import java.util.Set;
-import java.util.UUID;
+import java.util.*;
 
 public class WrapperEntity implements Tickable {
 
     private final UUID uuid;
     private final int entityId;
-
     private EntityType entityType;
     private EntityMeta entityMeta;
+    private boolean ticking;
     private Location location;
+    private Location preRidingLocation;
     private Set<UUID> viewers;
     private boolean onGround;
     private boolean spawned;
     private Vector3d velocity;
-
+    private int riding = -1;
+    private Set<Integer> passengers = new HashSet<>();
+    private WorldWrapper<?> world;
 
     public WrapperEntity(int entityId, UUID uuid, EntityType entityType, EntityMeta entityMeta) {
         this.entityId = entityId;
         this.uuid = uuid;
         this.entityType = entityType;
         this.entityMeta = entityMeta;
+        this.ticking = true;
     }
 
-    public void spawn() {}
-
-    public void despawn() {}
-
-    public void teleport(@NotNull Location location) {
+    public boolean spawn(WorldWrapper<?> world, Location location) {
+        if (spawned) return false;
         this.location = location;
+        this.world = world;
+        this.spawned = true;
+        int data = 0;
+        Optional<Vector3d> velocity;
+        double veloX = 0, veloY = 0, veloZ = 0;
+        if (entityMeta instanceof ObjectData) {
+            ObjectData od = (ObjectData) entityMeta;
+            data = od.getObjectData();
+            if (od.requiresVelocityPacketAtSpawn()) {
+                final WrapperPlayServerEntityVelocity veloPacket = getVelocityPacket();
+                veloX = veloPacket.getVelocity().getX();
+                veloY = veloPacket.getVelocity().getY();
+                veloZ = veloPacket.getVelocity().getZ();
+            }
+        }
+        if (veloX == 0 && veloY == 0 && veloZ == 0) {
+            velocity = Optional.empty();
+        } else {
+            velocity = Optional.of(new Vector3d(veloX, veloY, veloZ));
+        }
+        sendPacketToViewers(
+                new WrapperPlayServerSpawnEntity(
+                        entityId,
+                        Optional.of(this.uuid),
+                        entityType,
+                        location.getPosition(),
+                        location.getPitch(),
+                        location.getYaw(),
+                        location.getYaw(),
+                        data,
+                        velocity
+                )
+        );
+        sendPacketToViewers(entityMeta.createPacket());
+        return true;
+    }
 
+    public void despawn() {
+        if (!spawned) return;
+        spawned = false;
+        sendPacketToViewers(new WrapperPlayServerDestroyEntities(entityId));
+    }
+
+    public void teleport(WorldWrapper<?> world, @NotNull Location location) {
+        this.location = location;
+        this.world = world;
+        sendPacketToViewers(
+                new WrapperPlayServerEntityTeleport(
+                        entityId,
+                        location.getPosition(),
+                        location.getYaw(),
+                        location.getPitch(),
+                        onGround
+                )
+        );
     }
 
     public boolean addViewer(UUID uuid) {
@@ -131,12 +185,44 @@ public class WrapperEntity implements Tickable {
         return entityType;
     }
 
+    /**
+     * Returns an unmodifiable set of the passengers of the entity.
+     * @return the passengers of the entity
+     */
+    public Set<Integer> getPassengers() {
+        return Collections.unmodifiableSet(passengers);
+    }
+
+    public WrapperEntity getRiding() {
+        return world.getEntity(riding);
+    }
+
+
+
+    protected WrapperPlayServerSetPassengers createPassengerPacket() {
+        return new WrapperPlayServerSetPassengers(entityId, passengers.stream().mapToInt(i -> i).toArray());
+    }
+
 
     private WrapperPlayServerEntityVelocity getVelocityPacket() {
         Vector3d velocity = this.velocity.multiply(8000.0f / 20.0f);
         return new WrapperPlayServerEntityVelocity(entityId, velocity);
     }
 
+    public boolean isSpawned() {
+        return spawned;
+    }
+
+    @Override
+    public boolean isTicking() {
+        return ticking;
+    }
+
+    @Override
+    public void setTicking(boolean ticking) {
+        this.ticking = ticking;
+    }
+
     public boolean hasVelocity() {
         if (isOnGround()) {
             // if the entity is on the ground and only "moves" downwards, it does not have a velocity.
@@ -189,6 +275,135 @@ public class WrapperEntity implements Tickable {
         refresh();
     }
 
+    /**
+     * Adds a passenger to the entity. The passenger will be visible to all viewers of the entity.
+     * @param passenger the entity id of the passenger
+     */
+    public void addPassenger(int passenger) {
+        if (passengers.contains(passenger)) {
+            throw new IllegalArgumentException("Passenger already exists");
+        }
+        passengers.add(passenger);
+        sendPacketToViewers(createPassengerPacket());
+        WrapperEntity e = world.getEntity(passenger);
+        if (e != null) {
+            e.riding = this.entityId;
+            e.preRidingLocation = e.location;
+        }
+    }
+
+    public @Nullable Location getPreRidingLocation() {
+        return preRidingLocation;
+    }
+
+    /**
+     * @return the entity id of the entity that the entity is riding, -1 if the entity is not riding
+     */
+    public int getRidingId() {
+        return riding;
+    }
+
+
+    /**
+     * Adds multiple passengers to the entity. The passengers will be visible to all viewers of the entity.
+     * @param passengers the entity ids of the passengers
+     */
+    public void addPassengers(int... passengers) {
+        for (int passenger : passengers) {
+            addPassenger(passenger);
+        }
+    }
+
+    /**
+     * Adds a passenger to the entity. The passenger will be visible to all viewers of the entity.
+     * @param passenger the wrapper entity passenger
+     */
+    public void addPassenger(WrapperEntity passenger) {
+        addPassenger(passenger.getEntityId());
+    }
+
+    /**
+     * Adds multiple passengers to the entity. The passengers will be visible to all viewers of the entity.
+     * @param passengers the wrapper entity passengers
+     */
+    public void addPassengers(WrapperEntity... passengers) {
+        for (WrapperEntity passenger : passengers) {
+            addPassenger(passenger);
+        }
+    }
+
+    /**
+     * Removes a passenger from the entity. The passenger will be removed from the view of all viewers of the entity.
+     * @param passenger the entity id of the passenger
+     */
+    public void removePassenger(int passenger) {
+        if (!passengers.contains(passenger)) {
+            throw new IllegalArgumentException("Passenger does not exist");
+        }
+        passengers.remove(passenger);
+        sendPacketToViewers(createPassengerPacket());
+        WrapperEntity e = world.getEntity(passenger);
+        if (e != null) {
+            e.riding = -1;
+            e.teleport(world, e.preRidingLocation);
+        }
+    }
+
+    public WorldWrapper<?> getWorld() {
+        return world;
+    }
+
+    /**
+     * @param passenger the entity id of the passenger
+     * @return true if the entity has the passenger, false otherwise
+     */
+    public boolean hasPassenger(int passenger) {
+        return passengers.contains(passenger);
+    }
+
+    /**
+     * @param passenger the passenger wrapper entity
+     * @return true if the entity has the passenger, false otherwise
+     */
+    public boolean hasPassenger(WrapperEntity passenger) {
+        return hasPassenger(passenger.getEntityId());
+    }
+
+    /**
+     * Removes multiple passengers from the entity. The passengers will be removed from the view of all viewers of the entity.
+     * @param passengers the entity ids of the passengers
+     */
+    public void removePassengers(int... passengers) {
+        for (int passenger : passengers) {
+            removePassenger(passenger);
+        }
+    }
+
+    /**
+     * Removes a passenger from the entity. The passenger will be removed from the view of all viewers of the entity.
+     * @param passenger the wrapper entity passenger
+     */
+    public void removePassenger(WrapperEntity passenger) {
+        removePassenger(passenger.getEntityId());
+    }
+
+    /**
+     * Removes multiple passengers from the entity. The passengers will be removed from the view of all viewers of the entity.
+     * @param passengers the wrapper entity passengers
+     */
+    public void removePassengers(WrapperEntity... passengers) {
+        for (WrapperEntity passenger : passengers) {
+            removePassenger(passenger);
+        }
+    }
+
+    /**
+     * @return true if the entity has passengers, false otherwise
+     */
+    public boolean isRiding() {
+        return riding != -1;
+    }
+
     public @NotNull Set<UUID> getViewers() {
         return Collections.unmodifiableSet(viewers);
     }
@@ -199,6 +414,18 @@ public class WrapperEntity implements Tickable {
 
     @Override
     public void tick(long time) {
-
+        if (isRiding()) {
+            WrapperEntity riding = getRiding();
+            if (riding != null) {
+                Location l = riding.getLocation();
+                location = new Location(
+                        l.getX(),
+                        l.getY() + 1,
+                        l.getZ(),
+                        l.getYaw(),
+                        l.getPitch()
+                );
+            }
+        }
     }
 }
diff --git a/api/src/main/java/me/tofaa/entitylib/wrapper/ai/goals/RandomHeadMovementGoal.java b/api/src/main/java/me/tofaa/entitylib/wrapper/ai/goals/RandomHeadMovementGoal.java
index 973151c..af8868b 100644
--- a/api/src/main/java/me/tofaa/entitylib/wrapper/ai/goals/RandomHeadMovementGoal.java
+++ b/api/src/main/java/me/tofaa/entitylib/wrapper/ai/goals/RandomHeadMovementGoal.java
@@ -66,7 +66,7 @@ public class RandomHeadMovementGoal extends GoalSelector {
     @Override
     public void tick(long time) {
         --lookTime;
-        entity.teleport(CoordinateUtil.withDirection(entity.getLocation(), lookDirection));
+        entity.teleport(entity.getWorld(), CoordinateUtil.withDirection(entity.getLocation(), lookDirection));
     }
 
     @Override
diff --git a/common/src/main/java/me/tofaa/entitylib/common/AbstractWorldWrapper.java b/common/src/main/java/me/tofaa/entitylib/common/AbstractWorldWrapper.java
index 2fbaa67..b8515e3 100644
--- a/common/src/main/java/me/tofaa/entitylib/common/AbstractWorldWrapper.java
+++ b/common/src/main/java/me/tofaa/entitylib/common/AbstractWorldWrapper.java
@@ -37,8 +37,7 @@ public abstract class AbstractWorldWrapper<W> implements WorldWrapper<W> {
 
     @Override
     public <T extends WrapperEntity> @NotNull T spawnEntity(@NotNull T entity, @NotNull Location location) {
-        entity.teleport(location);
-        entity.spawn();
+        entity.spawn(this, location);
         entities.put(entity.getUuid(), entity);
         entitiesById.put(entity.getEntityId(), entity);
         return entity;