Compare commits
	
		
			No commits in common. "1c927c6b3295b5784b88c792e7534fa0bacd614b" and "f74f5c29e19ac9774ec9a9bcda2cfc22ab14aee6" have entirely different histories.
		
	
	
		
			1c927c6b32
			...
			f74f5c29e1
		
	
		
					 86 changed files with 382 additions and 1577 deletions
				
			
		| 
						 | 
				
			
			@ -12,14 +12,14 @@ Looking for up-to-date builds of the plugin? Check out our [Jenkins](https://ci.
 | 
			
		|||
## Why is it so good?
 | 
			
		||||
- 100% Packet Based - Nothing is ran on the main thread
 | 
			
		||||
- Performance & stability oriented code
 | 
			
		||||
- Support for all versions from 1.8 to 1.21.8
 | 
			
		||||
- Support for all versions from 1.8 to 1.20.4
 | 
			
		||||
- Support for multiple different storage options
 | 
			
		||||
- Intuitive command system
 | 
			
		||||
 | 
			
		||||
### Requirements, Extensions & Supported Software
 | 
			
		||||
Requirements:
 | 
			
		||||
- Java 8+
 | 
			
		||||
- Minecraft 1.8 - 1.21.8
 | 
			
		||||
- Minecraft 1.8 - 1.21
 | 
			
		||||
 | 
			
		||||
Supported Softwares:
 | 
			
		||||
- Spigot ([Website](https://www.spigotmc.org/))
 | 
			
		||||
| 
						 | 
				
			
			@ -40,7 +40,7 @@ Open an issue in the GitHub [issue tracker](https://github.com/Pyrbu/ZNPCsPlus/i
 | 
			
		|||
 | 
			
		||||
## Credits
 | 
			
		||||
- [PacketEvents 2.0](https://github.com/retrooper/packetevents) - Packet library
 | 
			
		||||
- [Minecraft Wiki Protocol (formally wiki.vg)](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Main_Page) - Minecraft protocol documentation
 | 
			
		||||
- [wiki.vg](https://wiki.vg/Main_Page) - Minecraft protocol documentation
 | 
			
		||||
- [gson](https://github.com/google/gson) - JSON parsing library made by Google
 | 
			
		||||
- [Mineskin.org](https://mineskin.org/) - Website for raw skin file uploads
 | 
			
		||||
- [adventure](https://docs.advntr.dev/) - Minecraft text api
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,6 @@ import lol.pyr.znpcsplus.api.interaction.ActionFactory;
 | 
			
		|||
import lol.pyr.znpcsplus.api.interaction.ActionRegistry;
 | 
			
		||||
import lol.pyr.znpcsplus.api.npc.NpcRegistry;
 | 
			
		||||
import lol.pyr.znpcsplus.api.npc.NpcTypeRegistry;
 | 
			
		||||
import lol.pyr.znpcsplus.api.serialization.NpcSerializerRegistry;
 | 
			
		||||
import lol.pyr.znpcsplus.api.skin.SkinDescriptorFactory;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -47,10 +46,4 @@ public interface NpcApi {
 | 
			
		|||
     * @return the skin descriptor factory
 | 
			
		||||
     */
 | 
			
		||||
    SkinDescriptorFactory getSkinDescriptorFactory();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the npc serializer registry.
 | 
			
		||||
     * @return the npc serializer registry
 | 
			
		||||
     */
 | 
			
		||||
    NpcSerializerRegistry getNpcSerializerRegistry();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,8 +2,6 @@ package lol.pyr.znpcsplus.api;
 | 
			
		|||
 | 
			
		||||
import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.plugin.Plugin;
 | 
			
		||||
import org.bukkit.plugin.ServicePriority;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provider for the registered entity property registry instance
 | 
			
		||||
| 
						 | 
				
			
			@ -32,12 +30,10 @@ public class NpcPropertyRegistryProvider {
 | 
			
		|||
     * Internal method used to register the main instance of the plugin as the entity property registry provider
 | 
			
		||||
     * You probably shouldn't call this method under any circumstances
 | 
			
		||||
     *
 | 
			
		||||
     * @param plugin Instance of the ZNPCsPlus plugin
 | 
			
		||||
     * @param api Instance of the ZNPCsPlus entity property registry
 | 
			
		||||
     */
 | 
			
		||||
    public static void register(Plugin plugin, EntityPropertyRegistry api) {
 | 
			
		||||
    public static void register(EntityPropertyRegistry api) {
 | 
			
		||||
        NpcPropertyRegistryProvider.registry = api;
 | 
			
		||||
        Bukkit.getServicesManager().register(EntityPropertyRegistry.class, registry, plugin, ServicePriority.Normal);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,25 +31,11 @@ public interface EntityPropertyRegistry {
 | 
			
		|||
     */
 | 
			
		||||
    <T> EntityProperty<T> getByName(String name, Class<T> type);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a dummy property that can be used to store unique information per npc<br>
 | 
			
		||||
     * Note: Properties registered this way will be player-modifiable by default
 | 
			
		||||
     *
 | 
			
		||||
     * @param name The name of the new property
 | 
			
		||||
     * @param type The type of the new property
 | 
			
		||||
     * @deprecated Use {@link #registerDummy(String, Class, boolean)} instead
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    default void registerDummy(String name, Class<?> type) {
 | 
			
		||||
        registerDummy(name, type, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a dummy property that can be used to store unique information per npc
 | 
			
		||||
     *
 | 
			
		||||
     * @param name The name of the new property
 | 
			
		||||
     * @param type The type of the new property
 | 
			
		||||
     * @param playerModifiable Whether this property can be modified by players using commands
 | 
			
		||||
     */
 | 
			
		||||
    void registerDummy(String name, Class<?> type, boolean playerModifiable);
 | 
			
		||||
    void registerDummy(String name, Class<?> type);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,12 +6,10 @@ import lol.pyr.znpcsplus.api.interaction.InteractionAction;
 | 
			
		|||
import lol.pyr.znpcsplus.util.NpcLocation;
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base class for all NPCs
 | 
			
		||||
| 
						 | 
				
			
			@ -137,16 +135,14 @@ public interface Npc extends PropertyHolder {
 | 
			
		|||
    /**
 | 
			
		||||
     * Shows this NPC to a player
 | 
			
		||||
     * @param player The {@link Player} to show to
 | 
			
		||||
     * @return A future that completes when the npc is fully shown to the player
 | 
			
		||||
     */
 | 
			
		||||
    CompletableFuture<Void> show(Player player);
 | 
			
		||||
    void show(Player player);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Respawns this NPC for a player
 | 
			
		||||
     * @param player The {@link Player} to respawn for
 | 
			
		||||
     * @return A future that completes when the npc is fully respawned
 | 
			
		||||
     */
 | 
			
		||||
    CompletableFuture<Void> respawn(Player player);
 | 
			
		||||
    void respawn(Player player);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the head rotation of this NPC for a player
 | 
			
		||||
| 
						 | 
				
			
			@ -178,35 +174,4 @@ public interface Npc extends PropertyHolder {
 | 
			
		|||
     * @param offHand Should the hand be the offhand
 | 
			
		||||
     */
 | 
			
		||||
    void swingHand(boolean offHand);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the passengers of this npc
 | 
			
		||||
     * @return The list of entity ids of the passengers
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    @Nullable List<Integer> getPassengers();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a passenger to this npc
 | 
			
		||||
     * @param entityId The entity id of the passenger to add
 | 
			
		||||
     */
 | 
			
		||||
    void addPassenger(int entityId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes a passenger from this npc
 | 
			
		||||
     * @param entityId The entity id of the passenger to remove
 | 
			
		||||
     */
 | 
			
		||||
    void removePassenger(int entityId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the vehicle entity id of this npc
 | 
			
		||||
     * @return The entity id of the vehicle
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable Integer getVehicleId();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the vehicle id of this npc
 | 
			
		||||
     * @param vehicleId The entity id of the vehicle
 | 
			
		||||
     */
 | 
			
		||||
    void setVehicleId(Integer vehicleId);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -64,27 +64,4 @@ public interface NpcRegistry {
 | 
			
		|||
     * @param id The ID of the NPC entry
 | 
			
		||||
     */
 | 
			
		||||
    void delete(String id);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deletes an NPC entry by its UUID
 | 
			
		||||
     * @param uuid The UUID of the NPC entry
 | 
			
		||||
     */
 | 
			
		||||
    void delete(UUID uuid);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register an NPC to this registry
 | 
			
		||||
     * NpcEntry instances can be obtained through the NpcSerializer classes
 | 
			
		||||
     * @param entry The npc to be registered
 | 
			
		||||
     */
 | 
			
		||||
    void register(NpcEntry entry);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reload all saveable npcs from storage
 | 
			
		||||
     */
 | 
			
		||||
    void reload();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save all saveable npcs to storage
 | 
			
		||||
     */
 | 
			
		||||
    void save();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,20 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.api.serialization;
 | 
			
		||||
 | 
			
		||||
import lol.pyr.znpcsplus.api.npc.NpcEntry;
 | 
			
		||||
 | 
			
		||||
public interface NpcSerializer<T> {
 | 
			
		||||
    /**
 | 
			
		||||
     * Serialize an npc into the type of this serializer
 | 
			
		||||
     * @param entry The npc entry
 | 
			
		||||
     * @return The serialized class
 | 
			
		||||
     */
 | 
			
		||||
    T serialize(NpcEntry entry);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deserialize an npc from a serialized class
 | 
			
		||||
     * Note: This npc will not be registered, you need to also register it using the NpcRegistry#register(NpcEntry) method
 | 
			
		||||
     * @param model The serialized class
 | 
			
		||||
     * @return The deserialized NpcEntry
 | 
			
		||||
     */
 | 
			
		||||
    NpcEntry deserialize(T model);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,19 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.api.serialization;
 | 
			
		||||
 | 
			
		||||
public interface NpcSerializerRegistry {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get an NpcSerializer that serializes npcs into the provided class
 | 
			
		||||
     * @param clazz The class to serialize into
 | 
			
		||||
     * @return The npc serializer instance
 | 
			
		||||
     * @param <T> The type of the class that the serializer serializes into
 | 
			
		||||
     */
 | 
			
		||||
    <T> NpcSerializer<T> getSerializer(Class<T> clazz);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register an NpcSerializer to be used by other plugins
 | 
			
		||||
     * @param clazz The class that the serializer serializes into
 | 
			
		||||
     * @param serializer The serializer itself
 | 
			
		||||
     * @param <T> The type of the class that the serializer serializes into
 | 
			
		||||
     */
 | 
			
		||||
    <T> void registerSerializer(Class<T> clazz, NpcSerializer<T> serializer);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
package lol.pyr.znpcsplus.api.skin;
 | 
			
		||||
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Factory for creating skin descriptors.
 | 
			
		||||
| 
						 | 
				
			
			@ -9,10 +8,8 @@ import java.util.UUID;
 | 
			
		|||
public interface SkinDescriptorFactory {
 | 
			
		||||
    SkinDescriptor createMirrorDescriptor();
 | 
			
		||||
    SkinDescriptor createRefreshingDescriptor(String playerName);
 | 
			
		||||
    SkinDescriptor createRefreshingDescriptor(UUID playerUUID);
 | 
			
		||||
    SkinDescriptor createStaticDescriptor(String playerName);
 | 
			
		||||
    SkinDescriptor createStaticDescriptor(String texture, String signature);
 | 
			
		||||
    SkinDescriptor createUrlDescriptor(String url, String variant);
 | 
			
		||||
    SkinDescriptor createUrlDescriptor(URL url, String variant);
 | 
			
		||||
    SkinDescriptor createFileDescriptor(String path);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.util;
 | 
			
		||||
 | 
			
		||||
public enum SkeletonType {
 | 
			
		||||
    NORMAL,
 | 
			
		||||
    WITHER,
 | 
			
		||||
    STRAY;
 | 
			
		||||
 | 
			
		||||
    public byte getLegacyId() {
 | 
			
		||||
        return (byte) ordinal();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.util;
 | 
			
		||||
 | 
			
		||||
public enum ZombieType {
 | 
			
		||||
    ZOMBIE,
 | 
			
		||||
    FARMER,
 | 
			
		||||
    LIBRARIAN,
 | 
			
		||||
    PRIEST,
 | 
			
		||||
    BLACKSMITH,
 | 
			
		||||
    BUTCHER,
 | 
			
		||||
    HUSK
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -10,7 +10,6 @@ subprojects {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    dependencies {
 | 
			
		||||
        compileOnly "org.jetbrains:annotations:26.0.1"
 | 
			
		||||
        compileOnly "org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
distributionBase=GRADLE_USER_HOME
 | 
			
		||||
distributionPath=wrapper/dists
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
 | 
			
		||||
zipStoreBase=GRADLE_USER_HOME
 | 
			
		||||
zipStorePath=wrapper/dists
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,9 +9,8 @@ processResources {
 | 
			
		|||
 | 
			
		||||
dependencies {
 | 
			
		||||
    compileOnly "me.clip:placeholderapi:2.11.6" // Placeholder support
 | 
			
		||||
    implementation "com.google.code.gson:gson:2.10.1" // JSON parsing
 | 
			
		||||
    implementation "org.bstats:bstats-bukkit:3.0.2" // Plugin stats
 | 
			
		||||
    implementation "com.github.retrooper:packetevents-spigot:2.9.3" // Packets
 | 
			
		||||
    implementation "com.google.code.gson:gson:2.12.1" // JSON parsing
 | 
			
		||||
    implementation "com.github.retrooper:packetevents-spigot:2.7.1-SNAPSHOT" // Packets
 | 
			
		||||
    implementation "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs
 | 
			
		||||
    implementation "lol.pyr:director-adventure:2.1.2" // Commands
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,7 +38,6 @@ import lol.pyr.znpcsplus.parsers.*;
 | 
			
		|||
import lol.pyr.znpcsplus.scheduling.FoliaScheduler;
 | 
			
		||||
import lol.pyr.znpcsplus.scheduling.SpigotScheduler;
 | 
			
		||||
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
 | 
			
		||||
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.cache.SkinCacheCleanTask;
 | 
			
		||||
import lol.pyr.znpcsplus.storage.NpcStorageType;
 | 
			
		||||
| 
						 | 
				
			
			@ -93,10 +92,10 @@ public class ZNpcsPlus {
 | 
			
		|||
        packetEvents.load();
 | 
			
		||||
 | 
			
		||||
        configManager = new ConfigManager(getDataFolder());
 | 
			
		||||
        skinCache = new MojangSkinCache(configManager, new File(getDataFolder(), "skins"));
 | 
			
		||||
        skinCache = new MojangSkinCache(configManager);
 | 
			
		||||
        propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager);
 | 
			
		||||
 | 
			
		||||
        NpcPropertyRegistryProvider.register(bootstrap, propertyRegistry);
 | 
			
		||||
        NpcPropertyRegistryProvider.register(propertyRegistry);
 | 
			
		||||
        shutdownTasks.add(NpcPropertyRegistryProvider::unregister);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -129,15 +128,14 @@ public class ZNpcsPlus {
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
        PacketFactory packetFactory = setupPacketFactory(scheduler, propertyRegistry, configManager);
 | 
			
		||||
        propertyRegistry.registerTypes(packetFactory, textSerializer, scheduler);
 | 
			
		||||
        propertyRegistry.registerTypes(bootstrap, packetFactory, textSerializer, scheduler);
 | 
			
		||||
 | 
			
		||||
        BungeeConnector bungeeConnector = new BungeeConnector(bootstrap);
 | 
			
		||||
        ActionRegistryImpl actionRegistry = new ActionRegistryImpl();
 | 
			
		||||
        ActionFactoryImpl actionFactory = new ActionFactoryImpl(scheduler, adventure, textSerializer, bungeeConnector);
 | 
			
		||||
        NpcTypeRegistryImpl typeRegistry = new NpcTypeRegistryImpl();
 | 
			
		||||
        NpcSerializerRegistryImpl serializerRegistry = new NpcSerializerRegistryImpl(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
 | 
			
		||||
        NpcRegistryImpl npcRegistry = new NpcRegistryImpl(configManager, this, packetFactory, actionRegistry,
 | 
			
		||||
                scheduler, typeRegistry, propertyRegistry, serializerRegistry, textSerializer);
 | 
			
		||||
                scheduler, typeRegistry, propertyRegistry, textSerializer);
 | 
			
		||||
        shutdownTasks.add(npcRegistry::unload);
 | 
			
		||||
 | 
			
		||||
        UserManager userManager = new UserManager();
 | 
			
		||||
| 
						 | 
				
			
			@ -159,7 +157,7 @@ public class ZNpcsPlus {
 | 
			
		|||
        pluginManager.registerEvents(new UserListener(userManager), bootstrap);
 | 
			
		||||
 | 
			
		||||
        registerCommands(npcRegistry, skinCache, adventure, actionRegistry,
 | 
			
		||||
                typeRegistry, propertyRegistry, importerRegistry, configManager, packetFactory, serializerRegistry);
 | 
			
		||||
                typeRegistry, propertyRegistry, importerRegistry, configManager, packetFactory);
 | 
			
		||||
 | 
			
		||||
        log(ChatColor.WHITE + " * Starting tasks...");
 | 
			
		||||
        if (configManager.getConfig().checkForUpdates()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -193,7 +191,7 @@ public class ZNpcsPlus {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        NpcApiProvider.register(bootstrap, new ZNpcsPlusApi(npcRegistry, typeRegistry, propertyRegistry, actionRegistry, actionFactory, skinCache, serializerRegistry));
 | 
			
		||||
        NpcApiProvider.register(bootstrap, new ZNpcsPlusApi(npcRegistry, typeRegistry, propertyRegistry, actionRegistry, actionFactory, skinCache));
 | 
			
		||||
        log(ChatColor.WHITE + " * Loading complete! (" + (System.currentTimeMillis() - before) + "ms)");
 | 
			
		||||
        log("");
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -246,7 +244,7 @@ public class ZNpcsPlus {
 | 
			
		|||
    private void registerCommands(NpcRegistryImpl npcRegistry, MojangSkinCache skinCache, BukkitAudiences adventure,
 | 
			
		||||
                                  ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry,
 | 
			
		||||
                                  EntityPropertyRegistryImpl propertyRegistry, DataImporterRegistry importerRegistry,
 | 
			
		||||
                                  ConfigManager configManager, PacketFactory packetFactory, NpcSerializerRegistryImpl serializerRegistry) {
 | 
			
		||||
                                  ConfigManager configManager, PacketFactory packetFactory) {
 | 
			
		||||
 | 
			
		||||
        Message<CommandContext> incorrectUsageMessage = context -> context.send(Component.text("Incorrect usage: /" + context.getUsage(), NamedTextColor.RED));
 | 
			
		||||
        CommandManager manager = new CommandManager(bootstrap, adventure, incorrectUsageMessage);
 | 
			
		||||
| 
						 | 
				
			
			@ -296,7 +294,6 @@ public class ZNpcsPlus {
 | 
			
		|||
        registerEnumParser(manager, ArmadilloState.class, incorrectUsageMessage);
 | 
			
		||||
        registerEnumParser(manager, WoldVariant.class, incorrectUsageMessage);
 | 
			
		||||
        registerEnumParser(manager, NpcStorageType.class, incorrectUsageMessage);
 | 
			
		||||
        registerEnumParser(manager, SkeletonType.class, incorrectUsageMessage);
 | 
			
		||||
 | 
			
		||||
        manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root"))
 | 
			
		||||
                .addSubcommand("center", new CenterCommand(npcRegistry))
 | 
			
		||||
| 
						 | 
				
			
			@ -322,7 +319,7 @@ public class ZNpcsPlus {
 | 
			
		|||
                        .addSubcommand("save", new SaveAllCommand(npcRegistry))
 | 
			
		||||
                        .addSubcommand("reload", new LoadAllCommand(npcRegistry))
 | 
			
		||||
                        .addSubcommand("import", new ImportCommand(npcRegistry, importerRegistry))
 | 
			
		||||
                        .addSubcommand("migrate", new MigrateCommand(configManager, this, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, npcRegistry.getStorage(), configManager.getConfig().storageType(), npcRegistry, serializerRegistry)))
 | 
			
		||||
                        .addSubcommand("migrate", new MigrateCommand(configManager, this, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, npcRegistry.getStorage(), configManager.getConfig().storageType(), npcRegistry)))
 | 
			
		||||
                .addSubcommand("holo", new MultiCommand(bootstrap.loadHelpMessage("holo"))
 | 
			
		||||
                        .addSubcommand("add", new HoloAddCommand(npcRegistry))
 | 
			
		||||
                        .addSubcommand("additem", new HoloAddItemCommand(npcRegistry))
 | 
			
		||||
| 
						 | 
				
			
			@ -342,7 +339,6 @@ public class ZNpcsPlus {
 | 
			
		|||
                        .addSubcommand("delete", new ActionDeleteCommand(npcRegistry))
 | 
			
		||||
                        .addSubcommand("edit", new ActionEditCommand(npcRegistry, actionRegistry))
 | 
			
		||||
                        .addSubcommand("list", new ActionListCommand(npcRegistry)))
 | 
			
		||||
                .addSubcommand("version", new VersionCommand(this))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,6 @@ import lol.pyr.znpcsplus.interaction.ActionFactoryImpl;
 | 
			
		|||
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.SkinDescriptorFactoryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -23,16 +22,14 @@ public class ZNpcsPlusApi implements NpcApi {
 | 
			
		|||
    private final ActionRegistryImpl actionRegistry;
 | 
			
		||||
    private final ActionFactoryImpl actionFactory;
 | 
			
		||||
    private final SkinDescriptorFactoryImpl skinDescriptorFactory;
 | 
			
		||||
    private final NpcSerializerRegistryImpl npcSerializerRegistry;
 | 
			
		||||
 | 
			
		||||
    public ZNpcsPlusApi(NpcRegistryImpl npcRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, ActionRegistryImpl actionRegistry, ActionFactoryImpl actionFactory, MojangSkinCache skinCache, NpcSerializerRegistryImpl npcSerializerRegistry) {
 | 
			
		||||
    public ZNpcsPlusApi(NpcRegistryImpl npcRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, ActionRegistryImpl actionRegistry, ActionFactoryImpl actionFactory, MojangSkinCache skinCache) {
 | 
			
		||||
        this.npcRegistry = npcRegistry;
 | 
			
		||||
        this.typeRegistry = typeRegistry;
 | 
			
		||||
        this.propertyRegistry = propertyRegistry;
 | 
			
		||||
        this.actionRegistry = actionRegistry;
 | 
			
		||||
        this.actionFactory = actionFactory;
 | 
			
		||||
        this.skinDescriptorFactory = new SkinDescriptorFactoryImpl(skinCache);
 | 
			
		||||
        this.npcSerializerRegistry = npcSerializerRegistry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -65,9 +62,4 @@ public class ZNpcsPlusApi implements NpcApi {
 | 
			
		|||
    public SkinDescriptorFactory getSkinDescriptorFactory() {
 | 
			
		||||
        return skinDescriptorFactory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public NpcSerializerRegistryImpl getNpcSerializerRegistry() {
 | 
			
		||||
        return npcSerializerRegistry;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,18 +26,12 @@ public class CreateCommand implements CommandHandler {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void run(CommandContext context) throws CommandExecutionException {
 | 
			
		||||
        context.setUsage(context.getLabel() + " create <id> [<type>]");
 | 
			
		||||
        context.setUsage(context.getLabel() + " create <id> <type>");
 | 
			
		||||
        Player player = context.ensureSenderIsPlayer();
 | 
			
		||||
        
 | 
			
		||||
        String id = context.popString();
 | 
			
		||||
        if (npcRegistry.getById(id) != null) context.halt(Component.text("NPC with that ID already exists.", NamedTextColor.RED));
 | 
			
		||||
 | 
			
		||||
        NpcTypeImpl type;
 | 
			
		||||
        if (context.argSize() == 1) {
 | 
			
		||||
            type = context.parse(NpcTypeImpl.class);
 | 
			
		||||
        } else {
 | 
			
		||||
            type = typeRegistry.getByName("player");
 | 
			
		||||
        }
 | 
			
		||||
        NpcTypeImpl type = context.parse(NpcTypeImpl.class);
 | 
			
		||||
 | 
			
		||||
        NpcEntryImpl entry = npcRegistry.create(id, player.getWorld(), type, new NpcLocation(player.getLocation()));
 | 
			
		||||
        entry.enableEverything();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,20 +11,16 @@ import lol.pyr.znpcsplus.npc.NpcImpl;
 | 
			
		|||
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.NameFetchingDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
 | 
			
		||||
import net.kyori.adventure.text.Component;
 | 
			
		||||
import net.kyori.adventure.text.format.NamedTextColor;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileNotFoundException;
 | 
			
		||||
import java.net.MalformedURLException;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
public class SkinCommand implements CommandHandler {
 | 
			
		||||
    private final MojangSkinCache skinCache;
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +63,7 @@ public class SkinCommand implements CommandHandler {
 | 
			
		|||
        } else if (type.equalsIgnoreCase("dynamic")) {
 | 
			
		||||
            context.ensureArgsNotEmpty();
 | 
			
		||||
            String name = context.dumpAllArgs();
 | 
			
		||||
            npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new NameFetchingDescriptor(skinCache, name));
 | 
			
		||||
            npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new FetchingDescriptor(skinCache, name));
 | 
			
		||||
            npc.respawn();
 | 
			
		||||
            context.halt(Component.text("The NPC's skin will now be resolved per-player from \"" + name + "\""));
 | 
			
		||||
        } else if (type.equalsIgnoreCase("url")) {
 | 
			
		||||
| 
						 | 
				
			
			@ -94,30 +90,6 @@ public class SkinCommand implements CommandHandler {
 | 
			
		|||
                context.send(Component.text("Invalid url!", NamedTextColor.RED));
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        } else if (type.equalsIgnoreCase("file")) {
 | 
			
		||||
            context.ensureArgsNotEmpty();
 | 
			
		||||
            String path = context.dumpAllArgs();
 | 
			
		||||
            context.send(Component.text("Fetching skin from file \"" + path + "\"...", NamedTextColor.GREEN));
 | 
			
		||||
            PrefetchedDescriptor.fromFile(skinCache, path).exceptionally(e -> {
 | 
			
		||||
                if (e instanceof FileNotFoundException || e.getCause() instanceof FileNotFoundException) {
 | 
			
		||||
                    context.send(Component.text("A file at the specified path could not be found!", NamedTextColor.RED));
 | 
			
		||||
                } else {
 | 
			
		||||
                    context.send(Component.text("An error occurred while fetching the skin from file! Check the console for more details.", NamedTextColor.RED));
 | 
			
		||||
                    //noinspection CallToPrintStackTrace
 | 
			
		||||
                    e.printStackTrace();
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }).thenAccept(skin -> {
 | 
			
		||||
                if (skin == null) return;
 | 
			
		||||
                if (skin.getSkin() == null) {
 | 
			
		||||
                    context.send(Component.text("Failed to fetch skin, are you sure the file path is valid?", NamedTextColor.RED));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), skin);
 | 
			
		||||
                npc.respawn();
 | 
			
		||||
                context.send(Component.text("The NPC's skin has been set.", NamedTextColor.GREEN));
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        context.send(Component.text("Unknown skin type! Please use one of the following: mirror, static, dynamic, url"));
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -125,19 +97,11 @@ public class SkinCommand implements CommandHandler {
 | 
			
		|||
    @Override
 | 
			
		||||
    public List<String> suggest(CommandContext context) throws CommandExecutionException {
 | 
			
		||||
        if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds());
 | 
			
		||||
        if (context.argSize() == 2) return context.suggestLiteral("mirror", "static", "dynamic", "url", "file");
 | 
			
		||||
        if (context.argSize() == 2) return context.suggestLiteral("mirror", "static", "dynamic", "url");
 | 
			
		||||
        if (context.matchSuggestion("*", "static")) return context.suggestPlayers();
 | 
			
		||||
        if (context.argSize() == 3 && context.matchSuggestion("*", "url")) {
 | 
			
		||||
            return context.suggestLiteral("slim", "classic");
 | 
			
		||||
        }
 | 
			
		||||
        if (context.argSize() == 3 && context.matchSuggestion("*", "file")) {
 | 
			
		||||
            if (skinCache.getSkinsFolder().exists()) {
 | 
			
		||||
                File[] files = skinCache.getSkinsFolder().listFiles();
 | 
			
		||||
                if (files != null) {
 | 
			
		||||
                    return Arrays.stream(files).map(File::getName).collect(Collectors.toList());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return Collections.emptyList();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,14 +21,9 @@ public class ToggleCommand implements CommandHandler {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void run(CommandContext context) throws CommandExecutionException {
 | 
			
		||||
        context.setUsage(context.getLabel() + " toggle <id> [<enable/disable>]");
 | 
			
		||||
        context.setUsage(context.getLabel() + " toggle <id>");
 | 
			
		||||
        NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc();
 | 
			
		||||
        boolean enabled;
 | 
			
		||||
        if (context.argSize() == 1) {
 | 
			
		||||
            enabled = context.popString().equalsIgnoreCase("enable");
 | 
			
		||||
        } else {
 | 
			
		||||
            enabled = !npc.isEnabled();
 | 
			
		||||
        }
 | 
			
		||||
        boolean enabled = !npc.isEnabled();
 | 
			
		||||
        npc.setEnabled(enabled);
 | 
			
		||||
        context.send(Component.text("NPC has been " + (enabled ? "enabled" : "disabled"), NamedTextColor.GREEN));
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +31,6 @@ public class ToggleCommand implements CommandHandler {
 | 
			
		|||
    @Override
 | 
			
		||||
    public List<String> suggest(CommandContext context) throws CommandExecutionException {
 | 
			
		||||
        if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds());
 | 
			
		||||
        if (context.argSize() == 2) return context.suggestLiteral("enable", "disable");
 | 
			
		||||
        return Collections.emptyList();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,66 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.commands;
 | 
			
		||||
 | 
			
		||||
import lol.pyr.director.adventure.command.CommandContext;
 | 
			
		||||
import lol.pyr.director.adventure.command.CommandHandler;
 | 
			
		||||
import lol.pyr.director.common.command.CommandExecutionException;
 | 
			
		||||
import lol.pyr.znpcsplus.ZNpcsPlus;
 | 
			
		||||
import net.kyori.adventure.text.Component;
 | 
			
		||||
import net.kyori.adventure.text.event.ClickEvent;
 | 
			
		||||
import net.kyori.adventure.text.format.NamedTextColor;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.net.URISyntaxException;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.jar.Attributes;
 | 
			
		||||
import java.util.jar.JarFile;
 | 
			
		||||
 | 
			
		||||
public class VersionCommand implements CommandHandler {
 | 
			
		||||
 | 
			
		||||
    private final String pluginVersion;
 | 
			
		||||
    private final String gitBranch;
 | 
			
		||||
    private final String gitCommitHash;
 | 
			
		||||
    private final String buildId;
 | 
			
		||||
 | 
			
		||||
    public VersionCommand(ZNpcsPlus plugin) {
 | 
			
		||||
        pluginVersion = plugin.getDescription().getVersion();
 | 
			
		||||
        String gitBranch = "";
 | 
			
		||||
        String gitCommitHash = "";
 | 
			
		||||
        String buildId = "";
 | 
			
		||||
        try {
 | 
			
		||||
            URL jarUrl = getClass().getProtectionDomain().getCodeSource().getLocation();
 | 
			
		||||
            JarFile jarFile = new JarFile(jarUrl.toURI().getPath());
 | 
			
		||||
            Attributes attributes = jarFile.getManifest().getMainAttributes();
 | 
			
		||||
            gitBranch = attributes.getValue("Git-Branch");
 | 
			
		||||
            gitCommitHash = attributes.getValue("Git-Commit-Hash");
 | 
			
		||||
            buildId = attributes.getValue("Build-Id");
 | 
			
		||||
        } catch (IOException | URISyntaxException e) {
 | 
			
		||||
            e.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
        this.gitBranch = gitBranch;
 | 
			
		||||
        this.gitCommitHash = gitCommitHash;
 | 
			
		||||
        this.buildId = buildId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void run(CommandContext context) throws CommandExecutionException {
 | 
			
		||||
 | 
			
		||||
        StringBuilder versionBuilder = new StringBuilder("This server is running ZNPCsPlus version ").append(pluginVersion);
 | 
			
		||||
        if (gitBranch != null && !gitBranch.isEmpty()) {
 | 
			
		||||
            versionBuilder.append("-").append(gitBranch);
 | 
			
		||||
        }
 | 
			
		||||
        if (gitCommitHash != null && !gitCommitHash.isEmpty()) {
 | 
			
		||||
            versionBuilder.append("@").append(gitCommitHash);
 | 
			
		||||
        }
 | 
			
		||||
        if (buildId != null && !buildId.isEmpty()) {
 | 
			
		||||
            versionBuilder.append(" (Build #").append(buildId).append(")");
 | 
			
		||||
        } else {
 | 
			
		||||
            versionBuilder.append(" (Development Build)");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        String version = versionBuilder.toString();
 | 
			
		||||
 | 
			
		||||
        context.send(Component.text(version, NamedTextColor.GREEN)
 | 
			
		||||
                .hoverEvent(Component.text("Click to copy version to clipboard"))
 | 
			
		||||
                .clickEvent(ClickEvent.copyToClipboard(version)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +11,6 @@ import lol.pyr.director.adventure.command.CommandHandler;
 | 
			
		|||
import lol.pyr.director.common.command.CommandExecutionException;
 | 
			
		||||
import lol.pyr.znpcsplus.api.entity.EntityProperty;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.properties.attributes.AttributeProperty;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +44,6 @@ public class PropertySetCommand implements CommandHandler {
 | 
			
		|||
        // TODO: find a way to do this better & rewrite this mess
 | 
			
		||||
 | 
			
		||||
        if (!npc.getType().getAllowedProperties().contains(property)) context.halt(Component.text("Property " + property.getName() + " not allowed for npc type " + npc.getType().getName(), NamedTextColor.RED));
 | 
			
		||||
        if (!property.isPlayerModifiable()) context.halt(Component.text("This property is not modifiable by players", NamedTextColor.RED));
 | 
			
		||||
        Class<?> type = property.getType();
 | 
			
		||||
        Object value;
 | 
			
		||||
        String valueName;
 | 
			
		||||
| 
						 | 
				
			
			@ -126,15 +124,6 @@ public class PropertySetCommand implements CommandHandler {
 | 
			
		|||
            value = context.parse(type);
 | 
			
		||||
            valueName = value == null ? "NONE" : ((Vector3i) value).toPrettyString();
 | 
			
		||||
        }
 | 
			
		||||
        else if (property instanceof AttributeProperty) {
 | 
			
		||||
            value = context.parse(type);
 | 
			
		||||
            if ((double) value < ((AttributeProperty) property).getMinValue() || (double) value > ((AttributeProperty) property).getMaxValue()) {
 | 
			
		||||
                double sanitizedValue = ((AttributeProperty) property).sanitizeValue((double) value);
 | 
			
		||||
                context.send(Component.text("WARNING: Value " + value + " is out of range for property " + property.getName() + ", setting to " + sanitizedValue, NamedTextColor.YELLOW));
 | 
			
		||||
                value = sanitizedValue;
 | 
			
		||||
            }
 | 
			
		||||
            valueName = String.valueOf(value);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            try {
 | 
			
		||||
                value = context.parse(type);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,13 +7,13 @@ import lol.pyr.znpcsplus.conversion.DataImporter;
 | 
			
		|||
import lol.pyr.znpcsplus.conversion.DataImporterRegistry;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.util.FutureUtil;
 | 
			
		||||
import net.kyori.adventure.text.Component;
 | 
			
		||||
import net.kyori.adventure.text.format.NamedTextColor;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
public class ImportCommand implements CommandHandler {
 | 
			
		||||
    private final NpcRegistryImpl npcRegistry;
 | 
			
		||||
| 
						 | 
				
			
			@ -33,7 +33,7 @@ public class ImportCommand implements CommandHandler {
 | 
			
		|||
        if (importer == null) context.halt(Component.text("Importer not found! Possible importers: " +
 | 
			
		||||
                String.join(", ", importerRegistry.getIds()), NamedTextColor.RED));
 | 
			
		||||
 | 
			
		||||
        FutureUtil.exceptionPrintingRunAsync(() -> {
 | 
			
		||||
        CompletableFuture.runAsync(() -> {
 | 
			
		||||
            if (!importer.isValid()) {
 | 
			
		||||
                context.send(Component.text("There is no data to import from this importer!", NamedTextColor.RED));
 | 
			
		||||
                return;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,12 +4,12 @@ import lol.pyr.director.adventure.command.CommandContext;
 | 
			
		|||
import lol.pyr.director.adventure.command.CommandHandler;
 | 
			
		||||
import lol.pyr.director.common.command.CommandExecutionException;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.util.FutureUtil;
 | 
			
		||||
import net.kyori.adventure.text.Component;
 | 
			
		||||
import net.kyori.adventure.text.format.NamedTextColor;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
public class LoadAllCommand implements CommandHandler {
 | 
			
		||||
    private final NpcRegistryImpl npcRegistry;
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ public class LoadAllCommand implements CommandHandler {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void run(CommandContext context) throws CommandExecutionException {
 | 
			
		||||
        FutureUtil.exceptionPrintingRunAsync(() -> {
 | 
			
		||||
        CompletableFuture.runAsync(() -> {
 | 
			
		||||
            npcRegistry.reload();
 | 
			
		||||
            context.send(Component.text("All NPCs have been re-loaded from storage", NamedTextColor.GREEN));
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,6 @@ import lol.pyr.znpcsplus.npc.NpcEntryImpl;
 | 
			
		|||
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		||||
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.storage.NpcStorage;
 | 
			
		||||
import lol.pyr.znpcsplus.storage.NpcStorageType;
 | 
			
		||||
import net.kyori.adventure.text.Component;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,9 +35,8 @@ public class MigrateCommand implements CommandHandler {
 | 
			
		|||
    private final NpcStorage currentStorage;
 | 
			
		||||
    private final NpcStorageType currentStorageType;
 | 
			
		||||
    private final NpcRegistryImpl npcRegistry;
 | 
			
		||||
    private final NpcSerializerRegistryImpl serializerRegistry;
 | 
			
		||||
 | 
			
		||||
    public MigrateCommand(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcStorage currentStorage, NpcStorageType currentStorageType, NpcRegistryImpl npcRegistry, NpcSerializerRegistryImpl serializerRegistry) {
 | 
			
		||||
    public MigrateCommand(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcStorage currentStorage, NpcStorageType currentStorageType, NpcRegistryImpl npcRegistry) {
 | 
			
		||||
        this.configManager = configManager;
 | 
			
		||||
        this.plugin = plugin;
 | 
			
		||||
        this.packetFactory = packetFactory;
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +47,6 @@ public class MigrateCommand implements CommandHandler {
 | 
			
		|||
        this.currentStorage = currentStorage;
 | 
			
		||||
        this.currentStorageType = currentStorageType;
 | 
			
		||||
        this.npcRegistry = npcRegistry;
 | 
			
		||||
        this.serializerRegistry = serializerRegistry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +63,7 @@ public class MigrateCommand implements CommandHandler {
 | 
			
		|||
        if (currentStorageType == from) {
 | 
			
		||||
            fromStorage = currentStorage;
 | 
			
		||||
        } else {
 | 
			
		||||
            fromStorage = from.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, serializerRegistry);
 | 
			
		||||
            fromStorage = from.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
 | 
			
		||||
            if (fromStorage == null) {
 | 
			
		||||
                context.halt(Component.text("Failed to initialize the source storage. Please check the console for more information.", NamedTextColor.RED));
 | 
			
		||||
                return;
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +84,7 @@ public class MigrateCommand implements CommandHandler {
 | 
			
		|||
        if (currentStorageType == to) {
 | 
			
		||||
            toStorage = currentStorage;
 | 
			
		||||
        } else {
 | 
			
		||||
            toStorage = to.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, serializerRegistry);
 | 
			
		||||
            toStorage = to.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
 | 
			
		||||
            if (toStorage == null) {
 | 
			
		||||
                context.halt(Component.text("Failed to initialize the destination storage. Please check the console for more information.", NamedTextColor.RED));
 | 
			
		||||
                return;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,12 +4,12 @@ import lol.pyr.director.adventure.command.CommandContext;
 | 
			
		|||
import lol.pyr.director.adventure.command.CommandHandler;
 | 
			
		||||
import lol.pyr.director.common.command.CommandExecutionException;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.util.FutureUtil;
 | 
			
		||||
import net.kyori.adventure.text.Component;
 | 
			
		||||
import net.kyori.adventure.text.format.NamedTextColor;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
public class SaveAllCommand implements CommandHandler {
 | 
			
		||||
    private final NpcRegistryImpl npcRegistry;
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ public class SaveAllCommand implements CommandHandler {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void run(CommandContext context) throws CommandExecutionException {
 | 
			
		||||
        FutureUtil.exceptionPrintingRunAsync(() -> {
 | 
			
		||||
        CompletableFuture.runAsync(() -> {
 | 
			
		||||
            npcRegistry.save();
 | 
			
		||||
            context.send(Component.text("All NPCs have been saved to storage", NamedTextColor.GREEN));
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,7 +24,7 @@ public class HologramTrait extends SectionCitizensTrait {
 | 
			
		|||
        if (linesSection != null) {
 | 
			
		||||
            List<String> keys = new ArrayList<>(linesSection.getKeys(false));
 | 
			
		||||
            for (int i = keys.size() - 1; i >= 0; i--) {
 | 
			
		||||
                String line = linesSection.isConfigurationSection(keys.get(i)) ? linesSection.getConfigurationSection(keys.get(i)).getString("text") : linesSection.getString(keys.get(i));
 | 
			
		||||
                String line = linesSection.getConfigurationSection(keys.get(i)).getString("text");
 | 
			
		||||
                if (line != null) {
 | 
			
		||||
                    Component component = textSerializer.deserialize(line);
 | 
			
		||||
                    npc.getHologram().addTextLineComponent(component);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		|||
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.SkinImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.NameFetchingDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.util.BungeeConnector;
 | 
			
		||||
| 
						 | 
				
			
			@ -175,7 +175,7 @@ public class ZNpcImporter implements DataImporter {
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
            if (model.getSkinName() != null) {
 | 
			
		||||
                npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new NameFetchingDescriptor(skinCache, model.getSkinName()));
 | 
			
		||||
                npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new FetchingDescriptor(skinCache, model.getSkinName()));
 | 
			
		||||
            }
 | 
			
		||||
            else if (model.getSkin() != null && model.getSignature() != null) {
 | 
			
		||||
                npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new SkinImpl(model.getSkin(), model.getSignature())));
 | 
			
		||||
| 
						 | 
				
			
			@ -189,15 +189,12 @@ public class ZNpcImporter implements DataImporter {
 | 
			
		|||
                if (toggleValues.containsKey("mirror")) {
 | 
			
		||||
                    npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new MirrorDescriptor(skinCache));
 | 
			
		||||
                }
 | 
			
		||||
                if (toggleValues.containsKey("glow") && (boolean) toggleValues.get("glow")) {
 | 
			
		||||
                    if (!model.getGlowName().isEmpty())
 | 
			
		||||
                if (toggleValues.containsKey("glow")) {
 | 
			
		||||
                    try {
 | 
			
		||||
                            npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.valueOf(model.getGlowName()));
 | 
			
		||||
                        npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.valueOf((String) toggleValues.get("glow")));
 | 
			
		||||
                    } catch (IllegalArgumentException e) {
 | 
			
		||||
                        npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.WHITE);
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                        npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.WHITE);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -84,8 +84,4 @@ public class ZNpcsModel {
 | 
			
		|||
    public String getSignature() {
 | 
			
		||||
        return signature;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getGlowName() {
 | 
			
		||||
        return glowName;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,76 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.entity;
 | 
			
		||||
 | 
			
		||||
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
 | 
			
		||||
import lol.pyr.znpcsplus.api.entity.EntityProperty;
 | 
			
		||||
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
 | 
			
		||||
import org.bukkit.inventory.ItemStack;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents an armor stand vehicle entity.
 | 
			
		||||
 * <p>
 | 
			
		||||
 *     This entity is used to make the NPC sit on an invisible armor stand.
 | 
			
		||||
 * </p>
 | 
			
		||||
 */
 | 
			
		||||
public class ArmorStandVehicleProperties implements PropertyHolder {
 | 
			
		||||
 | 
			
		||||
    private final Map<EntityPropertyImpl<?>, Object> propertyMap = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
    public ArmorStandVehicleProperties(EntityPropertyRegistryImpl propertyRegistry) {
 | 
			
		||||
        _setProperty(propertyRegistry.getByName("small", Boolean.class), true);
 | 
			
		||||
        _setProperty(propertyRegistry.getByName("invisible", Boolean.class), true);
 | 
			
		||||
        _setProperty(propertyRegistry.getByName("base_plate", Boolean.class), false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    public <T> T getProperty(EntityProperty<T> key) {
 | 
			
		||||
        return hasProperty(key) ? (T) propertyMap.get((EntityPropertyImpl<?>) key) : key.getDefaultValue();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasProperty(EntityProperty<?> key) {
 | 
			
		||||
        return propertyMap.containsKey((EntityPropertyImpl<?>) key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    private <T> void _setProperty(EntityProperty<T> key, T value) {
 | 
			
		||||
        Object val = value;
 | 
			
		||||
        if (val instanceof ItemStack) val = SpigotConversionUtil.fromBukkitItemStack((ItemStack) val);
 | 
			
		||||
 | 
			
		||||
        setProperty((EntityPropertyImpl<T>) key, (T) val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> void setProperty(EntityProperty<T> key, T value) {
 | 
			
		||||
        throw new UnsupportedOperationException("Cannot set properties on armor stands");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setItemProperty(EntityProperty<?> key, ItemStack value) {
 | 
			
		||||
        throw new UnsupportedOperationException("Cannot set item properties on armor stands");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack getItemProperty(EntityProperty<?> key) {
 | 
			
		||||
        throw new UnsupportedOperationException("Cannot get item properties on armor stands");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public <T> void setProperty(EntityPropertyImpl<T> key, T value) {
 | 
			
		||||
        if (key == null) return;
 | 
			
		||||
        if (value == null || value.equals(key.getDefaultValue())) propertyMap.remove(key);
 | 
			
		||||
        else propertyMap.put(key, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Set<EntityProperty<?>> getAllProperties() {
 | 
			
		||||
        return Collections.unmodifiableSet(propertyMap.keySet());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Set<EntityProperty<?>> getAppliedProperties() {
 | 
			
		||||
        return Collections.unmodifiableSet(propertyMap.keySet());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -47,16 +47,16 @@ public abstract class EntityPropertyImpl<T> implements EntityProperty<T> {
 | 
			
		|||
        dependencies.add(property);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected static <V> EntityData<V> newEntityData(int index, EntityDataType<V> type, V value) {
 | 
			
		||||
        return new EntityData<>(index, type, value);
 | 
			
		||||
    protected static <V> EntityData newEntityData(int index, EntityDataType<V> type, V value) {
 | 
			
		||||
        return new EntityData(index, type, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<EntityData<?>> applyStandalone(Player player, PacketEntity packetEntity, boolean isSpawned) {
 | 
			
		||||
        Map<Integer, EntityData<?>> map = new HashMap<>();
 | 
			
		||||
    public List<EntityData> applyStandalone(Player player, PacketEntity packetEntity, boolean isSpawned) {
 | 
			
		||||
        Map<Integer, EntityData> map = new HashMap<>();
 | 
			
		||||
        apply(player, packetEntity, isSpawned, map);
 | 
			
		||||
        for (EntityPropertyImpl<?> property : dependencies) property.apply(player, packetEntity, isSpawned, map);
 | 
			
		||||
        return new ArrayList<>(map.values());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    abstract public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties);
 | 
			
		||||
    abstract public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,7 +2,6 @@ package lol.pyr.znpcsplus.entity;
 | 
			
		|||
 | 
			
		||||
import com.github.retrooper.packetevents.PacketEvents;
 | 
			
		||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.pose.EntityPose;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
 | 
			
		||||
| 
						 | 
				
			
			@ -10,12 +9,12 @@ import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
 | 
			
		|||
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.player.EquipmentSlot;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.world.BlockFace;
 | 
			
		||||
import lol.pyr.znpcsplus.ZNpcsPlusBootstrap;
 | 
			
		||||
import lol.pyr.znpcsplus.api.entity.EntityProperty;
 | 
			
		||||
import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
 | 
			
		||||
import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.config.ConfigManager;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.properties.*;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.properties.attributes.AttributeProperty;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.properties.villager.VillagerLevelProperty;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.properties.villager.VillagerProfessionProperty;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.properties.villager.VillagerTypeProperty;
 | 
			
		||||
| 
						 | 
				
			
			@ -33,19 +32,18 @@ import java.util.*;
 | 
			
		|||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1.8  <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2767708">...</a>
 | 
			
		||||
 * 1.9  <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768074">...</a>
 | 
			
		||||
 * 1.10 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768201">...</a>
 | 
			
		||||
 * 1.11 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768444">...</a>
 | 
			
		||||
 * 1.12 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768647">...</a>
 | 
			
		||||
 * 1.13 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768701">...</a>
 | 
			
		||||
 * 1.14 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768716">...</a>
 | 
			
		||||
 * 1.15 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768877">...</a>
 | 
			
		||||
 * 1.16 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2769100">...</a>
 | 
			
		||||
 * 1.17 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2769318">...</a>
 | 
			
		||||
 * 1.18-1.19 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2769409">...</a>
 | 
			
		||||
 * 1.20 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2769476">...</a>
 | 
			
		||||
 * 1.21 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata">...</a>
 | 
			
		||||
 * 1.8  <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=7415">...</a>
 | 
			
		||||
 * 1.9  <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=7968">...</a>
 | 
			
		||||
 * 1.10 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=8241">...</a>
 | 
			
		||||
 * 1.11 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=8534">...</a>
 | 
			
		||||
 * 1.12 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=14048">...</a>
 | 
			
		||||
 * 1.13 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=14800">...</a>
 | 
			
		||||
 * 1.14 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=15240">...</a>
 | 
			
		||||
 * 1.15 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=15991">...</a>
 | 
			
		||||
 * 1.16 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=16539">...</a>
 | 
			
		||||
 * 1.17 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=17521">...</a>
 | 
			
		||||
 * 1.18-1.19 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=18191">...</a>
 | 
			
		||||
 * 1.20 <a href="https://wiki.vg/index.php?title=Entity_metadata">...</a>
 | 
			
		||||
 */
 | 
			
		||||
@SuppressWarnings("unchecked")
 | 
			
		||||
public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
			
		||||
| 
						 | 
				
			
			@ -92,7 +90,6 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
			
		|||
        registerEnumSerializer(Sound.class);
 | 
			
		||||
        registerEnumSerializer(ArmadilloState.class);
 | 
			
		||||
        registerEnumSerializer(WoldVariant.class);
 | 
			
		||||
        registerEnumSerializer(SkeletonType.class);
 | 
			
		||||
 | 
			
		||||
        registerPrimitiveSerializers(Integer.class, Boolean.class, Double.class, Float.class, Long.class, Short.class, Byte.class, String.class);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -108,7 +105,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
			
		|||
         */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void registerTypes(PacketFactory packetFactory, LegacyComponentSerializer textSerializer, TaskScheduler taskScheduler) {
 | 
			
		||||
    public void registerTypes(ZNpcsPlusBootstrap plugin, PacketFactory packetFactory, LegacyComponentSerializer textSerializer, TaskScheduler taskScheduler) {
 | 
			
		||||
        ServerVersion ver = PacketEvents.getAPI().getServerManager().getVersion();
 | 
			
		||||
        boolean legacyBooleans = ver.isOlderThan(ServerVersion.V_1_9);
 | 
			
		||||
        boolean legacyNames = ver.isOlderThan(ServerVersion.V_1_9);
 | 
			
		||||
| 
						 | 
				
			
			@ -127,12 +124,11 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
			
		|||
 | 
			
		||||
        register(new DummyProperty<>("look", LookType.FIXED));
 | 
			
		||||
        register(new DummyProperty<>("look_distance", configManager.getConfig().lookPropertyDistance()));
 | 
			
		||||
        register(new DummyProperty<>("look_return", false));
 | 
			
		||||
        register(new DummyProperty<>("view_distance", configManager.getConfig().viewDistance()));
 | 
			
		||||
 | 
			
		||||
        register(new DummyProperty<>("permission_required", false));
 | 
			
		||||
 | 
			
		||||
        register(new ForceBodyRotationProperty(taskScheduler));
 | 
			
		||||
        register(new ForceBodyRotationProperty(plugin, taskScheduler));
 | 
			
		||||
 | 
			
		||||
        register(new DummyProperty<>("player_knockback", false));
 | 
			
		||||
        register(new DummyProperty<>("player_knockback_exempt_permission", String.class));
 | 
			
		||||
| 
						 | 
				
			
			@ -155,16 +151,6 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
			
		|||
        linkProperties("glow", "fire", "invisible");
 | 
			
		||||
        register(new BooleanProperty("silent", 4, false, legacyBooleans));
 | 
			
		||||
 | 
			
		||||
        // Attribute Max Health
 | 
			
		||||
        register(new AttributeProperty(packetFactory, "attribute_max_health", Attributes.MAX_HEALTH));
 | 
			
		||||
 | 
			
		||||
        // Health - LivingEntity
 | 
			
		||||
        int healthIndex = 6;
 | 
			
		||||
        if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) healthIndex = 9;
 | 
			
		||||
        else if (ver.isNewerThanOrEquals(ServerVersion.V_1_14)) healthIndex = 8;
 | 
			
		||||
        else if (ver.isNewerThanOrEquals(ServerVersion.V_1_10)) healthIndex = 7;
 | 
			
		||||
        register(new HealthProperty(healthIndex));
 | 
			
		||||
 | 
			
		||||
        final int tameableIndex;
 | 
			
		||||
        if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) tameableIndex = 17;
 | 
			
		||||
        else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) tameableIndex = 16;
 | 
			
		||||
| 
						 | 
				
			
			@ -191,13 +177,11 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
			
		|||
        else if (ver.isNewerThanOrEquals(ServerVersion.V_1_9)) babyIndex = 11;
 | 
			
		||||
        else babyIndex = 12;
 | 
			
		||||
        if (ver.isOlderThan(ServerVersion.V_1_9)) {
 | 
			
		||||
            register(new LegacyBabyProperty(babyIndex));
 | 
			
		||||
            register(new EncodedByteProperty<>("baby", false, babyIndex, obj -> (byte) (obj ? -1 : 0)));
 | 
			
		||||
        } else {
 | 
			
		||||
            register(new BooleanProperty("baby", babyIndex, false, legacyBooleans));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        register(new EntitySittingProperty(packetFactory, this));
 | 
			
		||||
 | 
			
		||||
        // Player
 | 
			
		||||
        register(new DummyProperty<>("skin", SkinDescriptor.class, false));
 | 
			
		||||
        final int skinLayersIndex;
 | 
			
		||||
| 
						 | 
				
			
			@ -285,9 +269,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
			
		|||
        else horseIndex = 16;
 | 
			
		||||
        int horseEating = ver.isNewerThanOrEquals(ServerVersion.V_1_12) ? 0x10 : 0x20;
 | 
			
		||||
        register(new BitsetProperty("is_tame", horseIndex, 0x02, false, legacyBooleans));
 | 
			
		||||
        if (ver.isOlderThan(ServerVersion.V_1_21)) {
 | 
			
		||||
        register(new BitsetProperty("is_saddled", horseIndex, 0x04, false, legacyBooleans));
 | 
			
		||||
        }
 | 
			
		||||
        register(new BitsetProperty("is_eating", horseIndex, horseEating, false, legacyBooleans));
 | 
			
		||||
        register(new BitsetProperty("is_rearing", horseIndex, horseEating << 1, false, legacyBooleans));
 | 
			
		||||
        register(new BitsetProperty("has_mouth_open", horseIndex, horseEating << 2, false, legacyBooleans));
 | 
			
		||||
| 
						 | 
				
			
			@ -355,14 +337,10 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
			
		|||
        // Chested Horse
 | 
			
		||||
        if (ver.isOlderThan(ServerVersion.V_1_11)) {
 | 
			
		||||
            register(new BitsetProperty("has_chest", horseIndex, 0x08, false, legacyBooleans));
 | 
			
		||||
            linkProperties("is_tame", "is_saddled", "has_chest", "is_eating", "is_rearing", "has_mouth_open");
 | 
			
		||||
            linkProperties("is_saddled", "has_chest", "is_eating", "is_rearing", "has_mouth_open");
 | 
			
		||||
        } else {
 | 
			
		||||
            register(new BooleanProperty("has_chest", horseVariantIndex, false, legacyBooleans));
 | 
			
		||||
            if (ver.isOlderThan(ServerVersion.V_1_21)){
 | 
			
		||||
                linkProperties("is_tame", "is_saddled", "is_eating", "is_rearing", "has_mouth_open");
 | 
			
		||||
            } else {
 | 
			
		||||
                linkProperties("is_tame", "is_eating", "is_rearing", "has_mouth_open");
 | 
			
		||||
            }
 | 
			
		||||
            linkProperties("is_saddled", "is_eating", "is_rearing", "has_mouth_open");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Slime, Magma Cube and Phantom
 | 
			
		||||
| 
						 | 
				
			
			@ -458,39 +436,6 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
			
		|||
        witherIndex += 3; // skip the first 3 indexes, will be used for the other properties later
 | 
			
		||||
        register(new IntegerProperty("invulnerable_time", witherIndex, 0, false));
 | 
			
		||||
 | 
			
		||||
        // Skeleton
 | 
			
		||||
        if (ver.isOlderThan(ServerVersion.V_1_11)) {
 | 
			
		||||
            if (legacyBooleans) register(new EncodedByteProperty<>("skeleton_type", SkeletonType.NORMAL, 13, SkeletonType::getLegacyId));
 | 
			
		||||
            else register(new EncodedIntegerProperty<>("skeleton_type", SkeletonType.NORMAL, ver.isOlderThan(ServerVersion.V_1_10) ? 11 : 12, Enum::ordinal));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Zombie
 | 
			
		||||
        int zombieIndex;
 | 
			
		||||
        if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) zombieIndex = 17;
 | 
			
		||||
        else if (ver.isNewerThanOrEquals(ServerVersion.V_1_16)) zombieIndex = 16;
 | 
			
		||||
        else if (ver.isNewerThanOrEquals(ServerVersion.V_1_10)) zombieIndex = 13;
 | 
			
		||||
        else if (ver.isNewerThanOrEquals(ServerVersion.V_1_9)) zombieIndex = 12;
 | 
			
		||||
        else zombieIndex = 13;
 | 
			
		||||
 | 
			
		||||
        if (ver.isOlderThan(ServerVersion.V_1_9)) {
 | 
			
		||||
            register(new EncodedByteProperty<>("zombie_is_villager", false, zombieIndex++, b -> (byte) (b ? 1 : 0)));
 | 
			
		||||
        } else if (ver.isOlderThan(ServerVersion.V_1_11)) {
 | 
			
		||||
            register(new EncodedIntegerProperty<>("zombie_type", ZombieType.ZOMBIE, zombieIndex++, Enum::ordinal));
 | 
			
		||||
        } else {
 | 
			
		||||
            zombieIndex++; // Not a mistake, this is field unused in 1.11+
 | 
			
		||||
        }
 | 
			
		||||
        if (ver.isOlderThan(ServerVersion.V_1_9)) {
 | 
			
		||||
            register(new EncodedByteProperty<>("is_converting", false, zombieIndex++, b -> (byte) (b ? 1 : 0)));
 | 
			
		||||
        } else if (ver.isOlderThan(ServerVersion.V_1_11)) {
 | 
			
		||||
            register(new BooleanProperty("is_converting", zombieIndex++, false, legacyBooleans));
 | 
			
		||||
        }
 | 
			
		||||
        if (ver.isNewerThanOrEquals(ServerVersion.V_1_9) && ver.isOlderThan(ServerVersion.V_1_14)) {
 | 
			
		||||
            register(new BooleanProperty("zombie_hands_held_up", zombieIndex++, false, legacyBooleans));
 | 
			
		||||
        }
 | 
			
		||||
        if (ver.isNewerThanOrEquals(ServerVersion.V_1_13)) {
 | 
			
		||||
            register(new BooleanProperty("zombie_becoming_drowned", zombieIndex++, false, legacyBooleans));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!ver.isNewerThanOrEquals(ServerVersion.V_1_9)) return;
 | 
			
		||||
        // Shulker
 | 
			
		||||
        int shulkerIndex;
 | 
			
		||||
| 
						 | 
				
			
			@ -538,9 +483,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
			
		|||
        else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) llamaIndex = 20;
 | 
			
		||||
        else if (ver.isNewerThanOrEquals(ServerVersion.V_1_14)) llamaIndex = 19;
 | 
			
		||||
        else llamaIndex = 17;
 | 
			
		||||
 | 
			
		||||
        // Removed in 1.21
 | 
			
		||||
        if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21)) register(new EncodedIntegerProperty<DyeColor>("carpet_color", DyeColor.class, llamaIndex++, obj -> obj == null ? -1 : obj.ordinal()));
 | 
			
		||||
        register(new EncodedIntegerProperty<DyeColor>("carpet_color", DyeColor.class, llamaIndex++, obj -> obj == null ? -1 : obj.ordinal()));
 | 
			
		||||
        register(new EncodedIntegerProperty<>("llama_variant", LlamaVariant.CREAMY, llamaIndex, Enum::ordinal));
 | 
			
		||||
 | 
			
		||||
        if (!ver.isNewerThanOrEquals(ServerVersion.V_1_12)) return;
 | 
			
		||||
| 
						 | 
				
			
			@ -720,20 +663,8 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
			
		|||
 | 
			
		||||
        if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21)) return;
 | 
			
		||||
 | 
			
		||||
        register(new EquipmentProperty(packetFactory, "body", EquipmentSlot.BODY));
 | 
			
		||||
 | 
			
		||||
        // Bogged
 | 
			
		||||
        register(new BooleanProperty("bogged_sheared", 16, false, legacyBooleans));
 | 
			
		||||
 | 
			
		||||
        if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21_2)) return;
 | 
			
		||||
 | 
			
		||||
        // Creaking
 | 
			
		||||
        register(new BooleanProperty("creaking_active", 17, false, legacyBooleans));
 | 
			
		||||
 | 
			
		||||
        if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21_4)) return;
 | 
			
		||||
 | 
			
		||||
        // Creaking
 | 
			
		||||
        register(new BooleanProperty("creaking_crumbling", 18, false, legacyBooleans));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void registerSerializer(PropertySerializer<?> serializer) {
 | 
			
		||||
| 
						 | 
				
			
			@ -790,8 +721,8 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void registerDummy(String name, Class<?> type, boolean playerModifiable) {
 | 
			
		||||
        register(new DummyProperty<>(name, type, playerModifiable));
 | 
			
		||||
    public void registerDummy(String name, Class<?> type) {
 | 
			
		||||
        register(new DummyProperty<>(name, type));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public EntityPropertyImpl<?> getByName(String name) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,34 +8,27 @@ import lol.pyr.znpcsplus.api.entity.EntityProperty;
 | 
			
		|||
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
 | 
			
		||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		||||
import lol.pyr.znpcsplus.reflection.Reflections;
 | 
			
		||||
import lol.pyr.znpcsplus.util.FutureUtil;
 | 
			
		||||
import lol.pyr.znpcsplus.util.NpcLocation;
 | 
			
		||||
import lol.pyr.znpcsplus.util.Viewable;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
import org.bukkit.inventory.ItemStack;
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
public class PacketEntity implements PropertyHolder {
 | 
			
		||||
    private final PacketFactory packetFactory;
 | 
			
		||||
 | 
			
		||||
    private final PropertyHolder properties;
 | 
			
		||||
    private final Viewable viewable;
 | 
			
		||||
    private final int entityId;
 | 
			
		||||
    private final UUID uuid;
 | 
			
		||||
 | 
			
		||||
    private final EntityType type;
 | 
			
		||||
    private NpcLocation location;
 | 
			
		||||
 | 
			
		||||
    private PacketEntity vehicle;
 | 
			
		||||
    private Integer vehicleId;
 | 
			
		||||
    private List<Integer> passengers;
 | 
			
		||||
 | 
			
		||||
    public PacketEntity(PacketFactory packetFactory, PropertyHolder properties, Viewable viewable, EntityType type, NpcLocation location) {
 | 
			
		||||
    public PacketEntity(PacketFactory packetFactory, PropertyHolder properties, EntityType type, NpcLocation location) {
 | 
			
		||||
        this.packetFactory = packetFactory;
 | 
			
		||||
        this.properties = properties;
 | 
			
		||||
        this.viewable = viewable;
 | 
			
		||||
        this.entityId = reserveEntityID();
 | 
			
		||||
        this.uuid = UUID.randomUUID();
 | 
			
		||||
        this.type = type;
 | 
			
		||||
| 
						 | 
				
			
			@ -58,120 +51,22 @@ public class PacketEntity implements PropertyHolder {
 | 
			
		|||
        return type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setLocation(NpcLocation location) {
 | 
			
		||||
    public void setLocation(NpcLocation location, Collection<Player> viewers) {
 | 
			
		||||
        this.location = location;
 | 
			
		||||
        if (vehicle != null) {
 | 
			
		||||
            vehicle.setLocation(location.withY(location.getY() - 0.9));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        for (Player viewer : viewable.getViewers()) packetFactory.teleportEntity(viewer, this);
 | 
			
		||||
        for (Player viewer : viewers) packetFactory.teleportEntity(viewer, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public CompletableFuture<Void> spawn(Player player) {
 | 
			
		||||
        return FutureUtil.exceptionPrintingRunAsync(() -> {
 | 
			
		||||
            if (type == EntityTypes.PLAYER) packetFactory.spawnPlayer(player, this, properties).join();
 | 
			
		||||
    public void spawn(Player player) {
 | 
			
		||||
        if (type == EntityTypes.PLAYER) packetFactory.spawnPlayer(player, this, properties);
 | 
			
		||||
        else packetFactory.spawnEntity(player, this, properties);
 | 
			
		||||
            if (vehicle != null) {
 | 
			
		||||
                setVehicle(vehicle);
 | 
			
		||||
            }
 | 
			
		||||
            if (vehicleId != null) {
 | 
			
		||||
                packetFactory.setPassengers(player, vehicleId, this.getEntityId());
 | 
			
		||||
            }
 | 
			
		||||
            if (passengers != null) {
 | 
			
		||||
                packetFactory.setPassengers(player, this.getEntityId(), passengers.stream().mapToInt(Integer::intValue).toArray());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHeadRotation(Player player, float yaw, float pitch) {
 | 
			
		||||
        packetFactory.sendHeadRotation(player, this, yaw, pitch);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PacketEntity getVehicle() {
 | 
			
		||||
        return vehicle;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Viewable getViewable() {
 | 
			
		||||
        return viewable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setVehicleId(Integer vehicleId) {
 | 
			
		||||
        if (this.vehicle != null) {
 | 
			
		||||
            for (Player player : viewable.getViewers()) {
 | 
			
		||||
                packetFactory.setPassengers(player, this.vehicle.getEntityId());
 | 
			
		||||
                this.vehicle.despawn(player);
 | 
			
		||||
                packetFactory.teleportEntity(player, this);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (this.vehicleId != null) {
 | 
			
		||||
            for (Player player : viewable.getViewers()) {
 | 
			
		||||
                packetFactory.setPassengers(player, this.vehicleId);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        this.vehicleId = vehicleId;
 | 
			
		||||
        if (vehicleId == null) return;
 | 
			
		||||
 | 
			
		||||
        for (Player player : viewable.getViewers()) {
 | 
			
		||||
            packetFactory.setPassengers(player, this.getEntityId(), vehicleId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setVehicle(PacketEntity vehicle) {
 | 
			
		||||
        // remove old vehicle
 | 
			
		||||
        if (this.vehicle != null) {
 | 
			
		||||
            for (Player player : viewable.getViewers()) {
 | 
			
		||||
                packetFactory.setPassengers(player, this.vehicle.getEntityId());
 | 
			
		||||
                this.vehicle.despawn(player);
 | 
			
		||||
                packetFactory.teleportEntity(player, this);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (this.vehicleId != null) {
 | 
			
		||||
            for (Player player : viewable.getViewers()) {
 | 
			
		||||
                packetFactory.setPassengers(player, this.vehicleId);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.vehicle = vehicle;
 | 
			
		||||
        if (this.vehicle == null) return;
 | 
			
		||||
 | 
			
		||||
        vehicle.setLocation(location.withY(location.getY() - 0.9));
 | 
			
		||||
        for (Player player : viewable.getViewers()) {
 | 
			
		||||
            vehicle.spawn(player).thenRun(() -> {
 | 
			
		||||
                packetFactory.setPassengers(player, vehicle.getEntityId(), this.getEntityId());
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Integer getVehicleId() {
 | 
			
		||||
        return vehicleId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<Integer> getPassengers() {
 | 
			
		||||
        return passengers == null ? Collections.emptyList() : passengers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void addPassenger(int entityId) {
 | 
			
		||||
        if (passengers == null) {
 | 
			
		||||
            passengers = new ArrayList<>();
 | 
			
		||||
        }
 | 
			
		||||
        passengers.add(entityId);
 | 
			
		||||
        for (Player player : viewable.getViewers()) {
 | 
			
		||||
            packetFactory.setPassengers(player, this.getEntityId(), passengers.stream().mapToInt(Integer::intValue).toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void removePassenger(int entityId) {
 | 
			
		||||
        if (passengers == null) return;
 | 
			
		||||
        passengers.remove(entityId);
 | 
			
		||||
        for (Player player : viewable.getViewers()) {
 | 
			
		||||
            packetFactory.setPassengers(player, this.getEntityId(), passengers.stream().mapToInt(Integer::intValue).toArray());
 | 
			
		||||
        }
 | 
			
		||||
        if (passengers.isEmpty()) {
 | 
			
		||||
            passengers = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void despawn(Player player) {
 | 
			
		||||
        packetFactory.destroyEntity(player, this, properties);
 | 
			
		||||
        if (vehicle != null) vehicle.despawn(player);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void refreshMeta(Player player) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,22 +31,13 @@ public class BitsetProperty extends EntityPropertyImpl<Boolean> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
        EntityData<?> oldData = properties.get(index);
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        EntityData oldData = properties.get(index);
 | 
			
		||||
        boolean enabled = entity.getProperty(this);
 | 
			
		||||
        if (inverted) enabled = !enabled;
 | 
			
		||||
        if (integer) {
 | 
			
		||||
            int oldValue = 0;
 | 
			
		||||
            if (oldData != null && oldData.getValue() instanceof Number) {
 | 
			
		||||
                oldValue = ((Number) oldData.getValue()).intValue();
 | 
			
		||||
            }
 | 
			
		||||
            properties.put(index, newEntityData(index, EntityDataTypes.INT, oldValue | (enabled ? bitmask : 0)));
 | 
			
		||||
        } else {
 | 
			
		||||
            byte oldValue = 0;
 | 
			
		||||
            if (oldData != null && oldData.getValue() instanceof Number) {
 | 
			
		||||
                oldValue = ((Number) oldData.getValue()).byteValue();
 | 
			
		||||
            }
 | 
			
		||||
            properties.put(index, newEntityData(index, EntityDataTypes.BYTE, (byte) (oldValue | (enabled ? bitmask : 0))));
 | 
			
		||||
        }
 | 
			
		||||
        properties.put(index,
 | 
			
		||||
                integer ? newEntityData(index, EntityDataTypes.INT, (oldData == null ? 0 : (int) oldData.getValue()) | (enabled ? bitmask : 0)) :
 | 
			
		||||
                newEntityData(index, EntityDataTypes.BYTE, (byte) ((oldData == null ? 0 : (byte) oldData.getValue()) | (enabled ? bitmask : 0))));
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ public class BooleanProperty extends EntityPropertyImpl<Boolean> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        boolean enabled = entity.getProperty(this);
 | 
			
		||||
        if (inverted) enabled = !enabled;
 | 
			
		||||
        if (legacy) properties.put(index, newEntityData(index, EntityDataTypes.BYTE, (byte) (enabled ? 1 : 0)));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ public class CamelSittingProperty extends EntityPropertyImpl<Boolean> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        boolean value = entity.getProperty(this);
 | 
			
		||||
        if (value) {
 | 
			
		||||
            properties.put(poseIndex, newEntityData(poseIndex, EntityDataTypes.ENTITY_POSE, EntityPose.SITTING));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,7 @@ public class CustomTypeProperty<T, U> extends EntityPropertyImpl<T> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        properties.put(index, newEntityData(index, type, decoder.decode(entity.getProperty(this))));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package lol.pyr.znpcsplus.entity.properties;
 | 
			
		||||
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataType;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
 | 
			
		||||
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
 | 
			
		||||
| 
						 | 
				
			
			@ -15,21 +16,20 @@ import java.util.Optional;
 | 
			
		|||
public class DinnerboneProperty extends EntityPropertyImpl<Boolean> {
 | 
			
		||||
    private final boolean optional;
 | 
			
		||||
    private final Object serialized;
 | 
			
		||||
    private final EntityDataType<?> type;
 | 
			
		||||
 | 
			
		||||
    public DinnerboneProperty(boolean legacy, boolean optional) {
 | 
			
		||||
        super("dinnerbone", false, Boolean.class);
 | 
			
		||||
        this.optional = optional;
 | 
			
		||||
        Component name = Component.text("Dinnerbone");
 | 
			
		||||
        this.serialized = legacy ? AdventureSerializer.serializer().legacy().serialize(name) :
 | 
			
		||||
        Object serialized = legacy ? AdventureSerializer.getLegacyGsonSerializer().serialize(name) :
 | 
			
		||||
                optional ? name : LegacyComponentSerializer.legacySection().serialize(name);
 | 
			
		||||
        this.serialized = optional ? Optional.of(serialized) : serialized;
 | 
			
		||||
        this.type = optional ? EntityDataTypes.OPTIONAL_ADV_COMPONENT : EntityDataTypes.STRING;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
        if (optional) {
 | 
			
		||||
            properties.put(2, new EntityData<>(2, EntityDataTypes.OPTIONAL_ADV_COMPONENT, entity.getProperty(this) ? Optional.of((Component) serialized) : Optional.empty()));
 | 
			
		||||
        } else {
 | 
			
		||||
            properties.put(2, new EntityData<>(2, EntityDataTypes.STRING, entity.getProperty(this) ? (String) serialized : ""));
 | 
			
		||||
        }
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        properties.put(2, new EntityData(2, type, entity.getProperty(this) ? serialized : optional ? null : ""));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,6 @@ public class DummyProperty<T> extends EntityPropertyImpl<T> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ public class EncodedByteProperty<T> extends EntityPropertyImpl<T> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        T value = entity.getProperty(this);
 | 
			
		||||
        if (value == null) return;
 | 
			
		||||
        properties.put(index, newEntityData(index, type, decoder.decode(value)));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ public class EncodedIntegerProperty<T> extends EntityPropertyImpl<T> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        T value = entity.getProperty(this);
 | 
			
		||||
        if (value == null) return;
 | 
			
		||||
        properties.put(index, newEntityData(index, type, decoder.decode(value)));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ public class EncodedStringProperty<T> extends EntityPropertyImpl<T> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        T value = entity.getProperty(this);
 | 
			
		||||
        if (value == null) return;
 | 
			
		||||
        properties.put(index, newEntityData(index, type, decoder.decode(value)));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,37 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.entity.properties;
 | 
			
		||||
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.ArmorStandVehicleProperties;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.PacketEntity;
 | 
			
		||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public class EntitySittingProperty extends EntityPropertyImpl<Boolean> {
 | 
			
		||||
    private final PacketFactory packetFactory;
 | 
			
		||||
    private final EntityPropertyRegistryImpl propertyRegistry;
 | 
			
		||||
 | 
			
		||||
    public EntitySittingProperty(PacketFactory packetFactory, EntityPropertyRegistryImpl propertyRegistry) {
 | 
			
		||||
        super("entity_sitting", false, Boolean.class);
 | 
			
		||||
        this.packetFactory = packetFactory;
 | 
			
		||||
        this.propertyRegistry = propertyRegistry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
        boolean sitting = entity.getProperty(this);
 | 
			
		||||
        if (sitting) {
 | 
			
		||||
            if (entity.getVehicle() == null) {
 | 
			
		||||
                PacketEntity vehiclePacketEntity = new PacketEntity(packetFactory, new ArmorStandVehicleProperties(propertyRegistry),
 | 
			
		||||
                        entity.getViewable(), EntityTypes.ARMOR_STAND, entity.getLocation().withY(entity.getLocation().getY() - 0.9));
 | 
			
		||||
                entity.setVehicle(vehiclePacketEntity);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (entity.getVehicle() != null) {
 | 
			
		||||
            entity.setVehicle(null);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -22,7 +22,7 @@ public class EquipmentProperty extends EntityPropertyImpl<ItemStack> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        packetFactory.sendEquipment(player, entity, new Equipment(slot, entity.getProperty(this)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package lol.pyr.znpcsplus.entity.properties;
 | 
			
		||||
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
 | 
			
		||||
import lol.pyr.znpcsplus.ZNpcsPlusBootstrap;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.PacketEntity;
 | 
			
		||||
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
| 
						 | 
				
			
			@ -8,15 +9,17 @@ import org.bukkit.entity.Player;
 | 
			
		|||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public class ForceBodyRotationProperty extends DummyProperty<Boolean> {
 | 
			
		||||
    private final ZNpcsPlusBootstrap plugin;
 | 
			
		||||
    private final TaskScheduler scheduler;
 | 
			
		||||
 | 
			
		||||
    public ForceBodyRotationProperty(TaskScheduler scheduler) {
 | 
			
		||||
    public ForceBodyRotationProperty(ZNpcsPlusBootstrap plugin, TaskScheduler scheduler) {
 | 
			
		||||
        super("force_body_rotation", false);
 | 
			
		||||
        this.plugin = plugin;
 | 
			
		||||
        this.scheduler = scheduler;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        if (entity.getProperty(this)) {
 | 
			
		||||
            scheduler.runLaterAsync(() -> entity.swingHand(player, false), 2L);
 | 
			
		||||
            scheduler.runLaterAsync(() -> entity.swingHand(player, false), 6L);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,19 +19,12 @@ public class GlowProperty extends EntityPropertyImpl<NamedColor> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        NamedColor value = entity.getProperty(this);
 | 
			
		||||
        EntityData<?> oldData = properties.get(0);
 | 
			
		||||
//        byte oldValue = oldData == null ? 0 : (byte) oldData.getValue();
 | 
			
		||||
        byte oldValue = 0;
 | 
			
		||||
        if (oldData != null && oldData.getValue() instanceof Number) {
 | 
			
		||||
            oldValue = ((Number) oldData.getValue()).byteValue();
 | 
			
		||||
        }
 | 
			
		||||
        EntityData oldData = properties.get(0);
 | 
			
		||||
        byte oldValue = oldData == null ? 0 : (byte) oldData.getValue();
 | 
			
		||||
        properties.put(0, newEntityData(0, EntityDataTypes.BYTE, (byte) (oldValue | (value == null ? 0 : 0x40))));
 | 
			
		||||
        // the team is already created with the right glow color in the packet factory if the npc isnt spawned yet
 | 
			
		||||
        if (isSpawned) {
 | 
			
		||||
            packetFactory.removeTeam(player, entity);
 | 
			
		||||
        if (isSpawned) packetFactory.removeTeam(player, entity);
 | 
			
		||||
        packetFactory.createTeam(player, entity, value);
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,27 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.entity.properties;
 | 
			
		||||
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.PacketEntity;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public class HealthProperty extends EntityPropertyImpl<Float> {
 | 
			
		||||
    private final int index;
 | 
			
		||||
 | 
			
		||||
    public HealthProperty(int index) {
 | 
			
		||||
        super("health", 20f, Float.class);
 | 
			
		||||
        this.index = index;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
        float health = entity.getProperty(this);
 | 
			
		||||
        health = (float) Attributes.MAX_HEALTH.sanitizeValue(health);
 | 
			
		||||
        properties.put(index, new EntityData<>(index, EntityDataTypes.FLOAT, health));
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ public class HologramItemProperty extends EntityPropertyImpl<ItemStack> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        properties.put(8, newEntityData(8, EntityDataTypes.ITEMSTACK, entity.getProperty(this)));
 | 
			
		||||
        properties.put(5, newEntityData(5, EntityDataTypes.BOOLEAN, true));
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,13 +18,9 @@ public class HorseColorProperty extends EntityPropertyImpl<HorseColor> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
        EntityData<?> oldData = properties.get(index);
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        EntityData oldData = properties.get(index);
 | 
			
		||||
        HorseColor value = entity.getProperty(this);
 | 
			
		||||
        int oldValue = (oldData != null && oldData.getValue() instanceof Integer) ? (Integer) oldData.getValue() : 0;
 | 
			
		||||
 | 
			
		||||
        int newValue = value.ordinal() | (oldValue & 0xFF00);
 | 
			
		||||
        properties.put(index, newEntityData(index, EntityDataTypes.INT, newValue));
 | 
			
		||||
 | 
			
		||||
        properties.put(index, newEntityData(index, EntityDataTypes.INT, value.ordinal() | (oldData == null ? 0 : ((int) oldData.getValue() & 0xFF00))));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,13 +18,9 @@ public class HorseStyleProperty extends EntityPropertyImpl<HorseStyle> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
        EntityData<?> oldData = properties.get(index);
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        EntityData oldData = properties.get(index);
 | 
			
		||||
        HorseStyle value = entity.getProperty(this);
 | 
			
		||||
 | 
			
		||||
        int oldValue = (oldData != null && oldData.getValue() instanceof Integer) ? (Integer) oldData.getValue() : 0;
 | 
			
		||||
        int newValue = (oldValue & 0x00FF) | (value.ordinal() << 8);
 | 
			
		||||
        properties.put(index, newEntityData(index, EntityDataTypes.INT, newValue));
 | 
			
		||||
 | 
			
		||||
        properties.put(index, newEntityData(index, EntityDataTypes.INT, (oldData == null ? 0 : ((int) oldData.getValue() & 0x00FF)) | (value.ordinal() << 8)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,7 +23,7 @@ public class IntegerProperty extends EntityPropertyImpl<Integer> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        properties.put(index, legacy ?
 | 
			
		||||
                newEntityData(index, EntityDataTypes.BYTE, (byte) entity.getProperty(this).intValue()) :
 | 
			
		||||
                newEntityData(index, EntityDataTypes.INT, entity.getProperty(this)));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,29 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.entity.properties;
 | 
			
		||||
 | 
			
		||||
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.type.EntityTypes;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.PacketEntity;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public class LegacyBabyProperty extends EntityPropertyImpl<Boolean> {
 | 
			
		||||
    private final int index;
 | 
			
		||||
 | 
			
		||||
    public LegacyBabyProperty(int index) {
 | 
			
		||||
        super("baby", false, Boolean.class);
 | 
			
		||||
        this.index = index;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
        boolean isBaby = entity.getProperty(this);
 | 
			
		||||
        if (entity.getType().equals(EntityTypes.ZOMBIE)) {
 | 
			
		||||
            properties.put(index, newEntityData(index, EntityDataTypes.BYTE, (byte) (isBaby ? 1 : 0)));
 | 
			
		||||
        } else {
 | 
			
		||||
            properties.put(index, newEntityData(index, EntityDataTypes.BYTE, (byte) (isBaby ? -1 : 0)));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +48,7 @@ public class NBTProperty<T> extends EntityPropertyImpl<T> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        T value = entity.getProperty(this);
 | 
			
		||||
        if (value == null && !allowNull) return;
 | 
			
		||||
        properties.put(index, newEntityData(index, type, decoder.decode(value)));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,17 +27,14 @@ public class NameProperty extends EntityPropertyImpl<Component> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        Component value = entity.getProperty(this);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
            value = PapiUtil.set(legacySerializer, player, value);
 | 
			
		||||
            if (legacySerialization) {
 | 
			
		||||
                properties.put(2, newEntityData(2, EntityDataTypes.STRING, AdventureSerializer.serializer().asJson(value)));
 | 
			
		||||
            } else if (optional) {
 | 
			
		||||
                properties.put(2, newEntityData(2, EntityDataTypes.OPTIONAL_ADV_COMPONENT, Optional.of(value)));
 | 
			
		||||
            } else {
 | 
			
		||||
                properties.put(2, newEntityData(2, EntityDataTypes.STRING, LegacyComponentSerializer.legacySection().serialize(value)));
 | 
			
		||||
            }
 | 
			
		||||
            Object serialized = legacySerialization ? AdventureSerializer.getLegacyGsonSerializer().serialize(value) :
 | 
			
		||||
                    optional ? value : LegacyComponentSerializer.legacySection().serialize(value);
 | 
			
		||||
            if (optional) properties.put(2, new EntityData(2, EntityDataTypes.OPTIONAL_ADV_COMPONENT, Optional.of(serialized)));
 | 
			
		||||
            else properties.put(2, new EntityData(2, EntityDataTypes.STRING, serialized));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (legacySerialization) properties.put(3, newEntityData(3, EntityDataTypes.BYTE, (byte) (value != null ? 1 : 0)));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,10 +19,10 @@ public class OptionalBlockPosProperty extends EntityPropertyImpl<Vector3i> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        Vector3i value = entity.getProperty(this);
 | 
			
		||||
        if (value == null) properties.put(index, new EntityData<>(index, EntityDataTypes.OPTIONAL_BLOCK_POSITION, Optional.empty()));
 | 
			
		||||
        else properties.put(index, new EntityData<>(index, EntityDataTypes.OPTIONAL_BLOCK_POSITION,
 | 
			
		||||
        if (value == null) properties.put(index, new EntityData(index, EntityDataTypes.OPTIONAL_BLOCK_POSITION, Optional.empty()));
 | 
			
		||||
        else properties.put(index, new EntityData(index, EntityDataTypes.OPTIONAL_BLOCK_POSITION,
 | 
			
		||||
                Optional.of(new com.github.retrooper.packetevents.util.Vector3i(value.getX(), value.getY(), value.getZ()))));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package lol.pyr.znpcsplus.entity.properties;
 | 
			
		||||
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataType;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
 | 
			
		||||
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
 | 
			
		||||
| 
						 | 
				
			
			@ -16,38 +17,31 @@ import java.util.Optional;
 | 
			
		|||
public class RabbitTypeProperty extends EntityPropertyImpl<RabbitType> {
 | 
			
		||||
    private final int index;
 | 
			
		||||
    private final boolean legacyBooleans;
 | 
			
		||||
    private final boolean optional;
 | 
			
		||||
    private final Object serialized;
 | 
			
		||||
    private final EntityDataType<?> type;
 | 
			
		||||
 | 
			
		||||
    public RabbitTypeProperty(int index, boolean legacyBooleans, boolean legacyNames, boolean optional) {
 | 
			
		||||
        super("rabbit_type", RabbitType.BROWN, RabbitType.class);
 | 
			
		||||
        this.index = index;
 | 
			
		||||
        this.legacyBooleans = legacyBooleans;
 | 
			
		||||
        this.optional = optional;
 | 
			
		||||
        Component name = Component.text("Toast");
 | 
			
		||||
        this.serialized = legacyNames ? AdventureSerializer.serializer().legacy().serialize(name) :
 | 
			
		||||
        Object serialized = legacyNames ? AdventureSerializer.getLegacyGsonSerializer().serialize(name) :
 | 
			
		||||
                optional ? name : LegacyComponentSerializer.legacySection().serialize(name);
 | 
			
		||||
        this.serialized = optional ? Optional.of(serialized) : serialized;
 | 
			
		||||
        this.type = optional ? EntityDataTypes.OPTIONAL_ADV_COMPONENT : EntityDataTypes.STRING;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        RabbitType rabbitType = entity.getProperty(this);
 | 
			
		||||
        if (rabbitType == null) return;
 | 
			
		||||
        if (!rabbitType.equals(RabbitType.TOAST)) {
 | 
			
		||||
            properties.put(index, legacyBooleans ?
 | 
			
		||||
                    newEntityData(index, EntityDataTypes.BYTE, (byte) rabbitType.getId()) :
 | 
			
		||||
                    newEntityData(index, EntityDataTypes.INT, rabbitType.getId()));
 | 
			
		||||
            if (optional) {
 | 
			
		||||
                properties.put(2, new EntityData<>(2, EntityDataTypes.OPTIONAL_ADV_COMPONENT, Optional.empty()));
 | 
			
		||||
            properties.put(2, new EntityData(2, type, null));
 | 
			
		||||
        } else {
 | 
			
		||||
                properties.put(2, new EntityData<>(2, EntityDataTypes.STRING, ""));
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (optional) {
 | 
			
		||||
                properties.put(2, newEntityData(2, EntityDataTypes.OPTIONAL_ADV_COMPONENT, Optional.of((Component) serialized)));
 | 
			
		||||
            } else {
 | 
			
		||||
                properties.put(2, newEntityData(2, EntityDataTypes.STRING, (String) serialized));
 | 
			
		||||
            }
 | 
			
		||||
            properties.put(2, new EntityData(2, type, serialized));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ public class RotationProperty extends EntityPropertyImpl<Vector3f> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        Vector3f vec = entity.getProperty(this);
 | 
			
		||||
        properties.put(index, newEntityData(index, EntityDataTypes.ROTATION, new com.github.retrooper.packetevents.util.Vector3f(vec.getX(), vec.getY(), vec.getZ())));
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ public class TargetNpcProperty extends EntityPropertyImpl<NpcEntryImpl> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        NpcEntryImpl value = entity.getProperty(this);
 | 
			
		||||
        if (value == null) return;
 | 
			
		||||
        if (value.getNpc().getEntity().getEntityId() == entity.getEntityId()) return;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,14 +25,15 @@ public class TropicalFishVariantProperty<T> extends EntityPropertyImpl<T> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        T value = entity.getProperty(this);
 | 
			
		||||
        if (value == null) return;
 | 
			
		||||
 | 
			
		||||
        EntityData<?> oldData = properties.get(index);
 | 
			
		||||
        if (value == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        EntityData oldData = properties.get(index);
 | 
			
		||||
        TropicalFishVariant.Builder builder;
 | 
			
		||||
        if (oldData != null && oldData.getType() == EntityDataTypes.INT && oldData.getValue() instanceof Integer) {
 | 
			
		||||
            int oldVal = (Integer) oldData.getValue();
 | 
			
		||||
        if (oldData != null && oldData.getType() == EntityDataTypes.INT && oldData.getValue() != null) {
 | 
			
		||||
            int oldVal = (int) oldData.getValue();
 | 
			
		||||
            builder = TropicalFishVariant.Builder.fromInt(oldVal);
 | 
			
		||||
        } else {
 | 
			
		||||
            builder = new TropicalFishVariant.Builder();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,63 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.entity.properties.attributes;
 | 
			
		||||
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.attribute.Attribute;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
 | 
			
		||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateAttributes;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.PacketEntity;
 | 
			
		||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public class AttributeProperty extends EntityPropertyImpl<Double> {
 | 
			
		||||
    private final PacketFactory packetFactory;
 | 
			
		||||
    private final Attribute attribute;
 | 
			
		||||
 | 
			
		||||
    public AttributeProperty(PacketFactory packetFactory, String name, Attribute attribute) {
 | 
			
		||||
        super(name, attribute.getDefaultValue(), Double.class);
 | 
			
		||||
        this.packetFactory = packetFactory;
 | 
			
		||||
        this.attribute = attribute;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public double getMinValue() {
 | 
			
		||||
        return attribute.getMinValue();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public double getMaxValue() {
 | 
			
		||||
        return attribute.getMaxValue();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public double sanitizeValue(double value) {
 | 
			
		||||
        return attribute.sanitizeValue(value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<EntityData<?>> applyStandalone(Player player, PacketEntity packetEntity, boolean isSpawned) {
 | 
			
		||||
        apply(player, packetEntity, isSpawned, Collections.emptyList());
 | 
			
		||||
        return Collections.emptyList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, List<WrapperPlayServerUpdateAttributes.Property> properties) {
 | 
			
		||||
        Double value = entity.getProperty(this);
 | 
			
		||||
        if (value == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        value = attribute.sanitizeValue(value);
 | 
			
		||||
        if (isSpawned) {
 | 
			
		||||
            packetFactory.sendAttribute(player, entity, new WrapperPlayServerUpdateAttributes.Property(attribute, value, Collections.emptyList()));
 | 
			
		||||
        } else {
 | 
			
		||||
            properties.add(new WrapperPlayServerUpdateAttributes.Property(attribute, value, Collections.emptyList()));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Attribute getAttribute() {
 | 
			
		||||
        return attribute;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -21,8 +21,8 @@ public abstract class VillagerDataProperty<T> extends EntityPropertyImpl<T> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
 | 
			
		||||
        EntityData<?> oldData = properties.get(index);
 | 
			
		||||
    public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
 | 
			
		||||
        EntityData oldData = properties.get(index);
 | 
			
		||||
        VillagerData old = oldData == null ? new VillagerData(VillagerTypes.PLAINS, VillagerProfessions.NONE, 1) : (VillagerData) oldData.getValue();
 | 
			
		||||
        properties.put(index, newEntityData(index, EntityDataTypes.VILLAGER_DATA, apply(old, entity.getProperty(this))));
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,6 @@ import lol.pyr.znpcsplus.api.hologram.Hologram;
 | 
			
		|||
import lol.pyr.znpcsplus.config.ConfigManager;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		||||
import lol.pyr.znpcsplus.util.FutureUtil;
 | 
			
		||||
import lol.pyr.znpcsplus.util.NpcLocation;
 | 
			
		||||
import lol.pyr.znpcsplus.util.Viewable;
 | 
			
		||||
import net.kyori.adventure.text.Component;
 | 
			
		||||
| 
						 | 
				
			
			@ -17,8 +16,6 @@ import org.bukkit.entity.Player;
 | 
			
		|||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
public class HologramImpl extends Viewable implements Hologram {
 | 
			
		||||
    private final ConfigManager configManager;
 | 
			
		||||
| 
						 | 
				
			
			@ -41,15 +38,14 @@ public class HologramImpl extends Viewable implements Hologram {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public void addTextLineComponent(Component line) {
 | 
			
		||||
        HologramText newLine = new HologramText(this, propertyRegistry, packetFactory, null, line);
 | 
			
		||||
        HologramText newLine = new HologramText(propertyRegistry, packetFactory, null, line);
 | 
			
		||||
        lines.add(newLine);
 | 
			
		||||
        relocateLines();
 | 
			
		||||
        relocateLines(newLine);
 | 
			
		||||
        for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void addTextLine(String line) {
 | 
			
		||||
        Component component = line.contains("§") ? Component.text(line) : MiniMessage.miniMessage().deserialize(line);
 | 
			
		||||
        addTextLineComponent(textSerializer.deserialize(textSerializer.serialize(component)));
 | 
			
		||||
        addTextLineComponent(textSerializer.deserialize(textSerializer.serialize(MiniMessage.miniMessage().deserialize(line))));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void addItemLineStack(org.bukkit.inventory.ItemStack item) {
 | 
			
		||||
| 
						 | 
				
			
			@ -61,9 +57,9 @@ public class HologramImpl extends Viewable implements Hologram {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public void addItemLinePEStack(ItemStack item) {
 | 
			
		||||
        HologramItem newLine = new HologramItem(this, propertyRegistry, packetFactory, null, item);
 | 
			
		||||
        HologramItem newLine = new HologramItem(propertyRegistry, packetFactory, null, item);
 | 
			
		||||
        lines.add(newLine);
 | 
			
		||||
        relocateLines();
 | 
			
		||||
        relocateLines(newLine);
 | 
			
		||||
        for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -109,9 +105,9 @@ public class HologramImpl extends Viewable implements Hologram {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public void insertTextLineComponent(int index, Component line) {
 | 
			
		||||
        HologramText newLine = new HologramText(this, propertyRegistry, packetFactory, null, line);
 | 
			
		||||
        HologramText newLine = new HologramText(propertyRegistry, packetFactory, null, line);
 | 
			
		||||
        lines.add(index, newLine);
 | 
			
		||||
        relocateLines();
 | 
			
		||||
        relocateLines(newLine);
 | 
			
		||||
        for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -124,9 +120,9 @@ public class HologramImpl extends Viewable implements Hologram {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public void insertItemLinePEStack(int index, ItemStack item) {
 | 
			
		||||
        HologramItem newLine = new HologramItem(this, propertyRegistry, packetFactory, null, item);
 | 
			
		||||
        HologramItem newLine = new HologramItem(propertyRegistry, packetFactory, null, item);
 | 
			
		||||
        lines.add(index, newLine);
 | 
			
		||||
        relocateLines();
 | 
			
		||||
        relocateLines(newLine);
 | 
			
		||||
        for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -148,10 +144,8 @@ public class HologramImpl extends Viewable implements Hologram {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected CompletableFuture<Void> UNSAFE_show(Player player) {
 | 
			
		||||
        return FutureUtil.allOf(lines.stream()
 | 
			
		||||
                .map(line -> line.show(player))
 | 
			
		||||
                .collect(Collectors.toList()));
 | 
			
		||||
    protected void UNSAFE_show(Player player) {
 | 
			
		||||
        for (HologramLine<?> line : lines) line.show(player);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -184,10 +178,14 @@ public class HologramImpl extends Viewable implements Hologram {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    private void relocateLines() {
 | 
			
		||||
        relocateLines(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void relocateLines(HologramLine<?> newLine) {
 | 
			
		||||
        final double lineSpacing = configManager.getConfig().lineSpacing();
 | 
			
		||||
        double height = location.getY() + (lines.size() - 1) * lineSpacing + getOffset();
 | 
			
		||||
        for (HologramLine<?> line : lines) {
 | 
			
		||||
            line.setLocation(location.withY(height));
 | 
			
		||||
            line.setLocation(location.withY(height), line == newLine ? Collections.emptySet() : getViewers());
 | 
			
		||||
            height -= lineSpacing;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,11 +15,13 @@ import lol.pyr.znpcsplus.api.entity.EntityProperty;
 | 
			
		|||
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		||||
import lol.pyr.znpcsplus.util.NpcLocation;
 | 
			
		||||
import lol.pyr.znpcsplus.util.Viewable;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
 | 
			
		||||
public class HologramItem extends HologramLine<ItemStack> {
 | 
			
		||||
    public HologramItem(Viewable viewable,  EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, ItemStack item) {
 | 
			
		||||
        super(viewable, item, packetFactory, EntityTypes.ITEM, location);
 | 
			
		||||
    public HologramItem(EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, ItemStack item) {
 | 
			
		||||
        super(item, packetFactory, EntityTypes.ITEM, location);
 | 
			
		||||
        addProperty(propertyRegistry.getByName("holo_item"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -31,8 +33,8 @@ public class HologramItem extends HologramLine<ItemStack> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setLocation(NpcLocation location) {
 | 
			
		||||
        super.setLocation(location.withY(location.getY() + 2.05));
 | 
			
		||||
    public void setLocation(NpcLocation location, Collection<Player> viewers) {
 | 
			
		||||
        super.setLocation(location.withY(location.getY() + 2.05), viewers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean ensureValidItemInput(String in) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,22 +7,21 @@ import lol.pyr.znpcsplus.api.entity.PropertyHolder;
 | 
			
		|||
import lol.pyr.znpcsplus.entity.PacketEntity;
 | 
			
		||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		||||
import lol.pyr.znpcsplus.util.NpcLocation;
 | 
			
		||||
import lol.pyr.znpcsplus.util.Viewable;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
import org.bukkit.inventory.ItemStack;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
public class HologramLine<M> implements PropertyHolder {
 | 
			
		||||
    private M value;
 | 
			
		||||
    private final PacketEntity entity;
 | 
			
		||||
    private final Set<EntityProperty<?>> properties;
 | 
			
		||||
 | 
			
		||||
    public HologramLine(Viewable viewable, M value, PacketFactory packetFactory, EntityType type, NpcLocation location) {
 | 
			
		||||
    public HologramLine(M value, PacketFactory packetFactory, EntityType type, NpcLocation location) {
 | 
			
		||||
        this.value = value;
 | 
			
		||||
        this.entity = new PacketEntity(packetFactory, this, viewable, type, location);
 | 
			
		||||
        this.entity = new PacketEntity(packetFactory, this, type, location);
 | 
			
		||||
        this.properties = new HashSet<>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -38,16 +37,16 @@ public class HologramLine<M> implements PropertyHolder {
 | 
			
		|||
        entity.refreshMeta(player);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected CompletableFuture<Void> show(Player player) {
 | 
			
		||||
        return entity.spawn(player);
 | 
			
		||||
    protected void show(Player player) {
 | 
			
		||||
        entity.spawn(player);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void hide(Player player) {
 | 
			
		||||
        entity.despawn(player);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setLocation(NpcLocation location) {
 | 
			
		||||
        entity.setLocation(location);
 | 
			
		||||
    public void setLocation(NpcLocation location, Collection<Player> viewers) {
 | 
			
		||||
        entity.setLocation(location, viewers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getEntityId() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,26 +5,24 @@ import lol.pyr.znpcsplus.api.entity.EntityProperty;
 | 
			
		|||
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		||||
import lol.pyr.znpcsplus.util.NpcLocation;
 | 
			
		||||
import lol.pyr.znpcsplus.util.Viewable;
 | 
			
		||||
import net.kyori.adventure.text.Component;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
public class HologramText extends HologramLine<Component> {
 | 
			
		||||
 | 
			
		||||
    private static final Component BLANK  = Component.text("%blank%");
 | 
			
		||||
 | 
			
		||||
    public HologramText(Viewable viewable,  EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, Component text) {
 | 
			
		||||
        super(viewable, text, packetFactory, EntityTypes.ARMOR_STAND, location);
 | 
			
		||||
    public HologramText(EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, Component text) {
 | 
			
		||||
        super(text, packetFactory, EntityTypes.ARMOR_STAND, location);
 | 
			
		||||
        addProperty(propertyRegistry.getByName("name"));
 | 
			
		||||
        addProperty(propertyRegistry.getByName("invisible"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public CompletableFuture<Void> show(Player player) {
 | 
			
		||||
        if (getValue().equals(BLANK)) return CompletableFuture.completedFuture(null);
 | 
			
		||||
        return super.show(player);
 | 
			
		||||
    public void show(Player player) {
 | 
			
		||||
        if (!getValue().equals(BLANK)) {
 | 
			
		||||
            super.show(player);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,7 @@ public class SwitchServerAction extends InteractionActionImpl {
 | 
			
		|||
                        .hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT,
 | 
			
		||||
                                Component.text("Click to edit this action", NamedTextColor.GRAY)))
 | 
			
		||||
                        .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND,
 | 
			
		||||
                                "/" + context.getLabel() + " action edit " + id + " " + index + " switchserver " + getInteractionType().name() + " " + getCooldown()/1000 + " " + getDelay() + " " + server))
 | 
			
		||||
                                "/" + context.getLabel() + " action edit " + id + " " + index + " switcserver " + getInteractionType().name() + " " + getCooldown()/1000 + " " + getDelay() + " " + server))
 | 
			
		||||
                .append(Component.text(" | ", NamedTextColor.GRAY))
 | 
			
		||||
                .append(Component.text("[DELETE]", NamedTextColor.RED)
 | 
			
		||||
                        .hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,12 +20,9 @@ import org.bukkit.Location;
 | 
			
		|||
import org.bukkit.World;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
import org.bukkit.inventory.ItemStack;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
public class NpcImpl extends Viewable implements Npc {
 | 
			
		||||
| 
						 | 
				
			
			@ -41,8 +38,6 @@ public class NpcImpl extends Viewable implements Npc {
 | 
			
		|||
    private final Map<EntityPropertyImpl<?>, Object> propertyMap = new HashMap<>();
 | 
			
		||||
    private final List<InteractionAction> actions = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    private final Map<UUID, float[]> playerLookMap = new ConcurrentHashMap<>();
 | 
			
		||||
 | 
			
		||||
    protected NpcImpl(UUID uuid, EntityPropertyRegistryImpl propertyRegistry, ConfigManager configManager, LegacyComponentSerializer textSerializer, World world, NpcTypeImpl type, NpcLocation location, PacketFactory packetFactory) {
 | 
			
		||||
        this(uuid, propertyRegistry, configManager, packetFactory, textSerializer, world.getName(), type, location);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -53,14 +48,14 @@ public class NpcImpl extends Viewable implements Npc {
 | 
			
		|||
        this.type = type;
 | 
			
		||||
        this.location = location;
 | 
			
		||||
        this.uuid = uuid;
 | 
			
		||||
        entity = new PacketEntity(packetFactory, this, this, type.getType(), location);
 | 
			
		||||
        entity = new PacketEntity(packetFactory, this, type.getType(), location);
 | 
			
		||||
        hologram = new HologramImpl(propertyRegistry, configManager, packetFactory, textSerializer, location.withY(location.getY() + type.getHologramOffset()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setType(NpcTypeImpl type) {
 | 
			
		||||
        UNSAFE_hideAll();
 | 
			
		||||
        this.type = type;
 | 
			
		||||
        entity = new PacketEntity(packetFactory, this, this, type.getType(), entity.getLocation());
 | 
			
		||||
        entity = new PacketEntity(packetFactory, this, type.getType(), entity.getLocation());
 | 
			
		||||
        hologram.setLocation(location.withY(location.getY() + type.getHologramOffset()));
 | 
			
		||||
        UNSAFE_showAll();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -90,34 +85,20 @@ public class NpcImpl extends Viewable implements Npc {
 | 
			
		|||
 | 
			
		||||
    public void setLocation(NpcLocation location) {
 | 
			
		||||
        this.location = location;
 | 
			
		||||
        playerLookMap.clear();
 | 
			
		||||
        playerLookMap.putAll(getViewers().stream().collect(Collectors.toMap(Player::getUniqueId, player -> new float[]{location.getYaw(), location.getPitch()})));
 | 
			
		||||
        entity.setLocation(location);
 | 
			
		||||
        entity.setLocation(location, getViewers());
 | 
			
		||||
        hologram.setLocation(location.withY(location.getY() + type.getHologramOffset()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHeadRotation(Player player, float yaw, float pitch) {
 | 
			
		||||
        if (getHeadYaw(player) == yaw && getHeadPitch(player) == pitch) return;
 | 
			
		||||
        playerLookMap.put(player.getUniqueId(), new float[]{yaw, pitch});
 | 
			
		||||
        entity.setHeadRotation(player, yaw, pitch);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHeadRotation(float yaw, float pitch) {
 | 
			
		||||
        for (Player player : getViewers()) {
 | 
			
		||||
            if (getHeadYaw(player) == yaw && getHeadPitch(player) == pitch) continue;
 | 
			
		||||
            playerLookMap.put(player.getUniqueId(), new float[]{yaw, pitch});
 | 
			
		||||
            entity.setHeadRotation(player, yaw, pitch);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public float getHeadYaw(Player player) {
 | 
			
		||||
        return playerLookMap.getOrDefault(player.getUniqueId(), new float[]{location.getYaw(), location.getPitch()})[0];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public float getHeadPitch(Player player) {
 | 
			
		||||
        return playerLookMap.getOrDefault(player.getUniqueId(), new float[]{location.getYaw(), location.getPitch()})[1];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public HologramImpl getHologram() {
 | 
			
		||||
        return hologram;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -144,14 +125,13 @@ public class NpcImpl extends Viewable implements Npc {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected CompletableFuture<Void> UNSAFE_show(Player player) {
 | 
			
		||||
        playerLookMap.put(player.getUniqueId(), new float[]{location.getYaw(), location.getPitch()});
 | 
			
		||||
        return CompletableFuture.allOf(entity.spawn(player), hologram.show(player));
 | 
			
		||||
    protected void UNSAFE_show(Player player) {
 | 
			
		||||
        entity.spawn(player);
 | 
			
		||||
        hologram.show(player);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void UNSAFE_hide(Player player) {
 | 
			
		||||
        playerLookMap.remove(player.getUniqueId());
 | 
			
		||||
        entity.despawn(player);
 | 
			
		||||
        hologram.hide(player);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -159,7 +139,7 @@ public class NpcImpl extends Viewable implements Npc {
 | 
			
		|||
    private <T> void UNSAFE_refreshProperty(EntityPropertyImpl<T> property) {
 | 
			
		||||
        if (!type.isAllowedProperty(property)) return;
 | 
			
		||||
        for (Player viewer : getViewers()) {
 | 
			
		||||
            List<EntityData<?>> data = property.applyStandalone(viewer, entity, true);
 | 
			
		||||
            List<EntityData> data = property.applyStandalone(viewer, entity, true);
 | 
			
		||||
            if (!data.isEmpty()) packetFactory.sendMetadata(viewer, entity, data);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -232,8 +212,7 @@ public class NpcImpl extends Viewable implements Npc {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void addAction(InteractionAction action) throws IllegalArgumentException {
 | 
			
		||||
        if (action == null) throw new IllegalArgumentException("action can not be null");
 | 
			
		||||
    public void addAction(InteractionAction action) {
 | 
			
		||||
        actions.add(action);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -243,8 +222,7 @@ public class NpcImpl extends Viewable implements Npc {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void editAction(int index, InteractionAction action) throws IllegalArgumentException {
 | 
			
		||||
        if (action == null) throw new IllegalArgumentException("action can not be null");
 | 
			
		||||
    public void editAction(int index, InteractionAction action) {
 | 
			
		||||
        actions.set(index, action);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -268,29 +246,4 @@ public class NpcImpl extends Viewable implements Npc {
 | 
			
		|||
    public void swingHand(boolean offHand) {
 | 
			
		||||
        for (Player viewer : getViewers()) entity.swingHand(viewer, offHand);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public @NotNull List<Integer> getPassengers() {
 | 
			
		||||
        return entity.getPassengers();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void addPassenger(int entityId) {
 | 
			
		||||
        entity.addPassenger(entityId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void removePassenger(int entityId) {
 | 
			
		||||
        entity.removePassenger(entityId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public @Nullable Integer getVehicleId() {
 | 
			
		||||
        return entity.getVehicleId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setVehicleId(Integer vehicleId) {
 | 
			
		||||
        entity.setVehicleId(vehicleId);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,6 @@ import lol.pyr.znpcsplus.hologram.HologramText;
 | 
			
		|||
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		||||
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
 | 
			
		||||
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.storage.NpcStorage;
 | 
			
		||||
import lol.pyr.znpcsplus.storage.NpcStorageType;
 | 
			
		||||
import lol.pyr.znpcsplus.util.NpcLocation;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,13 +35,13 @@ public class NpcRegistryImpl implements NpcRegistry {
 | 
			
		|||
    private final Map<String, NpcEntryImpl> npcIdLookupMap = new HashMap<>();
 | 
			
		||||
    private final Map<UUID, NpcEntryImpl> npcUuidLookupMap = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
    public NpcRegistryImpl(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, TaskScheduler scheduler, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, NpcSerializerRegistryImpl serializerRegistry, LegacyComponentSerializer textSerializer) {
 | 
			
		||||
    public NpcRegistryImpl(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, TaskScheduler scheduler, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
 | 
			
		||||
        this.textSerializer = textSerializer;
 | 
			
		||||
        this.propertyRegistry = propertyRegistry;
 | 
			
		||||
        storage = configManager.getConfig().storageType().create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, serializerRegistry);
 | 
			
		||||
        storage = configManager.getConfig().storageType().create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
 | 
			
		||||
        if (storage == null) {
 | 
			
		||||
            Bukkit.getLogger().warning("Failed to initialize storage, falling back to YAML");
 | 
			
		||||
            storage = NpcStorageType.YAML.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, serializerRegistry);
 | 
			
		||||
            storage = NpcStorageType.YAML.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
 | 
			
		||||
        }
 | 
			
		||||
        this.packetFactory = packetFactory;
 | 
			
		||||
        this.configManager = configManager;
 | 
			
		||||
| 
						 | 
				
			
			@ -53,13 +52,7 @@ public class NpcRegistryImpl implements NpcRegistry {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void register(NpcEntry entry) {
 | 
			
		||||
        register((NpcEntryImpl) entry);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void register(NpcEntryImpl entry) {
 | 
			
		||||
        if (entry == null) throw new NullPointerException();
 | 
			
		||||
        unregister(npcIdLookupMap.put(entry.getId(), entry));
 | 
			
		||||
        unregister(npcUuidLookupMap.put(entry.getNpc().getUuid(), entry));
 | 
			
		||||
        npcList.add(entry);
 | 
			
		||||
| 
						 | 
				
			
			@ -202,14 +195,6 @@ public class NpcRegistryImpl implements NpcRegistry {
 | 
			
		|||
        storage.deleteNpc(entry);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void delete(UUID uuid) {
 | 
			
		||||
        NpcEntryImpl entry = npcUuidLookupMap.get(uuid);
 | 
			
		||||
        if (entry == null) return;
 | 
			
		||||
        unregister(entry);
 | 
			
		||||
        storage.deleteNpc(entry);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void switchIds(String oldId, String newId) {
 | 
			
		||||
        NpcEntryImpl entry = getById(oldId);
 | 
			
		||||
        delete(oldId);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -114,15 +114,12 @@ public class NpcTypeImpl implements NpcType {
 | 
			
		|||
 | 
			
		||||
        public NpcTypeImpl build() {
 | 
			
		||||
            ServerVersion version = PacketEvents.getAPI().getServerManager().getVersion();
 | 
			
		||||
            addProperties("fire", "invisible", "silent", "look", "look_distance", "look_return", "view_distance",
 | 
			
		||||
            addProperties("fire", "invisible", "silent", "look", "look_distance", "view_distance",
 | 
			
		||||
                    "potion_color", "potion_ambient", "display_name", "permission_required",
 | 
			
		||||
                    "player_knockback", "player_knockback_exempt_permission", "player_knockback_distance", "player_knockback_vertical",
 | 
			
		||||
                    "player_knockback_horizontal", "player_knockback_cooldown", "player_knockback_sound", "player_knockback_sound_name",
 | 
			
		||||
                    "player_knockback_sound_volume", "player_knockback_sound_pitch");
 | 
			
		||||
            if (!type.equals(EntityTypes.PLAYER)) addProperties("dinnerbone");
 | 
			
		||||
            if (EntityTypes.isTypeInstanceOf(type, EntityTypes.LIVINGENTITY)) {
 | 
			
		||||
                addProperties("health", "attribute_max_health");
 | 
			
		||||
            }
 | 
			
		||||
            // TODO: make this look nicer after completing the rest of the properties
 | 
			
		||||
            if (version.isNewerThanOrEquals(ServerVersion.V_1_9)) addProperties("glow");
 | 
			
		||||
            if (version.isNewerThanOrEquals(ServerVersion.V_1_14)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -146,9 +143,6 @@ public class NpcTypeImpl implements NpcType {
 | 
			
		|||
            } else if (version.isOlderThan(ServerVersion.V_1_11) && type.equals(EntityTypes.HORSE)) {
 | 
			
		||||
                addProperties("has_chest");
 | 
			
		||||
            }
 | 
			
		||||
            if (version.isOlderThan(ServerVersion.V_1_11) && EntityTypes.isTypeInstanceOf(type, EntityTypes.SKELETON)) {
 | 
			
		||||
                addProperties("skeleton_type");
 | 
			
		||||
            }
 | 
			
		||||
            if (EntityTypes.isTypeInstanceOf(type, EntityTypes.ABSTRACT_EVO_ILLU_ILLAGER)) {
 | 
			
		||||
                addProperties("spell");
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -180,35 +174,6 @@ public class NpcTypeImpl implements NpcType {
 | 
			
		|||
            if (version.isNewerThanOrEquals(ServerVersion.V_1_20_5)) {
 | 
			
		||||
                if (EntityTypes.isTypeInstanceOf(type, EntityTypes.WOLF)) {
 | 
			
		||||
                    addProperties("wolf_variant");
 | 
			
		||||
                    if (version.isNewerThanOrEquals(ServerVersion.V_1_21)) {
 | 
			
		||||
                        addProperties("body");
 | 
			
		||||
                    } else {
 | 
			
		||||
                        addProperties("chestplate");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (version.isNewerThanOrEquals(ServerVersion.V_1_21_4)) {
 | 
			
		||||
                if (EntityTypes.isTypeInstanceOf(type, EntityTypes.CREAKING)) {
 | 
			
		||||
                    addProperties("creaking_crumbling");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (EntityTypes.isTypeInstanceOf(type, EntityTypes.ZOMBIE)) {
 | 
			
		||||
                if (version.isOlderThan(ServerVersion.V_1_9)) {
 | 
			
		||||
                    addProperties("zombie_is_villager");
 | 
			
		||||
                } else if (version.isOlderThan(ServerVersion.V_1_11)) {
 | 
			
		||||
                    addProperties("zombie_type");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (version.isOlderThan(ServerVersion.V_1_11)) {
 | 
			
		||||
                    addProperties("is_converting");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (version.isNewerThanOrEquals(ServerVersion.V_1_9) && version.isOlderThan(ServerVersion.V_1_14)) {
 | 
			
		||||
                    addProperties("zombie_hands_held_up");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (version.isNewerThanOrEquals(ServerVersion.V_1_13)) {
 | 
			
		||||
                    addProperties("zombie_becoming_drowned");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return new NpcTypeImpl(name, type, hologramOffset, new HashSet<>(allowedProperties), defaultProperties);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
			
		|||
        register(builder(p, "player", EntityTypes.PLAYER)
 | 
			
		||||
                .setHologramOffset(-0.15D)
 | 
			
		||||
                .addEquipmentProperties()
 | 
			
		||||
                .addProperties("skin_cape", "skin_jacket", "skin_left_sleeve", "skin_right_sleeve", "skin_left_leg", "skin_right_leg", "skin_hat", "shoulder_entity_left", "shoulder_entity_right", "force_body_rotation", "entity_sitting")
 | 
			
		||||
                .addProperties("skin_cape", "skin_jacket", "skin_left_sleeve", "skin_right_sleeve", "skin_left_leg", "skin_right_leg", "skin_hat", "shoulder_entity_left", "shoulder_entity_right", "force_body_rotation")
 | 
			
		||||
                .addDefaultProperty("skin_cape", true)
 | 
			
		||||
                .addDefaultProperty("skin_jacket", true)
 | 
			
		||||
                .addDefaultProperty("skin_left_sleeve", true)
 | 
			
		||||
| 
						 | 
				
			
			@ -82,7 +82,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
			
		|||
 | 
			
		||||
        register(builder(p, "enderman", EntityTypes.ENDERMAN)
 | 
			
		||||
                .setHologramOffset(0.925)
 | 
			
		||||
                .addProperties("enderman_held_block", "enderman_screaming", "enderman_staring", "entity_sitting"));
 | 
			
		||||
                .addProperties("enderman_held_block", "enderman_screaming", "enderman_staring"));
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "endermite", EntityTypes.ENDERMITE)
 | 
			
		||||
                .setHologramOffset(-1.675));
 | 
			
		||||
| 
						 | 
				
			
			@ -93,8 +93,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
			
		|||
 | 
			
		||||
        register(builder(p, "giant", EntityTypes.GIANT)
 | 
			
		||||
                .setHologramOffset(10.025)
 | 
			
		||||
                .addEquipmentProperties()
 | 
			
		||||
                .addProperties("entity_sitting"));
 | 
			
		||||
                .addEquipmentProperties());
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "guardian", EntityTypes.GUARDIAN)
 | 
			
		||||
                .setHologramOffset(-1.125)
 | 
			
		||||
| 
						 | 
				
			
			@ -134,8 +133,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
			
		|||
 | 
			
		||||
        register(builder(p, "skeleton", EntityTypes.SKELETON)
 | 
			
		||||
                .setHologramOffset(0.015)
 | 
			
		||||
                .addEquipmentProperties()
 | 
			
		||||
                .addProperties("entity_sitting"));
 | 
			
		||||
                .addEquipmentProperties());
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "skeleton_horse", EntityTypes.SKELETON_HORSE)
 | 
			
		||||
                .setHologramOffset(-0.375));
 | 
			
		||||
| 
						 | 
				
			
			@ -171,16 +169,14 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
			
		|||
 | 
			
		||||
        register(builder(p, "zombie", EntityTypes.ZOMBIE)
 | 
			
		||||
                .setHologramOffset(-0.025)
 | 
			
		||||
                .addEquipmentProperties()
 | 
			
		||||
                .addProperties("entity_sitting"));
 | 
			
		||||
                .addEquipmentProperties());
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "zombie_horse", EntityTypes.ZOMBIE_HORSE)
 | 
			
		||||
                .setHologramOffset(-0.375));
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "zombified_piglin", EntityTypes.ZOMBIFIED_PIGLIN)
 | 
			
		||||
                .setHologramOffset(-0.025)
 | 
			
		||||
                .addEquipmentProperties()
 | 
			
		||||
                .addProperties("entity_sitting"));
 | 
			
		||||
                .addEquipmentProperties());
 | 
			
		||||
 | 
			
		||||
        if (!version.isNewerThanOrEquals(ServerVersion.V_1_9)) return;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -207,21 +203,21 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
			
		|||
 | 
			
		||||
        register(builder(p, "husk", EntityTypes.HUSK)
 | 
			
		||||
                .setHologramOffset(-0.025)
 | 
			
		||||
                .addEquipmentProperties()
 | 
			
		||||
                .addProperties("entity_sitting"));
 | 
			
		||||
                .addEquipmentProperties());
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "polar_bear", EntityTypes.POLAR_BEAR)
 | 
			
		||||
                .setHologramOffset(-0.575));
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "stray", EntityTypes.STRAY)
 | 
			
		||||
                .setHologramOffset(0.015)
 | 
			
		||||
                .addEquipmentProperties()
 | 
			
		||||
                .addProperties("entity_sitting"));
 | 
			
		||||
                .addEquipmentProperties());
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "evoker", EntityTypes.EVOKER)
 | 
			
		||||
                .setHologramOffset(-0.025)
 | 
			
		||||
                .addProperties("entity_sitting"));
 | 
			
		||||
                .setHologramOffset(-0.025));
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "llama", EntityTypes.LLAMA)
 | 
			
		||||
                .setHologramOffset(-0.105)
 | 
			
		||||
                .addProperties("carpet_color", "llama_variant", "body"));
 | 
			
		||||
                .addProperties("carpet_color", "llama_variant"));
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "vex", EntityTypes.VEX)
 | 
			
		||||
                .setHologramOffset(-1.175)
 | 
			
		||||
| 
						 | 
				
			
			@ -229,23 +225,20 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
			
		|||
 | 
			
		||||
        register(builder(p, "vindicator", EntityTypes.VINDICATOR)
 | 
			
		||||
                .setHologramOffset(-0.025)
 | 
			
		||||
                .addProperties("celebrating", "entity_sitting"));
 | 
			
		||||
                .addProperties("celebrating"));
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "wither_skeleton", EntityTypes.WITHER_SKELETON)
 | 
			
		||||
                .setHologramOffset(0.425)
 | 
			
		||||
                .addEquipmentProperties()
 | 
			
		||||
                .addProperties("entity_sitting"));
 | 
			
		||||
                .addEquipmentProperties());
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "zombie_villager", EntityTypes.ZOMBIE_VILLAGER)
 | 
			
		||||
                .setHologramOffset(-0.025)
 | 
			
		||||
                .addEquipmentProperties()
 | 
			
		||||
                .addProperties("entity_sitting"));
 | 
			
		||||
                .addEquipmentProperties());
 | 
			
		||||
 | 
			
		||||
        if (!version.isNewerThanOrEquals(ServerVersion.V_1_12)) return;
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "illusioner", EntityTypes.ILLUSIONER)
 | 
			
		||||
                .setHologramOffset(-0.025)
 | 
			
		||||
                .addProperties("entity_sitting"));
 | 
			
		||||
                .setHologramOffset(-0.025));
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "parrot", EntityTypes.PARROT)
 | 
			
		||||
                .setHologramOffset(-1.075)
 | 
			
		||||
| 
						 | 
				
			
			@ -262,8 +255,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
			
		|||
 | 
			
		||||
        register(builder(p, "drowned", EntityTypes.DROWNED)
 | 
			
		||||
                .setHologramOffset(-0.025)
 | 
			
		||||
                .addEquipmentProperties()
 | 
			
		||||
                .addProperties("entity_sitting"));
 | 
			
		||||
                .addEquipmentProperties());
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "phantom", EntityTypes.PHANTOM)
 | 
			
		||||
                .setHologramOffset(-1.475));
 | 
			
		||||
| 
						 | 
				
			
			@ -299,14 +291,14 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
			
		|||
        register(builder(p, "pillager", EntityTypes.PILLAGER)
 | 
			
		||||
                .setHologramOffset(-0.025)
 | 
			
		||||
                .addHandProperties()
 | 
			
		||||
                .addProperties("pillager_charging", "entity_sitting"));
 | 
			
		||||
                .addProperties("pillager_charging"));
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "ravager", EntityTypes.RAVAGER)
 | 
			
		||||
                .setHologramOffset(0.225));
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "trader_llama", EntityTypes.TRADER_LLAMA)
 | 
			
		||||
                .setHologramOffset(-0.105)
 | 
			
		||||
                .addProperties("carpet_color", "llama_variant", "body"));
 | 
			
		||||
                .addProperties("llama_variant"));
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "wandering_trader", EntityTypes.WANDERING_TRADER)
 | 
			
		||||
                .setHologramOffset(-0.025)
 | 
			
		||||
| 
						 | 
				
			
			@ -327,12 +319,11 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
			
		|||
        register(builder(p, "piglin", EntityTypes.PIGLIN)
 | 
			
		||||
                .setHologramOffset(-0.025)
 | 
			
		||||
                .addEquipmentProperties()
 | 
			
		||||
                .addProperties("piglin_baby", "piglin_charging_crossbow", "piglin_dancing", "entity_sitting"));
 | 
			
		||||
                .addProperties("piglin_baby", "piglin_charging_crossbow", "piglin_dancing"));
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "piglin_brute", EntityTypes.PIGLIN_BRUTE)
 | 
			
		||||
                .setHologramOffset(-0.025)
 | 
			
		||||
                .addEquipmentProperties()
 | 
			
		||||
                .addProperties("entity_sitting"));
 | 
			
		||||
                .addEquipmentProperties());
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "strider", EntityTypes.STRIDER)
 | 
			
		||||
                .setHologramOffset(-0.275)
 | 
			
		||||
| 
						 | 
				
			
			@ -392,16 +383,10 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
 | 
			
		|||
 | 
			
		||||
        register(builder(p, "bogged", EntityTypes.BOGGED)
 | 
			
		||||
                .setHologramOffset(0.015)
 | 
			
		||||
                .addProperties("bogged_sheared", "entity_sitting"));
 | 
			
		||||
                .addProperties("bogged_sheared"));
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "breeze", EntityTypes.BREEZE)
 | 
			
		||||
                .setHologramOffset(-0.205));
 | 
			
		||||
 | 
			
		||||
        if (!version.isNewerThanOrEquals(ServerVersion.V_1_21_2)) return;
 | 
			
		||||
 | 
			
		||||
        register(builder(p, "creaking", EntityTypes.CREAKING)
 | 
			
		||||
                .setHologramOffset(0.725)
 | 
			
		||||
                .addProperties("creaking_active"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Collection<NpcType> getAll() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,6 @@ package lol.pyr.znpcsplus.packets;
 | 
			
		|||
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.player.Equipment;
 | 
			
		||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateAttributes;
 | 
			
		||||
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.PacketEntity;
 | 
			
		||||
import lol.pyr.znpcsplus.util.NamedColor;
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +11,7 @@ import java.util.List;
 | 
			
		|||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
public interface PacketFactory {
 | 
			
		||||
    CompletableFuture<Void> spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties);
 | 
			
		||||
    void spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties);
 | 
			
		||||
    void spawnEntity(Player player, PacketEntity entity, PropertyHolder properties);
 | 
			
		||||
    void destroyEntity(Player player, PacketEntity entity, PropertyHolder properties);
 | 
			
		||||
    void teleportEntity(Player player, PacketEntity entity);
 | 
			
		||||
| 
						 | 
				
			
			@ -22,10 +21,7 @@ public interface PacketFactory {
 | 
			
		|||
    void removeTeam(Player player, PacketEntity entity);
 | 
			
		||||
    void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties);
 | 
			
		||||
    void sendEquipment(Player player, PacketEntity entity, Equipment equipment);
 | 
			
		||||
    void sendMetadata(Player player, PacketEntity entity, List<EntityData<?>> data);
 | 
			
		||||
    void sendMetadata(Player player, PacketEntity entity, List<EntityData> data);
 | 
			
		||||
    void sendHeadRotation(Player player, PacketEntity entity, float yaw, float pitch);
 | 
			
		||||
    void sendHandSwing(Player player, PacketEntity entity, boolean offHand);
 | 
			
		||||
    void setPassengers(Player player, int vehicle, int... passengers);
 | 
			
		||||
    void sendAllAttributes(Player player, PacketEntity entity, PropertyHolder properties);
 | 
			
		||||
    void sendAttribute(Player player, PacketEntity entity, WrapperPlayServerUpdateAttributes.Property property);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
package lol.pyr.znpcsplus.packets;
 | 
			
		||||
 | 
			
		||||
import com.github.retrooper.packetevents.PacketEventsAPI;
 | 
			
		||||
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
 | 
			
		||||
import com.github.retrooper.packetevents.util.Vector3d;
 | 
			
		||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSpawnEntity;
 | 
			
		||||
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +27,6 @@ public class V1_17PacketFactory extends V1_8PacketFactory {
 | 
			
		|||
        sendPacket(player, new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(),
 | 
			
		||||
                npcLocationToVector(location), location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.of(new Vector3d())));
 | 
			
		||||
        sendAllMetadata(player, entity, properties);
 | 
			
		||||
        if (EntityTypes.isTypeInstanceOf(entity.getType(), EntityTypes.LIVINGENTITY)) sendAllAttributes(player, entity, properties);
 | 
			
		||||
        createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,6 @@ import org.bukkit.entity.Player;
 | 
			
		|||
import org.bukkit.plugin.Plugin;
 | 
			
		||||
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
public class V1_20_2PacketFactory extends V1_19_3PacketFactory {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,15 +27,14 @@ public class V1_20_2PacketFactory extends V1_19_3PacketFactory {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public CompletableFuture<Void> spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties) {
 | 
			
		||||
        return addTabPlayer(player, entity, properties).thenAccept(ignored -> {
 | 
			
		||||
    public void spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties) {
 | 
			
		||||
        addTabPlayer(player, entity, properties).thenAccept(ignored -> {
 | 
			
		||||
            createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class)));
 | 
			
		||||
            NpcLocation location = entity.getLocation();
 | 
			
		||||
            sendPacket(player, new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(),
 | 
			
		||||
                    npcLocationToVector(location), location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.of(new Vector3d())));
 | 
			
		||||
            sendPacket(player, new WrapperPlayServerEntityHeadLook(entity.getEntityId(), location.getYaw()));
 | 
			
		||||
            sendAllMetadata(player, entity, properties);
 | 
			
		||||
            sendAllAttributes(player, entity, properties);
 | 
			
		||||
            scheduler.runLaterAsync(() -> removeTabPlayer(player, entity), configManager.getConfig().tabHideDelay());
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,6 @@ import lol.pyr.znpcsplus.config.ConfigManager;
 | 
			
		|||
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.PacketEntity;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.properties.attributes.AttributeProperty;
 | 
			
		||||
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.util.NamedColor;
 | 
			
		||||
| 
						 | 
				
			
			@ -48,15 +47,14 @@ public class V1_8PacketFactory implements PacketFactory {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public CompletableFuture<Void> spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties) {
 | 
			
		||||
        return addTabPlayer(player, entity, properties).thenAccept(ignored -> {
 | 
			
		||||
    public void spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties) {
 | 
			
		||||
        addTabPlayer(player, entity, properties).thenAccept(ignored -> {
 | 
			
		||||
            createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class)));
 | 
			
		||||
            NpcLocation location = entity.getLocation();
 | 
			
		||||
            sendPacket(player, new WrapperPlayServerSpawnPlayer(entity.getEntityId(),
 | 
			
		||||
                    entity.getUuid(), npcLocationToVector(location), location.getYaw(), location.getPitch(), Collections.emptyList()));
 | 
			
		||||
            sendPacket(player, new WrapperPlayServerEntityHeadLook(entity.getEntityId(), location.getYaw()));
 | 
			
		||||
            sendAllMetadata(player, entity, properties);
 | 
			
		||||
            sendAllAttributes(player, entity, properties);
 | 
			
		||||
            scheduler.runLaterAsync(() -> removeTabPlayer(player, entity), configManager.getConfig().tabHideDelay());
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +70,6 @@ public class V1_8PacketFactory implements PacketFactory {
 | 
			
		|||
                new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(), npcLocationToVector(location),
 | 
			
		||||
                        location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.empty()));
 | 
			
		||||
        sendAllMetadata(player, entity, properties);
 | 
			
		||||
        if (EntityTypes.isTypeInstanceOf(type, EntityTypes.LIVINGENTITY)) sendAllAttributes(player, entity, properties);
 | 
			
		||||
        createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -135,13 +132,13 @@ public class V1_8PacketFactory implements PacketFactory {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties) {
 | 
			
		||||
        Map<Integer, EntityData<?>> datas = new HashMap<>();
 | 
			
		||||
        Map<Integer, EntityData> datas = new HashMap<>();
 | 
			
		||||
        for (EntityProperty<?> property : properties.getAppliedProperties()) ((EntityPropertyImpl<?>) property).apply(player, entity, false, datas);
 | 
			
		||||
        sendMetadata(player, entity, new ArrayList<>(datas.values()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void sendMetadata(Player player, PacketEntity entity, List<EntityData<?>> data) {
 | 
			
		||||
    public void sendMetadata(Player player, PacketEntity entity, List<EntityData> data) {
 | 
			
		||||
        sendPacket(player, new WrapperPlayServerEntityMetadata(entity.getEntityId(), data));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -156,11 +153,6 @@ public class V1_8PacketFactory implements PacketFactory {
 | 
			
		|||
        sendPacket(player, new WrapperPlayServerEntityEquipment(entity.getEntityId(), Collections.singletonList(equipment)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setPassengers(Player player, int vehicleEntityId, int... passengers) {
 | 
			
		||||
        sendPacket(player, new WrapperPlayServerSetPassengers(vehicleEntityId, passengers));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void sendPacket(Player player, PacketWrapper<?> packet) {
 | 
			
		||||
        packetEvents.getPlayerManager().sendPacket(player, packet);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -180,7 +172,7 @@ public class V1_8PacketFactory implements PacketFactory {
 | 
			
		|||
        return future;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void add(Map<Integer, EntityData<?>> map, EntityData<?> data) {
 | 
			
		||||
    protected void add(Map<Integer, EntityData> map, EntityData data) {
 | 
			
		||||
        map.put(data.getIndex(), data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -190,19 +182,4 @@ public class V1_8PacketFactory implements PacketFactory {
 | 
			
		|||
                WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_OFF_HAND :
 | 
			
		||||
                WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_MAIN_ARM));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void sendAllAttributes(Player player, PacketEntity entity, PropertyHolder properties) {
 | 
			
		||||
        List<WrapperPlayServerUpdateAttributes.Property> attributesList = new ArrayList<>();
 | 
			
		||||
        properties.getAppliedProperties()
 | 
			
		||||
                .stream()
 | 
			
		||||
                .filter(property -> property instanceof AttributeProperty)
 | 
			
		||||
                .forEach(property -> ((AttributeProperty) property).apply(player, entity, false, attributesList));
 | 
			
		||||
        sendPacket(player, new WrapperPlayServerUpdateAttributes(entity.getEntityId(), attributesList));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void sendAttribute(Player player, PacketEntity entity, WrapperPlayServerUpdateAttributes.Property property) {
 | 
			
		||||
        sendPacket(player, new WrapperPlayServerUpdateAttributes(entity.getEntityId(), Collections.singletonList(property)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,33 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.serialization;
 | 
			
		||||
 | 
			
		||||
import lol.pyr.znpcsplus.api.serialization.NpcSerializer;
 | 
			
		||||
import lol.pyr.znpcsplus.api.serialization.NpcSerializerRegistry;
 | 
			
		||||
import lol.pyr.znpcsplus.config.ConfigManager;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
 | 
			
		||||
import org.bukkit.configuration.file.YamlConfiguration;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public class NpcSerializerRegistryImpl implements NpcSerializerRegistry {
 | 
			
		||||
    private final Map<Class<?>, NpcSerializer<?>> serializerMap = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
    public NpcSerializerRegistryImpl(PacketFactory packetFactory, ConfigManager configManager, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
 | 
			
		||||
        registerSerializer(YamlConfiguration.class, new YamlSerializer(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> NpcSerializer<T> getSerializer(Class<T> clazz) {
 | 
			
		||||
        return (NpcSerializer<T>) serializerMap.get(clazz);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> void registerSerializer(Class<T> clazz, NpcSerializer<T> serializer) {
 | 
			
		||||
        serializerMap.put(clazz, serializer);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,154 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.serialization;
 | 
			
		||||
 | 
			
		||||
import lol.pyr.znpcsplus.api.entity.EntityProperty;
 | 
			
		||||
import lol.pyr.znpcsplus.api.npc.NpcEntry;
 | 
			
		||||
import lol.pyr.znpcsplus.api.serialization.NpcSerializer;
 | 
			
		||||
import lol.pyr.znpcsplus.config.ConfigManager;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.PropertySerializer;
 | 
			
		||||
import lol.pyr.znpcsplus.hologram.HologramImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		||||
import lol.pyr.znpcsplus.util.NpcLocation;
 | 
			
		||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.configuration.ConfigurationSection;
 | 
			
		||||
import org.bukkit.configuration.file.YamlConfiguration;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
import java.util.logging.Logger;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
public class YamlSerializer implements NpcSerializer<YamlConfiguration> {
 | 
			
		||||
    private final static Logger logger = Logger.getLogger("YamlSerializer");
 | 
			
		||||
 | 
			
		||||
    private final PacketFactory packetFactory;
 | 
			
		||||
    private final ConfigManager configManager;
 | 
			
		||||
    private final ActionRegistryImpl actionRegistry;
 | 
			
		||||
    private final NpcTypeRegistryImpl typeRegistry;
 | 
			
		||||
    private final EntityPropertyRegistryImpl propertyRegistry;
 | 
			
		||||
    private final LegacyComponentSerializer textSerializer;
 | 
			
		||||
 | 
			
		||||
    public YamlSerializer(PacketFactory packetFactory, ConfigManager configManager, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
 | 
			
		||||
        this.packetFactory = packetFactory;
 | 
			
		||||
        this.configManager = configManager;
 | 
			
		||||
        this.actionRegistry = actionRegistry;
 | 
			
		||||
        this.typeRegistry = typeRegistry;
 | 
			
		||||
        this.propertyRegistry = propertyRegistry;
 | 
			
		||||
        this.textSerializer = textSerializer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public YamlConfiguration serialize(NpcEntry entry) {
 | 
			
		||||
        YamlConfiguration config = new YamlConfiguration();
 | 
			
		||||
        config.set("id", entry.getId());
 | 
			
		||||
        config.set("is-processed", entry.isProcessed());
 | 
			
		||||
        config.set("allow-commands", entry.isAllowCommandModification());
 | 
			
		||||
        config.set("save", entry.isSave());
 | 
			
		||||
 | 
			
		||||
        NpcImpl npc = (NpcImpl) entry.getNpc();
 | 
			
		||||
        config.set("enabled", npc.isEnabled());
 | 
			
		||||
        config.set("uuid", npc.getUuid().toString());
 | 
			
		||||
        config.set("world", npc.getWorldName());
 | 
			
		||||
        config.set("location", serializeLocation(npc.getLocation()));
 | 
			
		||||
        config.set("type", npc.getType().getName());
 | 
			
		||||
 | 
			
		||||
        for (EntityProperty<?> property : npc.getAllProperties()) try {
 | 
			
		||||
            PropertySerializer<?> serializer = propertyRegistry.getSerializer(((EntityPropertyImpl<?>) property).getType());
 | 
			
		||||
            if (serializer == null) {
 | 
			
		||||
                Bukkit.getLogger().log(Level.WARNING, "Unknown serializer for property '" + property.getName() + "' for npc '" + entry.getId() + "'. skipping ...");
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            config.set("properties." + property.getName(), serializer.UNSAFE_serialize(npc.getProperty(property)));
 | 
			
		||||
        } catch (Exception exception) {
 | 
			
		||||
            logger.severe("Failed to serialize property " + property.getName() + " for npc with id " + entry.getId());
 | 
			
		||||
            exception.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        HologramImpl hologram = npc.getHologram();
 | 
			
		||||
        if (hologram.getOffset() != 0.0) config.set("hologram.offset", hologram.getOffset());
 | 
			
		||||
        if (hologram.getRefreshDelay() != -1) config.set("hologram.refresh-delay", hologram.getRefreshDelay());
 | 
			
		||||
        List<String> lines = new ArrayList<>(npc.getHologram().getLines().size());
 | 
			
		||||
        for (int i = 0; i < hologram.getLines().size(); i++) {
 | 
			
		||||
            lines.add(hologram.getLine(i));
 | 
			
		||||
        }
 | 
			
		||||
        config.set("hologram.lines", lines);
 | 
			
		||||
        config.set("actions", npc.getActions().stream()
 | 
			
		||||
                .map(actionRegistry::serialize)
 | 
			
		||||
                .filter(Objects::nonNull)
 | 
			
		||||
                .collect(Collectors.toList()));
 | 
			
		||||
 | 
			
		||||
        return config;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public NpcEntry deserialize(YamlConfiguration config) {
 | 
			
		||||
        UUID uuid = config.contains("uuid") ? UUID.fromString(config.getString("uuid")) : UUID.randomUUID();
 | 
			
		||||
        NpcImpl npc = new NpcImpl(uuid, propertyRegistry, configManager, packetFactory, textSerializer, config.getString("world"),
 | 
			
		||||
                typeRegistry.getByName(config.getString("type")), deserializeLocation(config.getConfigurationSection("location")));
 | 
			
		||||
 | 
			
		||||
        if (config.isBoolean("enabled")) npc.setEnabled(config.getBoolean("enabled"));
 | 
			
		||||
 | 
			
		||||
        ConfigurationSection properties = config.getConfigurationSection("properties");
 | 
			
		||||
        if (properties != null) {
 | 
			
		||||
            for (String key : properties.getKeys(false)) {
 | 
			
		||||
                EntityPropertyImpl<?> property = propertyRegistry.getByName(key);
 | 
			
		||||
                if (property == null) {
 | 
			
		||||
                    Bukkit.getLogger().log(Level.WARNING, "Unknown property '" + key + "' for npc '" + config.getString("id") + "'. skipping ...");
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                PropertySerializer<?> serializer = propertyRegistry.getSerializer(property.getType());
 | 
			
		||||
                if (serializer == null) {
 | 
			
		||||
                    Bukkit.getLogger().log(Level.WARNING, "Unknown serializer for property '" + key + "' for npc '" + config.getString("id") + "'. skipping ...");
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                Object value = serializer.deserialize(properties.getString(key));
 | 
			
		||||
                if (value == null) {
 | 
			
		||||
                    Bukkit.getLogger().log(Level.WARNING, "Failed to deserialize property '" + key + "' for npc '" + config.getString("id") + "'. Resetting to default ...");
 | 
			
		||||
                    value = property.getDefaultValue();
 | 
			
		||||
                }
 | 
			
		||||
                npc.UNSAFE_setProperty(property, value);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        HologramImpl hologram = npc.getHologram();
 | 
			
		||||
        hologram.setOffset(config.getDouble("hologram.offset", 0.0));
 | 
			
		||||
        hologram.setRefreshDelay(config.getLong("hologram.refresh-delay", -1));
 | 
			
		||||
        for (String line : config.getStringList("hologram.lines")) hologram.addLine(line);
 | 
			
		||||
        for (String s : config.getStringList("actions")) npc.addAction(actionRegistry.deserialize(s));
 | 
			
		||||
 | 
			
		||||
        NpcEntryImpl entry = new NpcEntryImpl(config.getString("id"), npc);
 | 
			
		||||
        entry.setProcessed(config.getBoolean("is-processed"));
 | 
			
		||||
        entry.setAllowCommandModification(config.getBoolean("allow-commands"));
 | 
			
		||||
        entry.setSave(config.getBoolean("save", true));
 | 
			
		||||
 | 
			
		||||
        return entry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public NpcLocation deserializeLocation(ConfigurationSection section) {
 | 
			
		||||
        return new NpcLocation(
 | 
			
		||||
                section.getDouble("x"),
 | 
			
		||||
                section.getDouble("y"),
 | 
			
		||||
                section.getDouble("z"),
 | 
			
		||||
                (float) section.getDouble("yaw"),
 | 
			
		||||
                (float) section.getDouble("pitch")
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public YamlConfiguration serializeLocation(NpcLocation location) {
 | 
			
		||||
        YamlConfiguration config = new YamlConfiguration();
 | 
			
		||||
        config.set("x", location.getX());
 | 
			
		||||
        config.set("y", location.getY());
 | 
			
		||||
        config.set("z", location.getZ());
 | 
			
		||||
        config.set("yaw", location.getYaw());
 | 
			
		||||
        config.set("pitch", location.getPitch());
 | 
			
		||||
        return config;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,16 +3,14 @@ package lol.pyr.znpcsplus.skin;
 | 
			
		|||
import com.github.retrooper.packetevents.protocol.player.TextureProperty;
 | 
			
		||||
import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.NameFetchingDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.UUIDFetchingDescriptor;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
public interface BaseSkinDescriptor extends SkinDescriptor {
 | 
			
		||||
| 
						 | 
				
			
			@ -24,14 +22,7 @@ public interface BaseSkinDescriptor extends SkinDescriptor {
 | 
			
		|||
    static BaseSkinDescriptor deserialize(MojangSkinCache skinCache, String str) {
 | 
			
		||||
        String[] arr = str.split(";");
 | 
			
		||||
        if (arr[0].equalsIgnoreCase("mirror")) return new MirrorDescriptor(skinCache);
 | 
			
		||||
        else if (arr[0].equalsIgnoreCase("fetching-uuid")) {
 | 
			
		||||
            String value = String.join(";", Arrays.copyOfRange(arr, 1, arr.length));
 | 
			
		||||
            return new UUIDFetchingDescriptor(skinCache, UUID.fromString(value));
 | 
			
		||||
        }
 | 
			
		||||
        else if(arr[0].equalsIgnoreCase("fetching")) {
 | 
			
		||||
            String value = String.join(";", Arrays.copyOfRange(arr, 1, arr.length));
 | 
			
		||||
            return new NameFetchingDescriptor(skinCache, value);
 | 
			
		||||
        }
 | 
			
		||||
        else if (arr[0].equalsIgnoreCase("fetching")) return new FetchingDescriptor(skinCache, String.join(";", Arrays.copyOfRange(arr, 1, arr.length)));
 | 
			
		||||
        else if (arr[0].equalsIgnoreCase("prefetched")) {
 | 
			
		||||
            List<TextureProperty> properties = new ArrayList<>();
 | 
			
		||||
            for (int i = 0; i < (arr.length - 1) / 3; i++) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,14 +3,12 @@ package lol.pyr.znpcsplus.skin;
 | 
			
		|||
import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.api.skin.SkinDescriptorFactory;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.NameFetchingDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.descriptor.UUIDFetchingDescriptor;
 | 
			
		||||
 | 
			
		||||
import java.net.MalformedURLException;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory {
 | 
			
		||||
    private final MojangSkinCache skinCache;
 | 
			
		||||
| 
						 | 
				
			
			@ -28,12 +26,7 @@ public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public SkinDescriptor createRefreshingDescriptor(String playerName) {
 | 
			
		||||
        return new NameFetchingDescriptor(skinCache, playerName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public SkinDescriptor createRefreshingDescriptor(UUID playerUUID) {
 | 
			
		||||
        return new UUIDFetchingDescriptor(skinCache, playerUUID);
 | 
			
		||||
        return new FetchingDescriptor(skinCache, playerName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -59,9 +52,4 @@ public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory {
 | 
			
		|||
    public SkinDescriptor createUrlDescriptor(URL url, String variant) {
 | 
			
		||||
        return PrefetchedDescriptor.fromUrl(skinCache, url, variant).join();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public SkinDescriptor createFileDescriptor(String path) {
 | 
			
		||||
        return PrefetchedDescriptor.fromFile(skinCache, path).join();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,6 @@ import com.google.gson.JsonParser;
 | 
			
		|||
import lol.pyr.znpcsplus.config.ConfigManager;
 | 
			
		||||
import lol.pyr.znpcsplus.reflection.Reflections;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.SkinImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.util.FutureUtil;
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +14,6 @@ import java.net.HttpURLConnection;
 | 
			
		|||
import java.net.MalformedURLException;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
| 
						 | 
				
			
			@ -28,12 +26,9 @@ public class MojangSkinCache {
 | 
			
		|||
 | 
			
		||||
    private final Map<String, SkinImpl> cache = new ConcurrentHashMap<>();
 | 
			
		||||
    private final Map<String, CachedId> idCache = new ConcurrentHashMap<>();
 | 
			
		||||
    private final File skinsFolder;
 | 
			
		||||
 | 
			
		||||
    public MojangSkinCache(ConfigManager configManager, File skinsFolder) {
 | 
			
		||||
    public MojangSkinCache(ConfigManager configManager) {
 | 
			
		||||
        this.configManager = configManager;
 | 
			
		||||
        this.skinsFolder = skinsFolder;
 | 
			
		||||
        if (!skinsFolder.exists()) skinsFolder.mkdirs();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void cleanCache() {
 | 
			
		||||
| 
						 | 
				
			
			@ -47,8 +42,8 @@ public class MojangSkinCache {
 | 
			
		|||
 | 
			
		||||
        if (idCache.containsKey(name.toLowerCase())) return fetchByUUID(idCache.get(name.toLowerCase()).getId());
 | 
			
		||||
 | 
			
		||||
        return FutureUtil.exceptionPrintingSupplyAsync(() -> {
 | 
			
		||||
            URL url = parseUrl("https://api.minecraftservices.com/minecraft/profile/lookup/name/" + name);
 | 
			
		||||
        return CompletableFuture.supplyAsync(() -> {
 | 
			
		||||
            URL url = parseUrl("https://api.mojang.com/users/profiles/minecraft/" + name);
 | 
			
		||||
            HttpURLConnection connection = null;
 | 
			
		||||
            try {
 | 
			
		||||
                connection = (HttpURLConnection) url.openConnection();
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +75,7 @@ public class MojangSkinCache {
 | 
			
		|||
 | 
			
		||||
        if (idCache.containsKey(name.toLowerCase())) return fetchByUUID(idCache.get(name.toLowerCase()).getId());
 | 
			
		||||
 | 
			
		||||
        return FutureUtil.exceptionPrintingSupplyAsync(() -> {
 | 
			
		||||
        return CompletableFuture.supplyAsync(() -> {
 | 
			
		||||
            URL url = parseUrl("https://api.ashcon.app/mojang/v2/user/" + name);
 | 
			
		||||
            HttpURLConnection connection = null;
 | 
			
		||||
            try {
 | 
			
		||||
| 
						 | 
				
			
			@ -111,7 +106,7 @@ public class MojangSkinCache {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public CompletableFuture<SkinImpl> fetchByUrl(URL url, String variant) {
 | 
			
		||||
        return FutureUtil.exceptionPrintingSupplyAsync(() -> {
 | 
			
		||||
        return CompletableFuture.supplyAsync(() -> {
 | 
			
		||||
            URL apiUrl = parseUrl("https://api.mineskin.org/generate/url");
 | 
			
		||||
            HttpURLConnection connection = null;
 | 
			
		||||
            try {
 | 
			
		||||
| 
						 | 
				
			
			@ -147,58 +142,6 @@ public class MojangSkinCache {
 | 
			
		|||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public CompletableFuture<SkinImpl> fetchFromFile(String path) throws FileNotFoundException {
 | 
			
		||||
        File file = new File(skinsFolder, path);
 | 
			
		||||
        if (!file.exists()) throw new FileNotFoundException("File not found: " + path);
 | 
			
		||||
        return CompletableFuture.supplyAsync(() -> {
 | 
			
		||||
            URL apiUrl = parseUrl("https://api.mineskin.org/generate/upload");
 | 
			
		||||
            HttpURLConnection connection = null;
 | 
			
		||||
            try {
 | 
			
		||||
                String boundary = "*****";
 | 
			
		||||
                String CRLF = "\r\n";
 | 
			
		||||
 | 
			
		||||
                connection = (HttpURLConnection) apiUrl.openConnection();
 | 
			
		||||
                connection.setRequestMethod("POST");
 | 
			
		||||
                connection.setReadTimeout(10000);
 | 
			
		||||
                connection.setConnectTimeout(15000);
 | 
			
		||||
                connection.setUseCaches(false);
 | 
			
		||||
                connection.setRequestProperty("Cache-Control", "no-cache");
 | 
			
		||||
                connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
 | 
			
		||||
                connection.setDoInput(true);
 | 
			
		||||
                connection.setDoOutput(true);
 | 
			
		||||
                OutputStream outputStream = connection.getOutputStream();
 | 
			
		||||
                DataOutputStream out = new DataOutputStream(outputStream);
 | 
			
		||||
                out.writeBytes("--" + boundary + CRLF);
 | 
			
		||||
                out.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + file.getName() + "\"" + CRLF);
 | 
			
		||||
                out.writeBytes("Content-Type: image/png" + CRLF);
 | 
			
		||||
                out.writeBytes(CRLF);
 | 
			
		||||
                out.write(Files.readAllBytes(file.toPath()));
 | 
			
		||||
                out.writeBytes(CRLF);
 | 
			
		||||
                out.writeBytes("--" + boundary + "--" + CRLF);
 | 
			
		||||
                out.flush();
 | 
			
		||||
                out.close();
 | 
			
		||||
                outputStream.close();
 | 
			
		||||
 | 
			
		||||
                try (Reader reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) {
 | 
			
		||||
                    JsonObject obj = JsonParser.parseReader(reader).getAsJsonObject();
 | 
			
		||||
                    if (obj.has("error")) return null;
 | 
			
		||||
                    if (!obj.has("data")) return null;
 | 
			
		||||
                    JsonObject texture = obj.get("data").getAsJsonObject().get("texture").getAsJsonObject();
 | 
			
		||||
                    return new SkinImpl(texture.get("value").getAsString(), texture.get("signature").getAsString());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            } catch (IOException exception) {
 | 
			
		||||
                if (!configManager.getConfig().disableSkinFetcherWarnings()) {
 | 
			
		||||
                    logger.warning("Failed to get skin from file:");
 | 
			
		||||
                    exception.printStackTrace();
 | 
			
		||||
                }
 | 
			
		||||
            } finally {
 | 
			
		||||
                if (connection != null) connection.disconnect();
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isNameFullyCached(String s) {
 | 
			
		||||
        String name = s.toLowerCase();
 | 
			
		||||
        if (!idCache.containsKey(name)) return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -227,7 +170,7 @@ public class MojangSkinCache {
 | 
			
		|||
            if (!skin.isExpired()) return CompletableFuture.completedFuture(skin);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return FutureUtil.exceptionPrintingSupplyAsync(() -> {
 | 
			
		||||
        return CompletableFuture.supplyAsync(() -> {
 | 
			
		||||
            URL url = parseUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false");
 | 
			
		||||
            HttpURLConnection connection = null;
 | 
			
		||||
            try {
 | 
			
		||||
| 
						 | 
				
			
			@ -270,8 +213,4 @@ public class MojangSkinCache {
 | 
			
		|||
            throw new RuntimeException(exception);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public File getSkinsFolder() {
 | 
			
		||||
        return skinsFolder;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,11 +9,11 @@ import org.bukkit.entity.Player;
 | 
			
		|||
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
public class NameFetchingDescriptor implements BaseSkinDescriptor, SkinDescriptor {
 | 
			
		||||
public class FetchingDescriptor implements BaseSkinDescriptor, SkinDescriptor {
 | 
			
		||||
    private final MojangSkinCache skinCache;
 | 
			
		||||
    private final String name;
 | 
			
		||||
 | 
			
		||||
    public NameFetchingDescriptor(MojangSkinCache skinCache, String name) {
 | 
			
		||||
    public FetchingDescriptor(MojangSkinCache skinCache, String name) {
 | 
			
		||||
        this.skinCache = skinCache;
 | 
			
		||||
        this.name = name;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -5,10 +5,8 @@ import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
 | 
			
		|||
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.SkinImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
 | 
			
		||||
import lol.pyr.znpcsplus.util.FutureUtil;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
 | 
			
		||||
import java.io.FileNotFoundException;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -20,21 +18,11 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public static CompletableFuture<PrefetchedDescriptor> forPlayer(MojangSkinCache cache, String name) {
 | 
			
		||||
        return FutureUtil.exceptionPrintingSupplyAsync(() -> new PrefetchedDescriptor(cache.fetchByName(name).join()));
 | 
			
		||||
        return CompletableFuture.supplyAsync(() -> new PrefetchedDescriptor(cache.fetchByName(name).join()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static CompletableFuture<PrefetchedDescriptor> fromUrl(MojangSkinCache cache, URL url, String variant) {
 | 
			
		||||
        return FutureUtil.exceptionPrintingSupplyAsync(() -> new PrefetchedDescriptor(cache.fetchByUrl(url, variant).join()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static CompletableFuture<PrefetchedDescriptor> fromFile(MojangSkinCache cache, String path) {
 | 
			
		||||
        return CompletableFuture.supplyAsync(() -> {
 | 
			
		||||
            try {
 | 
			
		||||
                return new PrefetchedDescriptor(cache.fetchFromFile(path).join());
 | 
			
		||||
            } catch (FileNotFoundException e) {
 | 
			
		||||
                throw new RuntimeException(e);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        return CompletableFuture.supplyAsync(() -> new PrefetchedDescriptor(cache.fetchByUrl(url, variant).join()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,42 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.skin.descriptor;
 | 
			
		||||
 | 
			
		||||
import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.SkinImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
public class UUIDFetchingDescriptor implements BaseSkinDescriptor, SkinDescriptor {
 | 
			
		||||
 | 
			
		||||
    private final MojangSkinCache skinCache;
 | 
			
		||||
    private final UUID uuid;
 | 
			
		||||
 | 
			
		||||
    public UUIDFetchingDescriptor(MojangSkinCache skinCache, UUID uuid) {
 | 
			
		||||
        this.skinCache = skinCache;
 | 
			
		||||
        this.uuid = uuid;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public CompletableFuture<SkinImpl> fetch(Player player) {
 | 
			
		||||
        return skinCache.fetchByUUID(uuid.toString());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public SkinImpl fetchInstant(Player player) {
 | 
			
		||||
        return fetch(player).join();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean supportsInstant(Player player) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String serialize() {
 | 
			
		||||
        return "fetching-uuid;" + uuid.toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -6,7 +6,6 @@ import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
 | 
			
		|||
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		||||
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.storage.mysql.MySQLStorage;
 | 
			
		||||
import lol.pyr.znpcsplus.storage.sqlite.SQLiteStorage;
 | 
			
		||||
import lol.pyr.znpcsplus.storage.yaml.YamlStorage;
 | 
			
		||||
| 
						 | 
				
			
			@ -17,13 +16,13 @@ import java.io.File;
 | 
			
		|||
public enum NpcStorageType {
 | 
			
		||||
    YAML {
 | 
			
		||||
        @Override
 | 
			
		||||
        public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcSerializerRegistryImpl serializerRegistry) {
 | 
			
		||||
            return new YamlStorage(serializerRegistry, new File(plugin.getDataFolder(), "data"));
 | 
			
		||||
        public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
 | 
			
		||||
            return new YamlStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer, new File(plugin.getDataFolder(), "data"));
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    SQLITE {
 | 
			
		||||
        @Override
 | 
			
		||||
        public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcSerializerRegistryImpl serializerRegistry) {
 | 
			
		||||
        public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
 | 
			
		||||
            try {
 | 
			
		||||
                return new SQLiteStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer, new File(plugin.getDataFolder(), "znpcsplus.sqlite"));
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +33,7 @@ public enum NpcStorageType {
 | 
			
		|||
    },
 | 
			
		||||
    MYSQL {
 | 
			
		||||
        @Override
 | 
			
		||||
        public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcSerializerRegistryImpl serializerRegistry) {
 | 
			
		||||
        public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
 | 
			
		||||
            try {
 | 
			
		||||
                return new MySQLStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
| 
						 | 
				
			
			@ -44,5 +43,5 @@ public enum NpcStorageType {
 | 
			
		|||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    public abstract NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcSerializerRegistryImpl serializerRegistry);
 | 
			
		||||
    public abstract NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,28 +1,47 @@
 | 
			
		|||
package lol.pyr.znpcsplus.storage.yaml;
 | 
			
		||||
 | 
			
		||||
import lol.pyr.znpcsplus.api.serialization.NpcSerializer;
 | 
			
		||||
import lol.pyr.znpcsplus.api.entity.EntityProperty;
 | 
			
		||||
import lol.pyr.znpcsplus.config.ConfigManager;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.entity.PropertySerializer;
 | 
			
		||||
import lol.pyr.znpcsplus.hologram.HologramImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
 | 
			
		||||
import lol.pyr.znpcsplus.packets.PacketFactory;
 | 
			
		||||
import lol.pyr.znpcsplus.storage.NpcStorage;
 | 
			
		||||
import lol.pyr.znpcsplus.util.NpcLocation;
 | 
			
		||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.configuration.ConfigurationSection;
 | 
			
		||||
import org.bukkit.configuration.file.YamlConfiguration;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
import java.util.logging.Logger;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
public class YamlStorage implements NpcStorage {
 | 
			
		||||
    private final static Logger logger = Logger.getLogger("YamlStorage");
 | 
			
		||||
 | 
			
		||||
    private final PacketFactory packetFactory;
 | 
			
		||||
    private final ConfigManager configManager;
 | 
			
		||||
    private final ActionRegistryImpl actionRegistry;
 | 
			
		||||
    private final NpcTypeRegistryImpl typeRegistry;
 | 
			
		||||
    private final EntityPropertyRegistryImpl propertyRegistry;
 | 
			
		||||
    private final LegacyComponentSerializer textSerializer;
 | 
			
		||||
    private final File folder;
 | 
			
		||||
    private final NpcSerializer<YamlConfiguration> yamlSerializer;
 | 
			
		||||
 | 
			
		||||
    public YamlStorage(NpcSerializerRegistryImpl serializerRegistry, File folder) {
 | 
			
		||||
        this.yamlSerializer = serializerRegistry.getSerializer(YamlConfiguration.class);
 | 
			
		||||
    public YamlStorage(PacketFactory packetFactory, ConfigManager configManager, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, File folder) {
 | 
			
		||||
        this.packetFactory = packetFactory;
 | 
			
		||||
        this.configManager = configManager;
 | 
			
		||||
        this.actionRegistry = actionRegistry;
 | 
			
		||||
        this.typeRegistry = typeRegistry;
 | 
			
		||||
        this.propertyRegistry = propertyRegistry;
 | 
			
		||||
        this.textSerializer = textSerializer;
 | 
			
		||||
        this.folder = folder;
 | 
			
		||||
        if (!this.folder.exists()) this.folder.mkdirs();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +53,45 @@ public class YamlStorage implements NpcStorage {
 | 
			
		|||
        List<NpcEntryImpl> npcs = new ArrayList<>(files.length);
 | 
			
		||||
        for (File file : files) if (file.isFile() && file.getName().toLowerCase().endsWith(".yml")) try {
 | 
			
		||||
            YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
 | 
			
		||||
            npcs.add((NpcEntryImpl) yamlSerializer.deserialize(config));
 | 
			
		||||
            UUID uuid = config.contains("uuid") ? UUID.fromString(config.getString("uuid")) : UUID.randomUUID();
 | 
			
		||||
            NpcImpl npc = new NpcImpl(uuid, propertyRegistry, configManager, packetFactory, textSerializer, config.getString("world"),
 | 
			
		||||
                    typeRegistry.getByName(config.getString("type")), deserializeLocation(config.getConfigurationSection("location")));
 | 
			
		||||
 | 
			
		||||
            if (config.isBoolean("enabled")) npc.setEnabled(config.getBoolean("enabled"));
 | 
			
		||||
 | 
			
		||||
            ConfigurationSection properties = config.getConfigurationSection("properties");
 | 
			
		||||
            if (properties != null) {
 | 
			
		||||
                for (String key : properties.getKeys(false)) {
 | 
			
		||||
                    EntityPropertyImpl<?> property = propertyRegistry.getByName(key);
 | 
			
		||||
                    if (property == null) {
 | 
			
		||||
                        Bukkit.getLogger().log(Level.WARNING, "Unknown property '" + key + "' for npc '" + config.getString("id") + "'. skipping ...");
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    PropertySerializer<?> serializer = propertyRegistry.getSerializer(property.getType());
 | 
			
		||||
                    if (serializer == null) {
 | 
			
		||||
                        Bukkit.getLogger().log(Level.WARNING, "Unknown serializer for property '" + key + "' for npc '" + config.getString("id") + "'. skipping ...");
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    Object value = serializer.deserialize(properties.getString(key));
 | 
			
		||||
                    if (value == null) {
 | 
			
		||||
                        Bukkit.getLogger().log(Level.WARNING, "Failed to deserialize property '" + key + "' for npc '" + config.getString("id") + "'. Resetting to default ...");
 | 
			
		||||
                        value = property.getDefaultValue();
 | 
			
		||||
                    }
 | 
			
		||||
                    npc.UNSAFE_setProperty(property, value);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            HologramImpl hologram = npc.getHologram();
 | 
			
		||||
            hologram.setOffset(config.getDouble("hologram.offset", 0.0));
 | 
			
		||||
            hologram.setRefreshDelay(config.getLong("hologram.refresh-delay", -1));
 | 
			
		||||
            for (String line : config.getStringList("hologram.lines")) hologram.addLine(line);
 | 
			
		||||
            for (String s : config.getStringList("actions")) npc.addAction(actionRegistry.deserialize(s));
 | 
			
		||||
 | 
			
		||||
            NpcEntryImpl entry = new NpcEntryImpl(config.getString("id"), npc);
 | 
			
		||||
            entry.setProcessed(config.getBoolean("is-processed"));
 | 
			
		||||
            entry.setAllowCommandModification(config.getBoolean("allow-commands"));
 | 
			
		||||
            entry.setSave(true);
 | 
			
		||||
 | 
			
		||||
            npcs.add(entry);
 | 
			
		||||
        } catch (Throwable t) {
 | 
			
		||||
            logger.severe("Failed to load npc file: " + file.getName());
 | 
			
		||||
            t.printStackTrace();
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +102,43 @@ public class YamlStorage implements NpcStorage {
 | 
			
		|||
    @Override
 | 
			
		||||
    public void saveNpcs(Collection<NpcEntryImpl> npcs) {
 | 
			
		||||
        for (NpcEntryImpl entry : npcs) try {
 | 
			
		||||
            YamlConfiguration config = yamlSerializer.serialize(entry);
 | 
			
		||||
            YamlConfiguration config = new YamlConfiguration();
 | 
			
		||||
            config.set("id", entry.getId());
 | 
			
		||||
            config.set("is-processed", entry.isProcessed());
 | 
			
		||||
            config.set("allow-commands", entry.isAllowCommandModification());
 | 
			
		||||
 | 
			
		||||
            NpcImpl npc = entry.getNpc();
 | 
			
		||||
            config.set("enabled", npc.isEnabled());
 | 
			
		||||
            config.set("uuid", npc.getUuid().toString());
 | 
			
		||||
            config.set("world", npc.getWorldName());
 | 
			
		||||
            config.set("location", serializeLocation(npc.getLocation()));
 | 
			
		||||
            config.set("type", npc.getType().getName());
 | 
			
		||||
 | 
			
		||||
            for (EntityProperty<?> property : npc.getAllProperties()) try {
 | 
			
		||||
                PropertySerializer<?> serializer = propertyRegistry.getSerializer(((EntityPropertyImpl<?>) property).getType());
 | 
			
		||||
                if (serializer == null) {
 | 
			
		||||
                    Bukkit.getLogger().log(Level.WARNING, "Unknown serializer for property '" + property.getName() + "' for npc '" + entry.getId() + "'. skipping ...");
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                config.set("properties." + property.getName(), serializer.UNSAFE_serialize(npc.getProperty(property)));
 | 
			
		||||
            } catch (Exception exception) {
 | 
			
		||||
                logger.severe("Failed to serialize property " + property.getName() + " for npc with id " + entry.getId());
 | 
			
		||||
                exception.printStackTrace();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            HologramImpl hologram = npc.getHologram();
 | 
			
		||||
            if (hologram.getOffset() != 0.0) config.set("hologram.offset", hologram.getOffset());
 | 
			
		||||
            if (hologram.getRefreshDelay() != -1) config.set("hologram.refresh-delay", hologram.getRefreshDelay());
 | 
			
		||||
            List<String> lines = new ArrayList<>(npc.getHologram().getLines().size());
 | 
			
		||||
            for (int i = 0; i < hologram.getLines().size(); i++) {
 | 
			
		||||
                lines.add(hologram.getLine(i));
 | 
			
		||||
            }
 | 
			
		||||
            config.set("hologram.lines", lines);
 | 
			
		||||
            config.set("actions", npc.getActions().stream()
 | 
			
		||||
                    .map(actionRegistry::serialize)
 | 
			
		||||
                    .filter(Objects::nonNull)
 | 
			
		||||
                    .collect(Collectors.toList()));
 | 
			
		||||
 | 
			
		||||
            config.save(fileFor(entry));
 | 
			
		||||
        } catch (Exception exception) {
 | 
			
		||||
            logger.severe("Failed to save npc with id " + entry.getId());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,7 +33,6 @@ public class NpcProcessorTask extends BukkitRunnable {
 | 
			
		|||
        EntityPropertyImpl<Integer> viewDistanceProperty = propertyRegistry.getByName("view_distance", Integer.class); // Not sure why this is an Integer, but it is
 | 
			
		||||
        EntityPropertyImpl<LookType> lookProperty = propertyRegistry.getByName("look", LookType.class);
 | 
			
		||||
        EntityPropertyImpl<Double> lookDistanceProperty = propertyRegistry.getByName("look_distance", Double.class);
 | 
			
		||||
        EntityPropertyImpl<Boolean> lookReturnProperty = propertyRegistry.getByName("look_return", Boolean.class);
 | 
			
		||||
        EntityPropertyImpl<Boolean> permissionRequiredProperty = propertyRegistry.getByName("permission_required", Boolean.class);
 | 
			
		||||
        EntityPropertyImpl<Boolean> playerKnockbackProperty = propertyRegistry.getByName("player_knockback", Boolean.class);
 | 
			
		||||
        EntityPropertyImpl<String> playerKnockbackExemptPermissionProperty = propertyRegistry.getByName("player_knockback_exempt_permission", String.class);
 | 
			
		||||
| 
						 | 
				
			
			@ -46,7 +45,6 @@ public class NpcProcessorTask extends BukkitRunnable {
 | 
			
		|||
        EntityPropertyImpl<Float> playerKnockbackSoundVolumeProperty = propertyRegistry.getByName("player_knockback_sound_volume", Float.class);
 | 
			
		||||
        EntityPropertyImpl<Float> playerKnockbackSoundPitchProperty = propertyRegistry.getByName("player_knockback_sound_pitch", Float.class);
 | 
			
		||||
        double lookDistance;
 | 
			
		||||
        boolean lookReturn;
 | 
			
		||||
        boolean permissionRequired;
 | 
			
		||||
        boolean playerKnockback;
 | 
			
		||||
        String playerKnockbackExemptPermission = null;
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +64,6 @@ public class NpcProcessorTask extends BukkitRunnable {
 | 
			
		|||
            Player closest = null;
 | 
			
		||||
            LookType lookType = npc.getProperty(lookProperty);
 | 
			
		||||
            lookDistance =  NumberConversions.square(npc.getProperty(lookDistanceProperty));
 | 
			
		||||
            lookReturn = npc.getProperty(lookReturnProperty);
 | 
			
		||||
            permissionRequired = npc.getProperty(permissionRequiredProperty);
 | 
			
		||||
            playerKnockback = npc.getProperty(playerKnockbackProperty);
 | 
			
		||||
            if (playerKnockback) {
 | 
			
		||||
| 
						 | 
				
			
			@ -109,13 +106,9 @@ public class NpcProcessorTask extends BukkitRunnable {
 | 
			
		|||
                        closestDist = distance;
 | 
			
		||||
                        closest = player;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (lookType.equals(LookType.PER_PLAYER)) {
 | 
			
		||||
                        if (lookDistance >= distance) {
 | 
			
		||||
                    if (lookType.equals(LookType.PER_PLAYER) && lookDistance >= distance) {
 | 
			
		||||
                        NpcLocation expected = npc.getLocation().lookingAt(player.getLocation().add(0, -npc.getType().getHologramOffset(), 0));
 | 
			
		||||
                            npc.setHeadRotation(player, expected.getYaw(), expected.getPitch());
 | 
			
		||||
                        } else if (lookReturn) {
 | 
			
		||||
                            npc.setHeadRotation(player, npc.getLocation().getYaw(), npc.getLocation().getPitch());
 | 
			
		||||
                        }
 | 
			
		||||
                        if (!expected.equals(npc.getLocation())) npc.setHeadRotation(player, expected.getYaw(), expected.getPitch());
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // player knockback
 | 
			
		||||
| 
						 | 
				
			
			@ -139,11 +132,7 @@ public class NpcProcessorTask extends BukkitRunnable {
 | 
			
		|||
                if (closest != null && lookDistance >= closestDist) {
 | 
			
		||||
                    NpcLocation expected = npc.getLocation().lookingAt(closest.getLocation().add(0, -npc.getType().getHologramOffset(), 0));
 | 
			
		||||
                    if (!expected.equals(npc.getLocation())) npc.setHeadRotation(expected.getYaw(), expected.getPitch());
 | 
			
		||||
                } else if (lookReturn) {
 | 
			
		||||
                    npc.setHeadRotation(npc.getLocation().getYaw(), npc.getLocation().getPitch());
 | 
			
		||||
                }
 | 
			
		||||
            } else if (lookType.equals(LookType.FIXED)) {
 | 
			
		||||
                npc.setHeadRotation(npc.getLocation().getYaw(), npc.getLocation().getPitch());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,34 +0,0 @@
 | 
			
		|||
package lol.pyr.znpcsplus.util;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
public class FutureUtil {
 | 
			
		||||
    public static CompletableFuture<Void> allOf(Collection<CompletableFuture<?>> futures) {
 | 
			
		||||
        return exceptionPrintingRunAsync(() -> {
 | 
			
		||||
            for (CompletableFuture<?> future : futures) future.join();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static <T> CompletableFuture<T> newExceptionPrintingFuture() {
 | 
			
		||||
        return new CompletableFuture<T>().exceptionally(throwable -> {
 | 
			
		||||
            throwable.printStackTrace();
 | 
			
		||||
            return null;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static CompletableFuture<Void> exceptionPrintingRunAsync(Runnable runnable) {
 | 
			
		||||
        return CompletableFuture.runAsync(runnable).exceptionally(throwable -> {
 | 
			
		||||
            throwable.printStackTrace();
 | 
			
		||||
            return null;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static <T> CompletableFuture<T> exceptionPrintingSupplyAsync(Supplier<T> supplier) {
 | 
			
		||||
        return CompletableFuture.supplyAsync(supplier).exceptionally(throwable -> {
 | 
			
		||||
            throwable.printStackTrace();
 | 
			
		||||
            return null;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,95 +4,55 @@ import org.bukkit.entity.Player;
 | 
			
		|||
 | 
			
		||||
import java.lang.ref.Reference;
 | 
			
		||||
import java.lang.ref.WeakReference;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
import java.util.concurrent.ConcurrentLinkedQueue;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
public abstract class Viewable {
 | 
			
		||||
    private final static List<WeakReference<Viewable>> all = Collections.synchronizedList(new ArrayList<>());
 | 
			
		||||
    private final static List<WeakReference<Viewable>> all = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    public static List<Viewable> all() {
 | 
			
		||||
        synchronized (all) {
 | 
			
		||||
        all.removeIf(reference -> reference.get() == null);
 | 
			
		||||
        return all.stream()
 | 
			
		||||
                .map(Reference::get)
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean queueRunning = false;
 | 
			
		||||
    private final Queue<Runnable> visibilityTaskQueue = new ConcurrentLinkedQueue<>();
 | 
			
		||||
    private final Set<Player> viewers = ConcurrentHashMap.newKeySet();
 | 
			
		||||
 | 
			
		||||
    public Viewable() {
 | 
			
		||||
        all.add(new WeakReference<>(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void tryRunQueue() {
 | 
			
		||||
        if (visibilityTaskQueue.isEmpty() || queueRunning) return;
 | 
			
		||||
        queueRunning = true;
 | 
			
		||||
        FutureUtil.exceptionPrintingRunAsync(() -> {
 | 
			
		||||
            while (!visibilityTaskQueue.isEmpty()) try {
 | 
			
		||||
                visibilityTaskQueue.remove().run();
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                e.printStackTrace();
 | 
			
		||||
            }
 | 
			
		||||
            queueRunning = false;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void queueVisibilityTask(Runnable runnable) {
 | 
			
		||||
        visibilityTaskQueue.add(runnable);
 | 
			
		||||
        tryRunQueue();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void delete() {
 | 
			
		||||
        queueVisibilityTask(() -> {
 | 
			
		||||
        UNSAFE_hideAll();
 | 
			
		||||
        viewers.clear();
 | 
			
		||||
            synchronized (all) {
 | 
			
		||||
                all.removeIf(reference -> reference.get() == null || reference.get() == this);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public CompletableFuture<Void> respawn() {
 | 
			
		||||
        CompletableFuture<Void> future = new CompletableFuture<>();
 | 
			
		||||
        queueVisibilityTask(() -> {
 | 
			
		||||
    public void respawn() {
 | 
			
		||||
        UNSAFE_hideAll();
 | 
			
		||||
            UNSAFE_showAll().join();
 | 
			
		||||
            future.complete(null);
 | 
			
		||||
        });
 | 
			
		||||
        return future;
 | 
			
		||||
        UNSAFE_showAll();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public CompletableFuture<Void> respawn(Player player) {
 | 
			
		||||
        hide(player);
 | 
			
		||||
        return show(player);
 | 
			
		||||
    public void respawn(Player player) {
 | 
			
		||||
        if (!viewers.contains(player)) return;
 | 
			
		||||
        UNSAFE_hide(player);
 | 
			
		||||
        UNSAFE_show(player);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public CompletableFuture<Void> show(Player player) {
 | 
			
		||||
        CompletableFuture<Void> future = new CompletableFuture<>();
 | 
			
		||||
        queueVisibilityTask(() -> {
 | 
			
		||||
            if (viewers.contains(player)) {
 | 
			
		||||
                future.complete(null);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
    public void show(Player player) {
 | 
			
		||||
        if (viewers.contains(player)) return;
 | 
			
		||||
        viewers.add(player);
 | 
			
		||||
            UNSAFE_show(player).join();
 | 
			
		||||
            future.complete(null);
 | 
			
		||||
        });
 | 
			
		||||
        return future;
 | 
			
		||||
        UNSAFE_show(player);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void hide(Player player) {
 | 
			
		||||
        queueVisibilityTask(() -> {
 | 
			
		||||
        if (!viewers.contains(player)) return;
 | 
			
		||||
        viewers.remove(player);
 | 
			
		||||
        UNSAFE_hide(player);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void UNSAFE_removeViewer(Player player) {
 | 
			
		||||
| 
						 | 
				
			
			@ -103,10 +63,8 @@ public abstract class Viewable {
 | 
			
		|||
        for (Player viewer : viewers) UNSAFE_hide(viewer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected CompletableFuture<Void> UNSAFE_showAll() {
 | 
			
		||||
        return FutureUtil.allOf(viewers.stream()
 | 
			
		||||
                .map(this::UNSAFE_show)
 | 
			
		||||
                .collect(Collectors.toList()));
 | 
			
		||||
    protected void UNSAFE_showAll() {
 | 
			
		||||
        for (Player viewer : viewers) UNSAFE_show(viewer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Set<Player> getViewers() {
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +75,7 @@ public abstract class Viewable {
 | 
			
		|||
        return viewers.contains(player);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected abstract CompletableFuture<Void> UNSAFE_show(Player player);
 | 
			
		||||
    protected abstract void UNSAFE_show(Player player);
 | 
			
		||||
 | 
			
		||||
    protected abstract void UNSAFE_hide(Player player);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue