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 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 viewers = new HashSet<>(); + private final String worldName; + private PacketEntity entity; + private PacketLocation location; + private NPCType type; + + private final Map 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> 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> 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> buildFactoryMap() { - + HashMap> 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); + } }