From 3091083821b8a7a6642c7ff0b9599e1f79e5a7cf Mon Sep 17 00:00:00 2001
From: Tofaa <82680183+Tofaa2@users.noreply.github.com>
Date: Fri, 26 Jan 2024 13:17:38 +0400
Subject: [PATCH] rewrite to a more platformized structure.

---
 .idea/workspace.xml                           |  71 +++---
 api/build.gradle                              |  11 +-
 .../java/me/tofaa/entitylib/APISettings.java  | 100 ++++++++
 .../java/me/tofaa/entitylib/EntityLib.java    |  28 +++
 .../java/me/tofaa/entitylib/EntityLibAPI.java |  43 ++++
 .../java/me/tofaa/entitylib/Platform.java     |  53 +++++
 .../java/me/tofaa/entitylib/WorldWrapper.java |  19 ++
 .../tofaa/entitylib/event/EntityLibEvent.java |  13 ++
 .../event/UserReceiveMetaUpdateEvent.java     |  29 +++
 .../extras/InvalidVersionException.java       |  23 ++
 .../me/tofaa/entitylib/meta/EntityMeta.java   | 221 ++++++++++++++++++
 .../me/tofaa/entitylib/meta/Metadata.java     |  45 ++++
 .../tofaa/entitylib/tick/TickContainer.java   |  72 ++++++
 .../me/tofaa/entitylib/tick/Tickable.java     |   7 +
 build.gradle                                  |   6 +
 platforms/spigot/build.gradle                 |  15 ++
 .../entitylib/spigot/SpigotEntityLibAPI.java  |  98 ++++++++
 .../spigot/SpigotEntityLibPlatform.java       |  65 ++++++
 settings.gradle                               |   2 +
 .../me/tofaa/entitylib/meta/Metadata.java     |   1 -
 20 files changed, 888 insertions(+), 34 deletions(-)
 create mode 100644 api/src/main/java/me/tofaa/entitylib/APISettings.java
 create mode 100644 api/src/main/java/me/tofaa/entitylib/EntityLib.java
 create mode 100644 api/src/main/java/me/tofaa/entitylib/EntityLibAPI.java
 create mode 100644 api/src/main/java/me/tofaa/entitylib/Platform.java
 create mode 100644 api/src/main/java/me/tofaa/entitylib/WorldWrapper.java
 create mode 100644 api/src/main/java/me/tofaa/entitylib/event/EntityLibEvent.java
 create mode 100644 api/src/main/java/me/tofaa/entitylib/event/UserReceiveMetaUpdateEvent.java
 create mode 100644 api/src/main/java/me/tofaa/entitylib/extras/InvalidVersionException.java
 create mode 100644 api/src/main/java/me/tofaa/entitylib/meta/EntityMeta.java
 create mode 100644 api/src/main/java/me/tofaa/entitylib/meta/Metadata.java
 create mode 100644 api/src/main/java/me/tofaa/entitylib/tick/TickContainer.java
 create mode 100644 api/src/main/java/me/tofaa/entitylib/tick/Tickable.java
 create mode 100644 platforms/spigot/build.gradle
 create mode 100644 platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibAPI.java
 create mode 100644 platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibPlatform.java

diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 0c3487f..cd26f29 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,11 +5,20 @@
   </component>
   <component name="ChangeListManager">
     <list default="true" id="9d5d9b6f-43c8-41a4-bb42-a66ffc96c9b0" name="Changes" comment="">
-      <change afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/EntityLibPlatform.java" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/WrapperWorld.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/.idea/gradle.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/gradle.xml" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/EntityLib.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/EntityLibAPI.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/Platform.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/WorldWrapper.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/event/EntityLibEvent.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/event/UserReceiveMetaUpdateEvent.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/extras/InvalidVersionException.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/meta/EntityMeta.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/meta/Metadata.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/api/src/main/java/me/tofaa/entitylib/tick/TickContainer.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/settings.gradle" beforeDir="false" afterPath="$PROJECT_DIR$/settings.gradle" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/api/build.gradle" beforeDir="false" afterPath="$PROJECT_DIR$/api/build.gradle" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/build.gradle" beforeDir="false" afterPath="$PROJECT_DIR$/build.gradle" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/meta/Metadata.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/meta/Metadata.java" afterDir="false" />
     </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -72,30 +81,34 @@
     <option name="showExcludedFiles" value="false" />
     <option name="showLibraryContents" value="true" />
   </component>
