diff --git a/src/main/java/io/github/znetworkw/znpcservers/reflection/Reflections.java b/src/main/java/io/github/znetworkw/znpcservers/reflection/Reflections.java
index 1615ab1..1ea090f 100644
--- a/src/main/java/io/github/znetworkw/znpcservers/reflection/Reflections.java
+++ b/src/main/java/io/github/znetworkw/znpcservers/reflection/Reflections.java
@@ -667,5 +667,8 @@ public final class Reflections {
     public static final ReflectionLazyLoader<AtomicInteger> ATOMIC_ENTITY_ID_FIELD = new FieldReflection(new ReflectionBuilder(ReflectionPackage.ENTITY)
             .withClassName(ENTITY_CLASS)
             .withFieldName("entityCount")
+            .withFieldName("d")
+            .withFieldName("c")
+            .withExpectResult(AtomicInteger.class)
             .setStrict(Utils.versionNewer(14))).staticValueLoader(AtomicInteger.class);
 }
diff --git a/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java b/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java
index 09354c4..93d9dcb 100644
--- a/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java
+++ b/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java
@@ -6,14 +6,18 @@ import lol.pyr.znpcsplus.packets.PacketFactory;
 import org.bukkit.entity.Player;
 
 import java.util.Set;
+import java.util.UUID;
 
 public class PacketEntity {
     private final int entityId;
+    private final UUID uuid;
+
     private final EntityType type;
     private PacketLocation location;
 
     public PacketEntity(EntityType type, PacketLocation location) {
         this.entityId = EntityIDProvider.reserve();
+        this.uuid = UUID.randomUUID();
         this.type = type;
         this.location = location;
     }
@@ -26,6 +30,10 @@ public class PacketEntity {
         return location;
     }
 
+    public UUID getUuid() {
+        return uuid;
+    }
+
     public EntityType getType() {
         return type;
     }
diff --git a/src/main/java/lol/pyr/znpcsplus/npc/NPC.java b/src/main/java/lol/pyr/znpcsplus/npc/NPC.java
index d619ef8..9283a5e 100644
--- a/src/main/java/lol/pyr/znpcsplus/npc/NPC.java
+++ b/src/main/java/lol/pyr/znpcsplus/npc/NPC.java
@@ -1,4 +1,100 @@
 package lol.pyr.znpcsplus.npc;
 
+import lol.pyr.znpcsplus.entity.PacketEntity;
+import lol.pyr.znpcsplus.entity.PacketLocation;
+import lol.pyr.znpcsplus.properties.NPCProperty;
+import lol.pyr.znpcsplus.properties.NPCPropertyKey;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
 public class NPC {
+    private final Set<Player> viewers = new HashSet<>();
+    private final String worldName;
+    private PacketEntity entity;
+    private PacketLocation location;
+    private NPCType type;
+
+    private final Map<NPCPropertyKey, NPCProperty> propertyMap = new HashMap<>();
+
+    public NPC(World world, NPCType type, PacketLocation location) {
+        this.worldName = world.getName();
+        this.type = type;
+        this.location = location;
+        entity = new PacketEntity(type.getType(), location); // TODO: Entity ID Provider
+    }
+
+    public void setType(NPCType type) {
+        _hideAll();
+        this.type = type;
+        entity = new PacketEntity(type.getType(), entity.getLocation());
+        _showAll();
+    }
+
+    public NPCType getType() {
+        return type;
+    }
+
+    public PacketLocation getLocation() {
+        return location;
+    }
+
+    public void setLocation(PacketLocation location) {
+        this.location = location;
+        entity.setLocation(location, viewers);
+    }
+
+    public World getWorld() {
+        return Bukkit.getWorld(worldName);
+    }
+
+    public void delete() {
+        _hideAll();
+        viewers.clear();
+    }
+
+    public void show(Player player) {
+        if (viewers.contains(player)) return;
+        _show(player);
+        viewers.add(player);
+    }
+
+    private void _show(Player player) {
+        entity.spawn(player);
+    }
+
+    public void hide(Player player) {
+        if (!viewers.contains(player)) return;
+        _hide(player);
+        viewers.remove(player);
+    }
+
+    private void _hide(Player player) {
+        entity.despawn(player);
+    }
+
+    private void _hideAll() {
+        for (Player viewer : viewers) _hide(viewer);
+    }
+
+    private void _showAll() {
+        for (Player viewer : viewers) _show(viewer);
+    }
+
+    public boolean isShown(Player player) {
+        return viewers.contains(player);
+    }
+
+    public NPCProperty getProperty(NPCPropertyKey key) {
+        return propertyMap.get(key);
+    }
+
+    public boolean hasProperty(NPCPropertyKey key) {
+        return propertyMap.containsKey(key);
+    }
 }
diff --git a/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java b/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java
index ce5f394..3baf573 100644
--- a/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java
+++ b/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java
@@ -2,20 +2,43 @@ package lol.pyr.znpcsplus.packets;
 
 import com.github.retrooper.packetevents.PacketEvents;
 import com.github.retrooper.packetevents.manager.server.ServerVersion;
+import lol.pyr.znpcsplus.entity.PacketEntity;
 import lol.pyr.znpcsplus.util.LazyLoader;
+import org.bukkit.entity.Player;
 
+import java.util.HashMap;
 import java.util.Map;
 
 public interface PacketFactory {
+    void spawnPlayer(Player player, PacketEntity entity);
+    void spawnEntity(Player player, PacketEntity entity);
+    void destroyEntity(Player player, PacketEntity entity);
+    void teleportEntity(Player player, PacketEntity entity);
+    void addTabPlayer(Player player, PacketEntity entity);
+    void removeTabPlayer(Player player, PacketEntity entity);
+    void createTeam(Player player, PacketEntity entity);
+    void removeTeam(Player player, PacketEntity entity);
 
-    Map<ServerVersion, LazyLoader<? extends PacketFactory>> factories = buildFactoryMap();
-
+    PacketFactory factory = get();
 
     static PacketFactory get() {
-        return factories.get(PacketEvents.getAPI().getServerManager().getVersion()).get();
+        if (factory != null) return factory;
+        ServerVersion version = PacketEvents.getAPI().getServerManager().getVersion();
+        Map<ServerVersion, LazyLoader<? extends PacketFactory>> factories = buildFactoryMap();
+        if (factories.containsKey(version)) return factories.get(version).get();
+        for (ServerVersion v : ServerVersion.reversedValues()) {
+            if (v.isNewerThan(version)) continue;
+            if (!factories.containsKey(v)) continue;
+            return factories.get(v).get();
+        }
+        throw new RuntimeException("Unsupported version!");
     }
 
     private static Map<ServerVersion, LazyLoader<? extends PacketFactory>> buildFactoryMap() {
-
+        HashMap<ServerVersion, LazyLoader<? extends PacketFactory>> map = new HashMap<>();
+        map.put(ServerVersion.V_1_8, LazyLoader.of(V1_8Factory::new));
+        map.put(ServerVersion.V_1_14, LazyLoader.of(V1_14Factory::new));
+        map.put(ServerVersion.V_1_19, LazyLoader.of(V1_19Factory::new));
+        return map;
     }
 }
diff --git a/src/main/java/lol/pyr/znpcsplus/packets/V1_8Factory.java b/src/main/java/lol/pyr/znpcsplus/packets/V1_8Factory.java
index 6db778c..26e7d4b 100644
--- a/src/main/java/lol/pyr/znpcsplus/packets/V1_8Factory.java
+++ b/src/main/java/lol/pyr/znpcsplus/packets/V1_8Factory.java
@@ -1,4 +1,96 @@
 package lol.pyr.znpcsplus.packets;
 
+import com.github.retrooper.packetevents.PacketEvents;
+import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
+import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
+import com.github.retrooper.packetevents.protocol.player.ClientVersion;
+import com.github.retrooper.packetevents.protocol.player.GameMode;
+import com.github.retrooper.packetevents.protocol.player.UserProfile;
+import com.github.retrooper.packetevents.util.Vector3d;
+import com.github.retrooper.packetevents.wrapper.PacketWrapper;
+import com.github.retrooper.packetevents.wrapper.play.server.*;
+import lol.pyr.znpcsplus.ZNPCsPlus;
+import lol.pyr.znpcsplus.entity.PacketEntity;
+import lol.pyr.znpcsplus.entity.PacketLocation;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+import java.util.Optional;
+
 public class V1_8Factory implements PacketFactory {
+    @Override
+    public void spawnPlayer(Player player, PacketEntity entity) {
+        addTabPlayer(player, entity);
+        createTeam(player, entity);
+        PacketLocation location = entity.getLocation();
+        sendPacket(player, new WrapperPlayServerSpawnPlayer(entity.getEntityId(),
+                entity.getUuid(), location.toVector3d(), location.getYaw(), location.getPitch(), List.of()));
+        ZNPCsPlus.SCHEDULER.scheduleSyncDelayedTask(() -> removeTabPlayer(player, entity), 60);
+    }
+
+    @Override
+    public void spawnEntity(Player player, PacketEntity entity) {
+        PacketLocation location = entity.getLocation();
+        EntityType type = entity.getType();
+        ClientVersion clientVersion = PacketEvents.getAPI().getServerManager().getVersion().toClientVersion();
+        sendPacket(player, type.getLegacyId(clientVersion) == -1 ?
+                new WrapperPlayServerSpawnLivingEntity(entity.getEntityId(), entity.getUuid(), type, location.toVector3d(),
+                        location.getYaw(), location.getPitch(), location.getPitch(), new Vector3d(), List.of()) :
+                new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(), location.toVector3d(),
+                        location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.empty()));
+    }
+
+    @Override
+    public void destroyEntity(Player player, PacketEntity entity) {
+        sendPacket(player, new WrapperPlayServerDestroyEntities(entity.getEntityId()));
+        if (entity.getType() == EntityTypes.PLAYER) removeTeam(player, entity);
+    }
+
+    @Override
+    public void teleportEntity(Player player, PacketEntity entity) {
+        PacketLocation location = entity.getLocation();
+        sendPacket(player, new WrapperPlayServerEntityTeleport(entity.getEntityId(),
+                location.toVector3d(), location.getYaw(), location.getPitch(), true));
+    }
+
+    @Override
+    public void addTabPlayer(Player player, PacketEntity entity) {
+        if (entity.getType() != EntityTypes.PLAYER) return;
+        sendPacket(player, new WrapperPlayServerPlayerInfo(
+                WrapperPlayServerPlayerInfo.Action.ADD_PLAYER, new WrapperPlayServerPlayerInfo.PlayerData(Component.text(""),
+                new UserProfile(entity.getUuid(), Integer.toString(entity.getEntityId())), GameMode.CREATIVE, 1)));
+    }
+
+    @Override
+    public void removeTabPlayer(Player player, PacketEntity entity) {
+        if (entity.getType() != EntityTypes.PLAYER) return;
+        sendPacket(player, new WrapperPlayServerPlayerInfo(
+                WrapperPlayServerPlayerInfo.Action.REMOVE_PLAYER, new WrapperPlayServerPlayerInfo.PlayerData(null,
+                new UserProfile(entity.getUuid(), null), null, -1)));
+    }
+
+    @Override
+    public void createTeam(Player player, PacketEntity entity) {
+        sendPacket(player, new WrapperPlayServerTeams("npc_team_" + entity.getEntityId(), WrapperPlayServerTeams.TeamMode.CREATE, new WrapperPlayServerTeams.ScoreBoardTeamInfo(
+                Component.empty(),
+                Component.empty(),
+                Component.empty(),
+                WrapperPlayServerTeams.NameTagVisibility.NEVER,
+                WrapperPlayServerTeams.CollisionRule.NEVER,
+                NamedTextColor.WHITE,
+                WrapperPlayServerTeams.OptionData.NONE
+        )));
+        sendPacket(player, new WrapperPlayServerTeams("npc_team_" + entity.getEntityId(), WrapperPlayServerTeams.TeamMode.ADD_ENTITIES, (WrapperPlayServerTeams.ScoreBoardTeamInfo) null, Integer.toString(entity.getEntityId())));
+    }
+
+    @Override
+    public void removeTeam(Player player, PacketEntity entity) {
+        sendPacket(player, new WrapperPlayServerTeams("npc_team_" + entity.getEntityId(), WrapperPlayServerTeams.TeamMode.REMOVE, (WrapperPlayServerTeams.ScoreBoardTeamInfo) null));
+    }
+
+    protected void sendPacket(Player player, PacketWrapper<?> packet) {
+        PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet);
+    }
 }