-  <component name="PropertiesComponent"><![CDATA[{
-  "keyToString": {
-    "Gradle.Build EntityLib.executor": "Run",
-    "Gradle.EntityLib:test-plugin [runServer].executor": "Run",
-    "RunOnceActivity.OpenProjectViewOnStart": "true",
-    "RunOnceActivity.ShowReadmeOnStart": "true",
-    "WebServerToolWindowFactoryState": "false",
-    "git-widget-placeholder": "feat/platform-api",
-    "ignore.virus.scanning.warn.message": "true",
-    "jdk.selected.JAVA_MODULE": "corretto-17",
-    "kotlin-language-version-configured": "true",
-    "last_opened_file_path": "D:/Github/EntityLib",
-    "node.js.detected.package.eslint": "true",
-    "node.js.detected.package.tslint": "true",
-    "node.js.selected.package.eslint": "(autodetect)",
-    "node.js.selected.package.tslint": "(autodetect)",
-    "nodejs_package_manager_path": "npm",
-    "project.structure.last.edited": "Modules",
-    "project.structure.proportion": "0.0",
-    "project.structure.side.proportion": "0.0",
-    "settings.editor.selected.configurable": "preferences.pluginManager",
-    "vue.rearranger.settings.migration": "true"
+  <component name="PropertiesComponent">{
+  &quot;keyToString&quot;: {
+    &quot;Downloaded.Files.Path.Enabled&quot;: &quot;false&quot;,
+    &quot;Gradle.Build EntityLib.executor&quot;: &quot;Run&quot;,
+    &quot;Gradle.EntityLib:test-plugin [runServer].executor&quot;: &quot;Run&quot;,
+    &quot;Repository.Attach.Annotations&quot;: &quot;false&quot;,
+    &quot;Repository.Attach.JavaDocs&quot;: &quot;false&quot;,
+    &quot;Repository.Attach.Sources&quot;: &quot;false&quot;,
+    &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
+    &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
+    &quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
+    &quot;git-widget-placeholder&quot;: &quot;feat/platform-api&quot;,
+    &quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
+    &quot;jdk.selected.JAVA_MODULE&quot;: &quot;corretto-17&quot;,
+    &quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
+    &quot;last_opened_file_path&quot;: &quot;D:/Github/EntityLib&quot;,
+    &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
+    &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
+    &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
+    &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
+    &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
+    &quot;project.structure.last.edited&quot;: &quot;Modules&quot;,
+    &quot;project.structure.proportion&quot;: &quot;0.0&quot;,
+    &quot;project.structure.side.proportion&quot;: &quot;0.0&quot;,
+    &quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
+    &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
   }
-}]]></component>
+}</component>
   <component name="RecentsManager">
     <key name="CopyFile.RECENT_KEYS">
       <recent name="D:\Github\EntityLib\test-plugin" />
@@ -216,6 +229,10 @@
       <workItem from="1705578156456" duration="78000" />
       <workItem from="1705636302508" duration="7111000" />
       <workItem from="1705951390204" duration="3936000" />
+      <workItem from="1705984769972" duration="76000" />
+      <workItem from="1706183895216" duration="664000" />
+      <workItem from="1706187926445" duration="4339000" />
+      <workItem from="1706248178234" duration="1973000" />
     </task>
     <servers />
   </component>
diff --git a/api/build.gradle b/api/build.gradle
index 18d60d6..3e5d186 100644
--- a/api/build.gradle
+++ b/api/build.gradle
@@ -1,5 +1,6 @@
 plugins {
     id 'java'
+    id 'java-library'
 }
 
 group = 'me.tofaa.entitylib'
@@ -10,10 +11,8 @@ repositories {
 }
 
 dependencies {
-    testImplementation platform('org.junit:junit-bom:5.9.1')
-    testImplementation 'org.junit.jupiter:junit-jupiter'
-}
+    api 'org.jetbrains:annotations:24.0.0'
 
-test {
-    useJUnitPlatform()
-}
\ No newline at end of file
+    compileOnlyApi(adventureDependencies)
+    compileOnlyApi 'com.github.retrooper.packetevents:api:2.2.0'
+}
diff --git a/api/src/main/java/me/tofaa/entitylib/APISettings.java b/api/src/main/java/me/tofaa/entitylib/APISettings.java
new file mode 100644
index 0000000..01b5d17
--- /dev/null
+++ b/api/src/main/java/me/tofaa/entitylib/APISettings.java
@@ -0,0 +1,100 @@
+package me.tofaa.entitylib;
+
+import com.github.retrooper.packetevents.PacketEventsAPI;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonParser;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+
+public final class APISettings {
+
+    private boolean debugMode = false;
+    private boolean checkForUpdates = false;
+    private boolean tickTickables = false;
+    private PacketEventsAPI<?> packetEvents;
+    private boolean platformLogger = false;
+
+    public APISettings(PacketEventsAPI<?> packetEvents) {
+        this.packetEvents = packetEvents;
+    }
+
+    public boolean requiresUpdate() throws IOException {
+        if (!checkForUpdates) return false;
+        String urlString = "https://api.github.com/repos/Tofaa2/EntityLib/releases/latest";
+        String version = EntityLib.getVersion();
+
+        URL url = new URL(urlString);
+        JsonParser json = new JsonParser();
+        InputStream stream = url.openStream();
+        JsonArray array = json.parse(new InputStreamReader(stream)).getAsJsonArray();
+        String latest = array.get(0).getAsJsonObject().get("tag_name").getAsString();
+
+        stream.close();
+        return !version.equalsIgnoreCase(latest);
+    }
+
+    public @NotNull APISettings usePlatformLogger() {
+        this.platformLogger = true;
+        return this;
+    }
+
+    public @NotNull APISettings usePlatformLogger(boolean platformLogger) {
+        this.platformLogger = platformLogger;
+        return this;
+    }
+
+    public @NotNull APISettings debugMode(boolean debugMode) {
+        this.debugMode = debugMode;
+        return this;
+    }
+
+    public @NotNull APISettings checkForUpdates() {
+        this.checkForUpdates = true;
+        return this;
+    }
+
+    public @NotNull APISettings tickTickables() {
+        this.tickTickables = true;
+        return this;
+    }
+
+    public @NotNull APISettings debugMode() {
+        this.debugMode = true;
+        return this;
+    }
+
+    public @NotNull APISettings checkForUpdates(boolean checkForUpdates) {
+        this.checkForUpdates = checkForUpdates;
+        return this;
+    }
+
+    public @NotNull APISettings tickTickables(boolean tickTickables) {
+        this.tickTickables = tickTickables;
+        return this;
+    }
+
+    public boolean isDebugMode() {
+        return debugMode;
+    }
+
+    public boolean shouldCheckForUpdate() {
+        return checkForUpdates;
+    }
+
+    public boolean shouldTickTickables() {
+        return tickTickables;
+    }
+
+    public PacketEventsAPI<?> getPacketEvents() {
+        return packetEvents;
+    }
+
+    public boolean shouldUsePlatformLogger() {
+        return platformLogger;
+    }
+
+}
diff --git a/api/src/main/java/me/tofaa/entitylib/EntityLib.java b/api/src/main/java/me/tofaa/entitylib/EntityLib.java
new file mode 100644
index 0000000..453b99e
--- /dev/null
+++ b/api/src/main/java/me/tofaa/entitylib/EntityLib.java
@@ -0,0 +1,28 @@
+package me.tofaa.entitylib;
+
+public final class EntityLib {
+
+    private EntityLib() {}
+
+    private static String version = "1.2.0-SNAPSHOT";
+    private static Platform platform;
+    private static EntityLibAPI api;
+
+    public static void init(Platform<?> platform, APISettings settings) {
+        EntityLib.platform = platform;
+        platform.setupApi(settings);
+        api = platform.getAPI();
+    }
+
+    public static EntityLibAPI<?, ?> getApi() {
+        return api;
+    }
+
+    public static Platform<?> getPlatform() {
+        return platform;
+    }
+
+    public static String getVersion() {
+        return version;
+    }
+}
diff --git a/api/src/main/java/me/tofaa/entitylib/EntityLibAPI.java b/api/src/main/java/me/tofaa/entitylib/EntityLibAPI.java
new file mode 100644
index 0000000..6355aba
--- /dev/null
+++ b/api/src/main/java/me/tofaa/entitylib/EntityLibAPI.java
@@ -0,0 +1,43 @@
+package me.tofaa.entitylib;
+
+import com.github.retrooper.packetevents.PacketEventsAPI;
+import me.tofaa.entitylib.tick.TickContainer;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+/**
+ * Represents the API for EntityLib.
+ * Handles the loading, enabling, and disabling of the API. And handles platform specific creation of EntityLib content.
+ * @param <W> The {@link WorldWrapper}'s param type for the platform specific World.
+ * @param <T> The {@link TickContainer}'s param type for the platform specific TickContainer.
+ */
+public interface EntityLibAPI<W, T> {
+
+    PacketEventsAPI<?> getPacketEvents();
+
+    void onLoad();
+
+    void onEnable();
+
+    @NotNull APISettings getSettings();
+
+
+    /**
+     * If a platform supports ticking
+     * this method should be responsible for setting up the {@link me.tofaa.entitylib.tick.TickContainer}'s.
+     */
+    void setupTickingContainers();
+
+
+    /**
+     * @return An unmodifiable collection of TickContainers.
+     */
+    @NotNull Collection<TickContainer<?, T>> getTickContainers();
+
+    /**
+     * Adds a TickContainer to the API. Automatically starts ticking it.
+     * @param tickContainer the TickContainer to add.
+     */
+    void addTickContainer(@NotNull TickContainer<?, T> tickContainer);
+}
diff --git a/api/src/main/java/me/tofaa/entitylib/Platform.java b/api/src/main/java/me/tofaa/entitylib/Platform.java
new file mode 100644
index 0000000..caa366a
--- /dev/null
+++ b/api/src/main/java/me/tofaa/entitylib/Platform.java
@@ -0,0 +1,53 @@
+package me.tofaa.entitylib;
+
+import me.tofaa.entitylib.event.EntityLibEvent;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.Consumer;
+import java.util.logging.Logger;
+
+/**
+ * A generic representation of a platform that EntityLib is running on.
+ * @param <P> The platform handle, for Spigot this would be a JavaPlugin. etc etc.
+ */
+public interface Platform<P> {
+
+
+    /**
+     * @return the logger EntityLib uses internally.
+     */
+    @NotNull Logger getLogger();
+
+    /**
+     * Sends an event to the platform. Platform implementations should handle the event accordingly.
+     * @param event the event to send.
+     */
+    void sendEvent(EntityLibEvent event);
+
+    /**
+     * Registers a listener for the given event class, the handle will be called when the event is sent.
+     * @param eventClass the event class to listen for.
+     * @param handle the handle to call when the event is sent.
+     * @param <T> the event type.
+     */
+    <T extends EntityLibEvent> void registerListener(Class<T> eventClass, Consumer<T> handle);
+
+    /**
+     * Sets up the API for the platform. This method should be called automatically by the platform. Don't call it yourself.
+     * @param settings
+     */
+    void setupApi(@NotNull APISettings settings);
+
+    /**
+     * @return The API instance.
+     */
+    EntityLibAPI<?, ?> getAPI();
+
+    /**
+     * @return the platforms name.
+     */
+    String getName();
+
+    @NotNull P getHandle();
+
+}
diff --git a/api/src/main/java/me/tofaa/entitylib/WorldWrapper.java b/api/src/main/java/me/tofaa/entitylib/WorldWrapper.java
new file mode 100644
index 0000000..1cfdb45
--- /dev/null
+++ b/api/src/main/java/me/tofaa/entitylib/WorldWrapper.java
@@ -0,0 +1,19 @@
+package me.tofaa.entitylib;
+
+import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
+import com.github.retrooper.packetevents.resources.ResourceLocation;
+
+/**
+ * Represents a platform specific world.
+ * These are not needed at all times, and should exclusively be used when an Entity needs to be
+ * aware of its surroundings.
+ * @param <W> The platform specific World type.
+ */
+public interface WorldWrapper<W> {
+
+    ResourceLocation getPacketEventsWorld();
+
+    WrappedBlockState getBlock(int x, int y, int z);
+
+    W getHandle();
+}
diff --git a/api/src/main/java/me/tofaa/entitylib/event/EntityLibEvent.java b/api/src/main/java/me/tofaa/entitylib/event/EntityLibEvent.java
new file mode 100644
index 0000000..8f3e66c
--- /dev/null
+++ b/api/src/main/java/me/tofaa/entitylib/event/EntityLibEvent.java
@@ -0,0 +1,13 @@
+package me.tofaa.entitylib.event;
+
+public interface EntityLibEvent {
+
+    boolean isCancelled();
+
+    void setCancelled(boolean cancelled);
+
+    default boolean isAsync() {
+        return false;
+    }
+
+}
diff --git a/api/src/main/java/me/tofaa/entitylib/event/UserReceiveMetaUpdateEvent.java b/api/src/main/java/me/tofaa/entitylib/event/UserReceiveMetaUpdateEvent.java
new file mode 100644
index 0000000..40df9cd
--- /dev/null
+++ b/api/src/main/java/me/tofaa/entitylib/event/UserReceiveMetaUpdateEvent.java
@@ -0,0 +1,29 @@
+package me.tofaa.entitylib.event;
+
+import com.github.retrooper.packetevents.protocol.player.User;
+import me.tofaa.entitylib.meta.EntityMeta;
+
+public final class UserReceiveMetaUpdateEvent implements EntityLibEvent {
+
+    private final User user;
+    private boolean cancelled;
+    private EntityMeta meta;
+
+    public UserReceiveMetaUpdateEvent(User user) {
+        this.user = user;
+    }
+
+    public User getUser() {
+        return user;
+    }
+
+    @Override
+    public boolean isCancelled() {
+        return false;
+    }
+
+    @Override
+    public void setCancelled(boolean cancelled) {
+
+    }
+}
diff --git a/api/src/main/java/me/tofaa/entitylib/extras/InvalidVersionException.java b/api/src/main/java/me/tofaa/entitylib/extras/InvalidVersionException.java
new file mode 100644
index 0000000..a6d0964
--- /dev/null
+++ b/api/src/main/java/me/tofaa/entitylib/extras/InvalidVersionException.java
@@ -0,0 +1,23 @@
+package me.tofaa.entitylib.extras;
+
+public class InvalidVersionException extends RuntimeException {
+
+    public InvalidVersionException() {
+    }
+
+    public InvalidVersionException(String message) {
+        super(message);
+    }
+
+    public InvalidVersionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public InvalidVersionException(Throwable cause) {
+        super(cause);
+    }
+
+    public InvalidVersionException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}
diff --git a/api/src/main/java/me/tofaa/entitylib/meta/EntityMeta.java b/api/src/main/java/me/tofaa/entitylib/meta/EntityMeta.java
new file mode 100644
index 0000000..bab0c11
--- /dev/null
+++ b/api/src/main/java/me/tofaa/entitylib/meta/EntityMeta.java
@@ -0,0 +1,221 @@
+package me.tofaa.entitylib.meta;
+
+import com.github.retrooper.packetevents.manager.server.ServerVersion;
+import com.github.retrooper.packetevents.manager.server.VersionComparison;
+import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
+import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
+import com.github.retrooper.packetevents.protocol.entity.data.EntityMetadataProvider;
+import com.github.retrooper.packetevents.protocol.entity.pose.EntityPose;
+import com.github.retrooper.packetevents.protocol.player.ClientVersion;
+import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityMetadata;
+import me.tofaa.entitylib.EntityLib;
+import me.tofaa.entitylib.extras.InvalidVersionException;
+import net.kyori.adventure.text.Component;
+
+import java.util.List;
+
+public class EntityMeta implements EntityMetadataProvider {
+
+    public static final byte OFFSET = 0;
+    public static final byte MAX_OFFSET = OFFSET + 8;
+
+    private final static byte ON_FIRE_BIT = 0x01;
+    private final static byte CROUCHING_BIT = 0x02;
+    private final static byte SPRINTING_BIT = 0x08;
+    private final static byte SWIMMING_BIT = 0x10;
+    private final static byte INVISIBLE_BIT = 0x20;
+    private final static byte HAS_GLOWING_EFFECT_BIT = 0x40;
+    private final static byte FLYING_WITH_ELYTRA_BIT = (byte) 0x80;
+
+    protected final int entityId;
+    protected final Metadata metadata;
+
+    public EntityMeta(int entityId, Metadata metadata) {
+        this.entityId = entityId;
+        this.metadata = metadata;
+    }
+
+    public boolean isOnFire() {
+        return getMaskBit(OFFSET, ON_FIRE_BIT);
+    }
+
+    public void setOnFire(boolean value) {
+        setMaskBit(OFFSET, ON_FIRE_BIT, value);
+    }
+
+    public boolean isSneaking() {
+        return getMaskBit(OFFSET, CROUCHING_BIT);
+    }
+
+    public void setSneaking(boolean value) {
+        setMaskBit(OFFSET, CROUCHING_BIT, value);
+    }
+
+    public boolean isSprinting() {
+        return getMaskBit(OFFSET, SPRINTING_BIT);
+    }
+
+    public void setSprinting(boolean value) {
+        setMaskBit(OFFSET, SPRINTING_BIT, value);
+    }
+
+    public boolean isInvisible() {
+        return getMaskBit(OFFSET, INVISIBLE_BIT);
+    }
+
+    public void setInvisible(boolean value) {
+        setMaskBit(OFFSET, INVISIBLE_BIT, value);
+    }
+
+    public short getAirTicks() {
+        return this.metadata.getIndex((byte) 1, (short) 300);
+    }
+
+    public void setAirTicks(short value) {
+        this.metadata.setIndex((byte) 1, EntityDataTypes.SHORT, value);
+    }
+
+    public Component getCustomName() {
+        return this.metadata.getIndex(offset(OFFSET, 2), null);
+    }
+
+    public void setCustomName(Component value) {
+        this.metadata.setIndex(offset(OFFSET, 2), EntityDataTypes.ADV_COMPONENT, value);
+    }
+
+    public boolean isCustomNameVisible() {
+        return this.metadata.getIndex(offset(OFFSET, 3), false);
+    }
+
+    public void setCustomNameVisible(boolean value) {
+        this.metadata.setIndex(offset(OFFSET, 3), EntityDataTypes.BOOLEAN, value);
+    }
+
+    public boolean hasGlowingEffect() {
+        return getMaskBit(OFFSET, HAS_GLOWING_EFFECT_BIT);
+    }
+
+    public boolean isSwimming() {
+        return getMaskBit(OFFSET, SWIMMING_BIT);
+    }
+
+    public void setSwimming(boolean value) {
+        setMaskBit(OFFSET, SWIMMING_BIT, value);
+    }
+
+    public void setHasGlowingEffect(boolean value) {
+        setMaskBit(OFFSET, HAS_GLOWING_EFFECT_BIT, value);
+    }
+
+    public boolean isFlyingWithElytra() {
+        return getMaskBit(OFFSET, FLYING_WITH_ELYTRA_BIT);
+    }
+
+    public void setFlyingWithElytra(boolean value) {
+        setMaskBit(OFFSET, FLYING_WITH_ELYTRA_BIT, value);
+    }
+
+    public boolean isSilent() {
+        return this.metadata.getIndex((byte) 4, false);
+    }
+
+    public void setSilent(boolean value) {
+        this.metadata.setIndex((byte) 4, EntityDataTypes.BOOLEAN, value);
+    }
+
+    public boolean isHasNoGravity() {
+        return this.metadata.getIndex(offset(OFFSET, 5), true);
+    }
+
+    public void setHasNoGravity(boolean value) {
+        this.metadata.setIndex(offset(OFFSET, 5), EntityDataTypes.BOOLEAN, value);
+    }
+
+    public EntityPose getPose() {
+        return this.metadata.getIndex(offset(OFFSET, 6), EntityPose.STANDING);
+    }
+
+    public void setPose(EntityPose value) {
+        this.metadata.setIndex(offset(OFFSET, 6), EntityDataTypes.ENTITY_POSE, value);
+    }
+
+    public int getTicksFrozenInPowderedSnow() {
+        return this.metadata.getIndex(offset(OFFSET, 7), 0);
+    }
+
+    public void setTicksFrozenInPowderedSnow(int value) {
+        this.metadata.setIndex(offset(OFFSET, 7), EntityDataTypes.INT, value);
+    }
+
+    public WrapperPlayServerEntityMetadata createPacket() {
+        return metadata.createPacket();
+    }
+
+    protected static void isVersionOlder(ServerVersion version) {
+        if (!EntityLib.getApi().getPacketEvents().getServerManager().getVersion().is(VersionComparison.OLDER_THAN, version)) {
+            throw new InvalidVersionException("This method is only available for versions older than " + version.name() + ".");
+        }
+    }
+
+    protected static void isVersionNewer(ServerVersion version) {
+        if (!EntityLib.getApi().getPacketEvents().getServerManager().getVersion().is(VersionComparison.NEWER_THAN, version)) {
+            throw new InvalidVersionException("This method is only available for versions newer than " + version.name() + ".");
+        }
+    }
+
+    protected static boolean isVersion(ServerVersion version, VersionComparison comparison) {
+        return EntityLib.getApi().getPacketEvents().getServerManager().getVersion().is(comparison, version);
+    }
+
+    protected static boolean isVersion(ServerVersion version) {
+        return EntityLib.getApi().getPacketEvents().getServerManager().getVersion().is(VersionComparison.EQUALS, version);
+    }
+
+    /**
+     * Annoying java 8 not letting me do OFFSET + amount in the method call so this is a workaround
+     * @param value the value to offset
+     * @param amount the amount to offset by
+     * @return the offset value
+     */
+    protected static byte offset(byte value, int amount) {
+        return (byte) (value + amount);
+    }
+
+    protected byte getMask(byte index) {
+        return this.metadata.getIndex(index, (byte) 0);
+    }
+
+    protected void setMask(byte index, byte mask) {
+        this.metadata.setIndex(index, EntityDataTypes.BYTE, mask);
+    }
+
+    protected boolean getMaskBit(byte index, byte bit) {
+        return (getMask(index) & bit) == bit;
+    }
+
+    protected void setMaskBit(int index, byte bit, boolean value) {
+        byte mask = getMask((byte)index);
+        boolean currentValue = (mask & bit) == bit;
+        if (currentValue == value) {
+            return;
+        }
+        if (value) {
+            mask |= bit;
+        } else {
+            mask &= (byte) ~bit;
+        }
+        setMask((byte)index, mask);
+    }
+
+    @Override
+    public List<EntityData> entityData(ClientVersion clientVersion) {
+        return metadata.getEntries(); // TODO: Atm this is useless cause of the way the api works. Might change in the future
+    }
+
+    @Override
+    public List<EntityData> entityData() {
+        return metadata.getEntries();
+    }
+
+
+}
diff --git a/api/src/main/java/me/tofaa/entitylib/meta/Metadata.java b/api/src/main/java/me/tofaa/entitylib/meta/Metadata.java
new file mode 100644
index 0000000..cc4c681
--- /dev/null
+++ b/api/src/main/java/me/tofaa/entitylib/meta/Metadata.java
@@ -0,0 +1,45 @@
+package me.tofaa.entitylib.meta;
+
+import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
+import com.github.retrooper.packetevents.protocol.entity.data.EntityDataType;
+import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityMetadata;
+import me.tofaa.entitylib.EntityLib;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@SuppressWarnings("unchecked")
+public class Metadata {
+
+    private final Map<Byte, EntityData> metadataMap = new ConcurrentHashMap<>();
+    private final int entityId;
+
+    public Metadata(int entityId) {
+        this.entityId = entityId;
+    }
+
+    public <T> T getIndex(byte index, @Nullable T defaultValue) {
+        EntityData entityData = metadataMap.get(index);
+        if (entityData == null) return defaultValue;
+        if (entityData.getValue() == null) return defaultValue;
+        return (T) entityData.getValue();
+    }
+
+    public <T> void setIndex(byte index, @NotNull EntityDataType<T> dataType, T value) {
+        EntityData data = new EntityData(index, dataType, value);
+        this.metadataMap.put(index, data);
+    }
+
+    @NotNull List<EntityData> getEntries() {
+        return new ArrayList<>(metadataMap.values());
+    }
+
+    public WrapperPlayServerEntityMetadata createPacket() {
+        return new WrapperPlayServerEntityMetadata(entityId, getEntries());
+    }
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/me/tofaa/entitylib/tick/TickContainer.java b/api/src/main/java/me/tofaa/entitylib/tick/TickContainer.java
new file mode 100644
index 0000000..f1b856f
--- /dev/null
+++ b/api/src/main/java/me/tofaa/entitylib/tick/TickContainer.java
@@ -0,0 +1,72 @@
+package me.tofaa.entitylib.tick;
+
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Represents a storage/container for {@link Tickable}s.
+ * @param <T> The type of {@link Tickable} to store.
+ * @param <H> If a platform enforces a specific method of ticking, this type represents the handler for that method.
+ */
+public abstract class TickContainer<T extends Tickable, H> {
+
+    private final Set<T> tickables = new HashSet<>();
+    private H handle;
+
+    /**
+     * @return The {@link Tickable}s stored in this container. This collection is immutable
+     */
+    public @NotNull Collection<T> getTickables() {
+        return Collections.unmodifiableCollection(tickables);
+    }
+
+    /**
+     * Adds a {@link Tickable} to this container.
+     * @param tickable The {@link Tickable} to add.
+     * @return {@code true} if the {@link Tickable} was added, {@code false} otherwise.
+     */
+    public boolean addTickable(@NotNull T tickable) {
+        return tickables.add(tickable);
+    }
+
+    /**
+     * Removes a {@link Tickable} from this container.
+     * @param tickable The {@link Tickable} to remove.
+     * @return {@code true} if the {@link Tickable} was removed, {@code false} otherwise.
+     */
+    public boolean removeTickable(T tickable) {
+        return tickables.remove(tickable);
+    }
+
+    /**
+     * Ticks all {@link Tickable}s in this container, this method can be overriden to provide a custom implementation.
+     * @param time The current time in milliseconds, incase the {@link Tickable} needs to know the current time.
+     */
+    public void tick(long time) {
+        for (T tickable : tickables) {
+            tickable.tick(time);
+        }
+    }
+
+    /**
+     * @return The handler for this container.
+     */
+    public @NotNull H getHandle() {
+        return handle;
+    }
+
+    /**
+     * Sets the handler for this container. This method is only used internally.
+     * @param handle The handler to set.
+     */
+    @ApiStatus.Internal
+    public void setHandle(@NotNull H handle) {
+        this.handle = handle;
+    }
+
+}
diff --git a/api/src/main/java/me/tofaa/entitylib/tick/Tickable.java b/api/src/main/java/me/tofaa/entitylib/tick/Tickable.java
new file mode 100644
index 0000000..11dd41b
--- /dev/null
+++ b/api/src/main/java/me/tofaa/entitylib/tick/Tickable.java
@@ -0,0 +1,7 @@
+package me.tofaa.entitylib.tick;
+
+public interface Tickable {
+
+    void tick(long time);
+
+}
diff --git a/build.gradle b/build.gradle
index 1958f2c..555b4ab 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,6 +5,12 @@ plugins {
 
 allprojects {
 
+    project.ext.adventureVersion = '4.15.0'
+    project.ext.adventureDependencies=["net.kyori:adventure-api:${adventureVersion}",
+                                       "net.kyori:adventure-text-serializer-gson:${adventureVersion}",
+                                       "net.kyori:adventure-text-serializer-legacy:${adventureVersion}",
+                                       "net.kyori:adventure-nbt:${adventureVersion}"]
+
     apply plugin: 'java'
 
     group = 'me.tofaa.entitylib'
diff --git a/platforms/spigot/build.gradle b/platforms/spigot/build.gradle
new file mode 100644
index 0000000..90169d6
--- /dev/null
+++ b/platforms/spigot/build.gradle
@@ -0,0 +1,15 @@
+plugins {
+    id 'java'
+}
+
+group = 'me.tofaa.entitylib'
+version = '1.0-SNAPSHOT'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation(project(":api"))
+    implementation('org.spigotmc:spigot-api:1.20.1-R0.1-SNAPSHOT')
+}
diff --git a/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibAPI.java b/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibAPI.java
new file mode 100644
index 0000000..5f2222e
--- /dev/null
+++ b/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibAPI.java
@@ -0,0 +1,98 @@
+package me.tofaa.entitylib.spigot;
+
+import com.github.retrooper.packetevents.PacketEventsAPI;
+import me.tofaa.entitylib.APISettings;
+import me.tofaa.entitylib.EntityLibAPI;
+import me.tofaa.entitylib.tick.TickContainer;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.scheduler.BukkitTask;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.logging.Level;
+
+public class SpigotEntityLibAPI implements EntityLibAPI<World, BukkitTask> {
+
+    private final SpigotEntityLibPlatform platform;
+    private final PacketEventsAPI<?> packetEvents;
+    private final APISettings settings;
+    private Collection<TickContainer<?, BukkitTask>> tickContainers;
+
+    SpigotEntityLibAPI(SpigotEntityLibPlatform platform, APISettings settings) {
+        this.platform = platform;
+        this.packetEvents = settings.getPacketEvents();
+        this.settings = settings;
+    }
+
+    @Override
+    public void onLoad() {
+        this.tickContainers = settings.shouldTickTickables() ? new HashSet<>() : Collections.EMPTY_SET;
+    }
+
+    @Override
+    public void onEnable() {
+
+    }
+
+    @NotNull @Override
+    public APISettings getSettings() {
+        return settings;
+    }
+
+    @Override
+    public void setupTickingContainers() {
+        if (!getSettings().shouldTickTickables()) {
+            if (getSettings().isDebugMode()) {
+                platform.getLogger().log(Level.CONFIG, "Skipping ticking containers as it is disabled in the settings.");
+            }
+            return;
+        }
+
+        if (getSettings().isDebugMode()) {
+            platform.getLogger().log(Level.CONFIG, "Setting up ticking containers...");
+        }
+        if (tickContainers.isEmpty()) {
+            if (getSettings().isDebugMode()) {
+                platform.getLogger().log(Level.CONFIG, "No tick containers found.");
+            }
+            return;
+        }
+
+        if (getSettings().isDebugMode()) {
+            platform.getLogger().log(Level.CONFIG, "Found " + tickContainers.size() + " tick containers.");
+        }
+
+        tickContainers.forEach(this::registerNewTickContainer);
+    }
+
+    @Override
+    public PacketEventsAPI<?> getPacketEvents() {
+        return packetEvents;
+    }
+
+    @Override
+    public Collection<TickContainer<?, BukkitTask>> getTickContainers() {
+        return tickContainers;
+    }
+
+    @Override
+    public void addTickContainer(@NotNull TickContainer<?, BukkitTask> tickContainer) {
+        tickContainers.add(tickContainer);
+        registerNewTickContainer(tickContainer);
+    }
+
+    public void registerNewTickContainer(TickContainer<?, BukkitTask> tickContainer) {
+
+        if (getSettings().isDebugMode()) {
+            platform.getLogger().log(Level.CONFIG, "Registering new tick container...");
+        }
+        getTickContainers().add(tickContainer);
+        BukkitTask task = Bukkit.getScheduler().runTaskTimerAsynchronously(platform.getHandle(), () -> tickContainer.tick(System.currentTimeMillis()), 1L, 1L);
+        tickContainer.setHandle(task);
+    }
+
+
+}
diff --git a/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibPlatform.java b/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibPlatform.java
new file mode 100644
index 0000000..c719df0
--- /dev/null
+++ b/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibPlatform.java
@@ -0,0 +1,65 @@
+package me.tofaa.entitylib.spigot;
+
+import me.tofaa.entitylib.APISettings;
+import me.tofaa.entitylib.EntityLibAPI;
+import me.tofaa.entitylib.Platform;
+import me.tofaa.entitylib.event.EntityLibEvent;
+import me.tofaa.entitylib.tick.TickContainer;
+import org.bukkit.Bukkit;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.scheduler.BukkitTask;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.function.Consumer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class SpigotEntityLibPlatform implements Platform<JavaPlugin> {
+
+    private final JavaPlugin plugin;
+    private SpigotEntityLibAPI api;
+    private Logger logger;
+
+    public SpigotEntityLibPlatform(@NotNull JavaPlugin plugin) {
+        this.plugin = plugin;
+    }
+
+    @Override
+    public void setupApi(@NotNull APISettings settings) {
+        this.logger = settings.shouldUsePlatformLogger() ? plugin.getLogger() : Logger.getLogger("EntityLib");
+        this.api = new SpigotEntityLibAPI(this, settings);
+        this.api.onLoad();
+        this.api.onEnable();
+    }
+
+    @Override
+    public @NotNull Logger getLogger() {
+        return logger;
+    }
+
+    @Override
+    public void sendEvent(EntityLibEvent event) {
+
+    }
+
+    @Override
+    public <T extends EntityLibEvent> void registerListener(Class<T> eventClass, Consumer<T> handle) {
+
+    }
+
+    @Override
+    public EntityLibAPI getAPI() {
+        return api;
+    }
+
+    @Override
+    public @NotNull JavaPlugin getHandle() {
+        return plugin;
+    }
+
+    @Override
+    public String getName() {
+        return "Spigot";
+    }
+}
diff --git a/settings.gradle b/settings.gradle
index 81e87e9..5426790 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,4 +1,6 @@
 rootProject.name = 'EntityLib'
 include 'test-plugin'
 include 'api'
+include 'platforms:spigot'
+findProject(':platforms:spigot')?.name = 'spigot'
 
diff --git a/src/main/java/me/tofaa/entitylib/meta/Metadata.java b/src/main/java/me/tofaa/entitylib/meta/Metadata.java
index 8dd0d57..bcb0f7f 100644
--- a/src/main/java/me/tofaa/entitylib/meta/Metadata.java
+++ b/src/main/java/me/tofaa/entitylib/meta/Metadata.java
@@ -9,7 +9,6 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;