Merge remote-tracking branch 'upstream/2.X' into 2.X

# Conflicts:
#	build.gradle
#	gradle/wrapper/gradle-wrapper.properties
#	plugin/build.gradle
This commit is contained in:
bridge 2025-07-27 00:45:57 +02:00
commit 1c927c6b32
86 changed files with 1577 additions and 382 deletions

View file

@ -12,14 +12,14 @@ Looking for up-to-date builds of the plugin? Check out our [Jenkins](https://ci.
## Why is it so good? ## Why is it so good?
- 100% Packet Based - Nothing is ran on the main thread - 100% Packet Based - Nothing is ran on the main thread
- Performance & stability oriented code - Performance & stability oriented code
- Support for all versions from 1.8 to 1.20.4 - Support for all versions from 1.8 to 1.21.8
- Support for multiple different storage options - Support for multiple different storage options
- Intuitive command system - Intuitive command system
### Requirements, Extensions & Supported Software ### Requirements, Extensions & Supported Software
Requirements: Requirements:
- Java 8+ - Java 8+
- Minecraft 1.8 - 1.21 - Minecraft 1.8 - 1.21.8
Supported Softwares: Supported Softwares:
- Spigot ([Website](https://www.spigotmc.org/)) - 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 ## Credits
- [PacketEvents 2.0](https://github.com/retrooper/packetevents) - Packet library - [PacketEvents 2.0](https://github.com/retrooper/packetevents) - Packet library
- [wiki.vg](https://wiki.vg/Main_Page) - Minecraft protocol documentation - [Minecraft Wiki Protocol (formally wiki.vg)](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Main_Page) - Minecraft protocol documentation
- [gson](https://github.com/google/gson) - JSON parsing library made by Google - [gson](https://github.com/google/gson) - JSON parsing library made by Google
- [Mineskin.org](https://mineskin.org/) - Website for raw skin file uploads - [Mineskin.org](https://mineskin.org/) - Website for raw skin file uploads
- [adventure](https://docs.advntr.dev/) - Minecraft text api - [adventure](https://docs.advntr.dev/) - Minecraft text api

View file

@ -5,6 +5,7 @@ import lol.pyr.znpcsplus.api.interaction.ActionFactory;
import lol.pyr.znpcsplus.api.interaction.ActionRegistry; import lol.pyr.znpcsplus.api.interaction.ActionRegistry;
import lol.pyr.znpcsplus.api.npc.NpcRegistry; import lol.pyr.znpcsplus.api.npc.NpcRegistry;
import lol.pyr.znpcsplus.api.npc.NpcTypeRegistry; import lol.pyr.znpcsplus.api.npc.NpcTypeRegistry;
import lol.pyr.znpcsplus.api.serialization.NpcSerializerRegistry;
import lol.pyr.znpcsplus.api.skin.SkinDescriptorFactory; import lol.pyr.znpcsplus.api.skin.SkinDescriptorFactory;
/** /**
@ -46,4 +47,10 @@ public interface NpcApi {
* @return the skin descriptor factory * @return the skin descriptor factory
*/ */
SkinDescriptorFactory getSkinDescriptorFactory(); SkinDescriptorFactory getSkinDescriptorFactory();
/**
* Gets the npc serializer registry.
* @return the npc serializer registry
*/
NpcSerializerRegistry getNpcSerializerRegistry();
} }

View file

@ -2,6 +2,8 @@ package lol.pyr.znpcsplus.api;
import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry; import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.ServicePriority;
/** /**
* Provider for the registered entity property registry instance * Provider for the registered entity property registry instance
@ -30,10 +32,12 @@ public class NpcPropertyRegistryProvider {
* Internal method used to register the main instance of the plugin as the entity property registry provider * 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 * 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 * @param api Instance of the ZNPCsPlus entity property registry
*/ */
public static void register(EntityPropertyRegistry api) { public static void register(Plugin plugin, EntityPropertyRegistry api) {
NpcPropertyRegistryProvider.registry = api; NpcPropertyRegistryProvider.registry = api;
Bukkit.getServicesManager().register(EntityPropertyRegistry.class, registry, plugin, ServicePriority.Normal);
} }
/** /**

View file

@ -31,11 +31,25 @@ public interface EntityPropertyRegistry {
*/ */
<T> EntityProperty<T> getByName(String name, Class<T> type); <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 * Register a dummy property that can be used to store unique information per npc
* *
* @param name The name of the new property * @param name The name of the new property
* @param type The type 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); void registerDummy(String name, Class<?> type, boolean playerModifiable);
} }

View file

@ -6,10 +6,12 @@ import lol.pyr.znpcsplus.api.interaction.InteractionAction;
import lol.pyr.znpcsplus.util.NpcLocation; import lol.pyr.znpcsplus.util.NpcLocation;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/** /**
* Base class for all NPCs * Base class for all NPCs
@ -135,14 +137,16 @@ public interface Npc extends PropertyHolder {
/** /**
* Shows this NPC to a player * Shows this NPC to a player
* @param player The {@link Player} to show to * @param player The {@link Player} to show to
* @return A future that completes when the npc is fully shown to the player
*/ */
void show(Player player); CompletableFuture<Void> show(Player player);
/** /**
* Respawns this NPC for a player * Respawns this NPC for a player
* @param player The {@link Player} to respawn for * @param player The {@link Player} to respawn for
* @return A future that completes when the npc is fully respawned
*/ */
void respawn(Player player); CompletableFuture<Void> respawn(Player player);
/** /**
* Sets the head rotation of this NPC for a player * Sets the head rotation of this NPC for a player
@ -174,4 +178,35 @@ public interface Npc extends PropertyHolder {
* @param offHand Should the hand be the offhand * @param offHand Should the hand be the offhand
*/ */
void swingHand(boolean 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);
} }

View file

@ -64,4 +64,27 @@ public interface NpcRegistry {
* @param id The ID of the NPC entry * @param id The ID of the NPC entry
*/ */
void delete(String id); 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();
} }

View file

@ -0,0 +1,20 @@
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);
}

View file

@ -0,0 +1,19 @@
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);
}

View file

@ -1,6 +1,7 @@
package lol.pyr.znpcsplus.api.skin; package lol.pyr.znpcsplus.api.skin;
import java.net.URL; import java.net.URL;
import java.util.UUID;
/** /**
* Factory for creating skin descriptors. * Factory for creating skin descriptors.
@ -8,8 +9,10 @@ import java.net.URL;
public interface SkinDescriptorFactory { public interface SkinDescriptorFactory {
SkinDescriptor createMirrorDescriptor(); SkinDescriptor createMirrorDescriptor();
SkinDescriptor createRefreshingDescriptor(String playerName); SkinDescriptor createRefreshingDescriptor(String playerName);
SkinDescriptor createRefreshingDescriptor(UUID playerUUID);
SkinDescriptor createStaticDescriptor(String playerName); SkinDescriptor createStaticDescriptor(String playerName);
SkinDescriptor createStaticDescriptor(String texture, String signature); SkinDescriptor createStaticDescriptor(String texture, String signature);
SkinDescriptor createUrlDescriptor(String url, String variant); SkinDescriptor createUrlDescriptor(String url, String variant);
SkinDescriptor createUrlDescriptor(URL url, String variant); SkinDescriptor createUrlDescriptor(URL url, String variant);
SkinDescriptor createFileDescriptor(String path);
} }

View file

@ -0,0 +1,11 @@
package lol.pyr.znpcsplus.util;
public enum SkeletonType {
NORMAL,
WITHER,
STRAY;
public byte getLegacyId() {
return (byte) ordinal();
}
}

View file

@ -0,0 +1,11 @@
package lol.pyr.znpcsplus.util;
public enum ZombieType {
ZOMBIE,
FARMER,
LIBRARIAN,
PRIEST,
BLACKSMITH,
BUTCHER,
HUSK
}

View file

@ -10,6 +10,7 @@ subprojects {
} }
dependencies { dependencies {
compileOnly "org.jetbrains:annotations:26.0.1"
compileOnly "org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT" compileOnly "org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT"
} }

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View file

@ -9,8 +9,9 @@ processResources {
dependencies { dependencies {
compileOnly "me.clip:placeholderapi:2.11.6" // Placeholder support compileOnly "me.clip:placeholderapi:2.11.6" // Placeholder support
implementation "com.google.code.gson:gson:2.12.1" // JSON parsing implementation "com.google.code.gson:gson:2.10.1" // JSON parsing
implementation "com.github.retrooper:packetevents-spigot:2.7.1-SNAPSHOT" // Packets implementation "org.bstats:bstats-bukkit:3.0.2" // Plugin stats
implementation "com.github.retrooper:packetevents-spigot:2.9.3" // Packets
implementation "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs implementation "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs
implementation "lol.pyr:director-adventure:2.1.2" // Commands implementation "lol.pyr:director-adventure:2.1.2" // Commands

View file

@ -38,6 +38,7 @@ import lol.pyr.znpcsplus.parsers.*;
import lol.pyr.znpcsplus.scheduling.FoliaScheduler; import lol.pyr.znpcsplus.scheduling.FoliaScheduler;
import lol.pyr.znpcsplus.scheduling.SpigotScheduler; import lol.pyr.znpcsplus.scheduling.SpigotScheduler;
import lol.pyr.znpcsplus.scheduling.TaskScheduler; 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.MojangSkinCache;
import lol.pyr.znpcsplus.skin.cache.SkinCacheCleanTask; import lol.pyr.znpcsplus.skin.cache.SkinCacheCleanTask;
import lol.pyr.znpcsplus.storage.NpcStorageType; import lol.pyr.znpcsplus.storage.NpcStorageType;
@ -92,10 +93,10 @@ public class ZNpcsPlus {
packetEvents.load(); packetEvents.load();
configManager = new ConfigManager(getDataFolder()); configManager = new ConfigManager(getDataFolder());
skinCache = new MojangSkinCache(configManager); skinCache = new MojangSkinCache(configManager, new File(getDataFolder(), "skins"));
propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager); propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager);
NpcPropertyRegistryProvider.register(propertyRegistry); NpcPropertyRegistryProvider.register(bootstrap, propertyRegistry);
shutdownTasks.add(NpcPropertyRegistryProvider::unregister); shutdownTasks.add(NpcPropertyRegistryProvider::unregister);
} }
@ -128,14 +129,15 @@ public class ZNpcsPlus {
PacketFactory packetFactory = setupPacketFactory(scheduler, propertyRegistry, configManager); PacketFactory packetFactory = setupPacketFactory(scheduler, propertyRegistry, configManager);
propertyRegistry.registerTypes(bootstrap, packetFactory, textSerializer, scheduler); propertyRegistry.registerTypes(packetFactory, textSerializer, scheduler);
BungeeConnector bungeeConnector = new BungeeConnector(bootstrap); BungeeConnector bungeeConnector = new BungeeConnector(bootstrap);
ActionRegistryImpl actionRegistry = new ActionRegistryImpl(); ActionRegistryImpl actionRegistry = new ActionRegistryImpl();
ActionFactoryImpl actionFactory = new ActionFactoryImpl(scheduler, adventure, textSerializer, bungeeConnector); ActionFactoryImpl actionFactory = new ActionFactoryImpl(scheduler, adventure, textSerializer, bungeeConnector);
NpcTypeRegistryImpl typeRegistry = new NpcTypeRegistryImpl(); NpcTypeRegistryImpl typeRegistry = new NpcTypeRegistryImpl();
NpcSerializerRegistryImpl serializerRegistry = new NpcSerializerRegistryImpl(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
NpcRegistryImpl npcRegistry = new NpcRegistryImpl(configManager, this, packetFactory, actionRegistry, NpcRegistryImpl npcRegistry = new NpcRegistryImpl(configManager, this, packetFactory, actionRegistry,
scheduler, typeRegistry, propertyRegistry, textSerializer); scheduler, typeRegistry, propertyRegistry, serializerRegistry, textSerializer);
shutdownTasks.add(npcRegistry::unload); shutdownTasks.add(npcRegistry::unload);
UserManager userManager = new UserManager(); UserManager userManager = new UserManager();
@ -157,7 +159,7 @@ public class ZNpcsPlus {
pluginManager.registerEvents(new UserListener(userManager), bootstrap); pluginManager.registerEvents(new UserListener(userManager), bootstrap);
registerCommands(npcRegistry, skinCache, adventure, actionRegistry, registerCommands(npcRegistry, skinCache, adventure, actionRegistry,
typeRegistry, propertyRegistry, importerRegistry, configManager, packetFactory); typeRegistry, propertyRegistry, importerRegistry, configManager, packetFactory, serializerRegistry);
log(ChatColor.WHITE + " * Starting tasks..."); log(ChatColor.WHITE + " * Starting tasks...");
if (configManager.getConfig().checkForUpdates()) { if (configManager.getConfig().checkForUpdates()) {
@ -191,7 +193,7 @@ public class ZNpcsPlus {
} }
} }
NpcApiProvider.register(bootstrap, new ZNpcsPlusApi(npcRegistry, typeRegistry, propertyRegistry, actionRegistry, actionFactory, skinCache)); NpcApiProvider.register(bootstrap, new ZNpcsPlusApi(npcRegistry, typeRegistry, propertyRegistry, actionRegistry, actionFactory, skinCache, serializerRegistry));
log(ChatColor.WHITE + " * Loading complete! (" + (System.currentTimeMillis() - before) + "ms)"); log(ChatColor.WHITE + " * Loading complete! (" + (System.currentTimeMillis() - before) + "ms)");
log(""); log("");
@ -244,7 +246,7 @@ public class ZNpcsPlus {
private void registerCommands(NpcRegistryImpl npcRegistry, MojangSkinCache skinCache, BukkitAudiences adventure, private void registerCommands(NpcRegistryImpl npcRegistry, MojangSkinCache skinCache, BukkitAudiences adventure,
ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry,
EntityPropertyRegistryImpl propertyRegistry, DataImporterRegistry importerRegistry, EntityPropertyRegistryImpl propertyRegistry, DataImporterRegistry importerRegistry,
ConfigManager configManager, PacketFactory packetFactory) { ConfigManager configManager, PacketFactory packetFactory, NpcSerializerRegistryImpl serializerRegistry) {
Message<CommandContext> incorrectUsageMessage = context -> context.send(Component.text("Incorrect usage: /" + context.getUsage(), NamedTextColor.RED)); Message<CommandContext> incorrectUsageMessage = context -> context.send(Component.text("Incorrect usage: /" + context.getUsage(), NamedTextColor.RED));
CommandManager manager = new CommandManager(bootstrap, adventure, incorrectUsageMessage); CommandManager manager = new CommandManager(bootstrap, adventure, incorrectUsageMessage);
@ -294,6 +296,7 @@ public class ZNpcsPlus {
registerEnumParser(manager, ArmadilloState.class, incorrectUsageMessage); registerEnumParser(manager, ArmadilloState.class, incorrectUsageMessage);
registerEnumParser(manager, WoldVariant.class, incorrectUsageMessage); registerEnumParser(manager, WoldVariant.class, incorrectUsageMessage);
registerEnumParser(manager, NpcStorageType.class, incorrectUsageMessage); registerEnumParser(manager, NpcStorageType.class, incorrectUsageMessage);
registerEnumParser(manager, SkeletonType.class, incorrectUsageMessage);
manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root")) manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root"))
.addSubcommand("center", new CenterCommand(npcRegistry)) .addSubcommand("center", new CenterCommand(npcRegistry))
@ -319,7 +322,7 @@ public class ZNpcsPlus {
.addSubcommand("save", new SaveAllCommand(npcRegistry)) .addSubcommand("save", new SaveAllCommand(npcRegistry))
.addSubcommand("reload", new LoadAllCommand(npcRegistry)) .addSubcommand("reload", new LoadAllCommand(npcRegistry))
.addSubcommand("import", new ImportCommand(npcRegistry, importerRegistry)) .addSubcommand("import", new ImportCommand(npcRegistry, importerRegistry))
.addSubcommand("migrate", new MigrateCommand(configManager, this, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, npcRegistry.getStorage(), configManager.getConfig().storageType(), npcRegistry))) .addSubcommand("migrate", new MigrateCommand(configManager, this, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, npcRegistry.getStorage(), configManager.getConfig().storageType(), npcRegistry, serializerRegistry)))
.addSubcommand("holo", new MultiCommand(bootstrap.loadHelpMessage("holo")) .addSubcommand("holo", new MultiCommand(bootstrap.loadHelpMessage("holo"))
.addSubcommand("add", new HoloAddCommand(npcRegistry)) .addSubcommand("add", new HoloAddCommand(npcRegistry))
.addSubcommand("additem", new HoloAddItemCommand(npcRegistry)) .addSubcommand("additem", new HoloAddItemCommand(npcRegistry))
@ -339,6 +342,7 @@ public class ZNpcsPlus {
.addSubcommand("delete", new ActionDeleteCommand(npcRegistry)) .addSubcommand("delete", new ActionDeleteCommand(npcRegistry))
.addSubcommand("edit", new ActionEditCommand(npcRegistry, actionRegistry)) .addSubcommand("edit", new ActionEditCommand(npcRegistry, actionRegistry))
.addSubcommand("list", new ActionListCommand(npcRegistry))) .addSubcommand("list", new ActionListCommand(npcRegistry)))
.addSubcommand("version", new VersionCommand(this))
); );
} }

View file

@ -12,6 +12,7 @@ import lol.pyr.znpcsplus.interaction.ActionFactoryImpl;
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl; import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl; import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.skin.SkinDescriptorFactoryImpl; import lol.pyr.znpcsplus.skin.SkinDescriptorFactoryImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
@ -22,14 +23,16 @@ public class ZNpcsPlusApi implements NpcApi {
private final ActionRegistryImpl actionRegistry; private final ActionRegistryImpl actionRegistry;
private final ActionFactoryImpl actionFactory; private final ActionFactoryImpl actionFactory;
private final SkinDescriptorFactoryImpl skinDescriptorFactory; private final SkinDescriptorFactoryImpl skinDescriptorFactory;
private final NpcSerializerRegistryImpl npcSerializerRegistry;
public ZNpcsPlusApi(NpcRegistryImpl npcRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, ActionRegistryImpl actionRegistry, ActionFactoryImpl actionFactory, MojangSkinCache skinCache) { public ZNpcsPlusApi(NpcRegistryImpl npcRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, ActionRegistryImpl actionRegistry, ActionFactoryImpl actionFactory, MojangSkinCache skinCache, NpcSerializerRegistryImpl npcSerializerRegistry) {
this.npcRegistry = npcRegistry; this.npcRegistry = npcRegistry;
this.typeRegistry = typeRegistry; this.typeRegistry = typeRegistry;
this.propertyRegistry = propertyRegistry; this.propertyRegistry = propertyRegistry;
this.actionRegistry = actionRegistry; this.actionRegistry = actionRegistry;
this.actionFactory = actionFactory; this.actionFactory = actionFactory;
this.skinDescriptorFactory = new SkinDescriptorFactoryImpl(skinCache); this.skinDescriptorFactory = new SkinDescriptorFactoryImpl(skinCache);
this.npcSerializerRegistry = npcSerializerRegistry;
} }
@Override @Override
@ -62,4 +65,9 @@ public class ZNpcsPlusApi implements NpcApi {
public SkinDescriptorFactory getSkinDescriptorFactory() { public SkinDescriptorFactory getSkinDescriptorFactory() {
return skinDescriptorFactory; return skinDescriptorFactory;
} }
@Override
public NpcSerializerRegistryImpl getNpcSerializerRegistry() {
return npcSerializerRegistry;
}
} }

View file

@ -26,12 +26,18 @@ public class CreateCommand implements CommandHandler {
@Override @Override
public void run(CommandContext context) throws CommandExecutionException { 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(); Player player = context.ensureSenderIsPlayer();
String id = context.popString(); String id = context.popString();
if (npcRegistry.getById(id) != null) context.halt(Component.text("NPC with that ID already exists.", NamedTextColor.RED)); if (npcRegistry.getById(id) != null) context.halt(Component.text("NPC with that ID already exists.", NamedTextColor.RED));
NpcTypeImpl type = context.parse(NpcTypeImpl.class);
NpcTypeImpl type;
if (context.argSize() == 1) {
type = context.parse(NpcTypeImpl.class);
} else {
type = typeRegistry.getByName("player");
}
NpcEntryImpl entry = npcRegistry.create(id, player.getWorld(), type, new NpcLocation(player.getLocation())); NpcEntryImpl entry = npcRegistry.create(id, player.getWorld(), type, new NpcLocation(player.getLocation()));
entry.enableEverything(); entry.enableEverything();

View file

@ -11,16 +11,20 @@ import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl; import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor; import lol.pyr.znpcsplus.skin.descriptor.NameFetchingDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor; import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor; import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
public class SkinCommand implements CommandHandler { public class SkinCommand implements CommandHandler {
private final MojangSkinCache skinCache; private final MojangSkinCache skinCache;
@ -63,7 +67,7 @@ public class SkinCommand implements CommandHandler {
} else if (type.equalsIgnoreCase("dynamic")) { } else if (type.equalsIgnoreCase("dynamic")) {
context.ensureArgsNotEmpty(); context.ensureArgsNotEmpty();
String name = context.dumpAllArgs(); String name = context.dumpAllArgs();
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new FetchingDescriptor(skinCache, name)); npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new NameFetchingDescriptor(skinCache, name));
npc.respawn(); npc.respawn();
context.halt(Component.text("The NPC's skin will now be resolved per-player from \"" + name + "\"")); context.halt(Component.text("The NPC's skin will now be resolved per-player from \"" + name + "\""));
} else if (type.equalsIgnoreCase("url")) { } else if (type.equalsIgnoreCase("url")) {
@ -90,6 +94,30 @@ public class SkinCommand implements CommandHandler {
context.send(Component.text("Invalid url!", NamedTextColor.RED)); context.send(Component.text("Invalid url!", NamedTextColor.RED));
} }
return; 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")); context.send(Component.text("Unknown skin type! Please use one of the following: mirror, static, dynamic, url"));
} }
@ -97,11 +125,19 @@ public class SkinCommand implements CommandHandler {
@Override @Override
public List<String> suggest(CommandContext context) throws CommandExecutionException { public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds()); if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds());
if (context.argSize() == 2) return context.suggestLiteral("mirror", "static", "dynamic", "url"); if (context.argSize() == 2) return context.suggestLiteral("mirror", "static", "dynamic", "url", "file");
if (context.matchSuggestion("*", "static")) return context.suggestPlayers(); if (context.matchSuggestion("*", "static")) return context.suggestPlayers();
if (context.argSize() == 3 && context.matchSuggestion("*", "url")) { if (context.argSize() == 3 && context.matchSuggestion("*", "url")) {
return context.suggestLiteral("slim", "classic"); 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(); return Collections.emptyList();
} }
} }

View file

@ -21,9 +21,14 @@ public class ToggleCommand implements CommandHandler {
@Override @Override
public void run(CommandContext context) throws CommandExecutionException { public void run(CommandContext context) throws CommandExecutionException {
context.setUsage(context.getLabel() + " toggle <id>"); context.setUsage(context.getLabel() + " toggle <id> [<enable/disable>]");
NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc(); NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc();
boolean enabled = !npc.isEnabled(); boolean enabled;
if (context.argSize() == 1) {
enabled = context.popString().equalsIgnoreCase("enable");
} else {
enabled = !npc.isEnabled();
}
npc.setEnabled(enabled); npc.setEnabled(enabled);
context.send(Component.text("NPC has been " + (enabled ? "enabled" : "disabled"), NamedTextColor.GREEN)); context.send(Component.text("NPC has been " + (enabled ? "enabled" : "disabled"), NamedTextColor.GREEN));
} }
@ -31,6 +36,7 @@ public class ToggleCommand implements CommandHandler {
@Override @Override
public List<String> suggest(CommandContext context) throws CommandExecutionException { public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds()); if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds());
if (context.argSize() == 2) return context.suggestLiteral("enable", "disable");
return Collections.emptyList(); return Collections.emptyList();
} }
} }

View file

@ -0,0 +1,66 @@
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)));
}
}

View file

@ -11,6 +11,7 @@ import lol.pyr.director.adventure.command.CommandHandler;
import lol.pyr.director.common.command.CommandExecutionException; import lol.pyr.director.common.command.CommandExecutionException;
import lol.pyr.znpcsplus.api.entity.EntityProperty; import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl; 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.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcImpl; import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
@ -44,6 +45,7 @@ public class PropertySetCommand implements CommandHandler {
// TODO: find a way to do this better & rewrite this mess // 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 (!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(); Class<?> type = property.getType();
Object value; Object value;
String valueName; String valueName;
@ -124,6 +126,15 @@ public class PropertySetCommand implements CommandHandler {
value = context.parse(type); value = context.parse(type);
valueName = value == null ? "NONE" : ((Vector3i) value).toPrettyString(); 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 { else {
try { try {
value = context.parse(type); value = context.parse(type);

View file

@ -7,13 +7,13 @@ import lol.pyr.znpcsplus.conversion.DataImporter;
import lol.pyr.znpcsplus.conversion.DataImporterRegistry; import lol.pyr.znpcsplus.conversion.DataImporterRegistry;
import lol.pyr.znpcsplus.npc.NpcEntryImpl; import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.util.FutureUtil;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
public class ImportCommand implements CommandHandler { public class ImportCommand implements CommandHandler {
private final NpcRegistryImpl npcRegistry; 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: " + if (importer == null) context.halt(Component.text("Importer not found! Possible importers: " +
String.join(", ", importerRegistry.getIds()), NamedTextColor.RED)); String.join(", ", importerRegistry.getIds()), NamedTextColor.RED));
CompletableFuture.runAsync(() -> { FutureUtil.exceptionPrintingRunAsync(() -> {
if (!importer.isValid()) { if (!importer.isValid()) {
context.send(Component.text("There is no data to import from this importer!", NamedTextColor.RED)); context.send(Component.text("There is no data to import from this importer!", NamedTextColor.RED));
return; return;

View file

@ -4,12 +4,12 @@ import lol.pyr.director.adventure.command.CommandContext;
import lol.pyr.director.adventure.command.CommandHandler; import lol.pyr.director.adventure.command.CommandHandler;
import lol.pyr.director.common.command.CommandExecutionException; import lol.pyr.director.common.command.CommandExecutionException;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.util.FutureUtil;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
public class LoadAllCommand implements CommandHandler { public class LoadAllCommand implements CommandHandler {
private final NpcRegistryImpl npcRegistry; private final NpcRegistryImpl npcRegistry;
@ -20,7 +20,7 @@ public class LoadAllCommand implements CommandHandler {
@Override @Override
public void run(CommandContext context) throws CommandExecutionException { public void run(CommandContext context) throws CommandExecutionException {
CompletableFuture.runAsync(() -> { FutureUtil.exceptionPrintingRunAsync(() -> {
npcRegistry.reload(); npcRegistry.reload();
context.send(Component.text("All NPCs have been re-loaded from storage", NamedTextColor.GREEN)); context.send(Component.text("All NPCs have been re-loaded from storage", NamedTextColor.GREEN));
}); });

View file

@ -11,6 +11,7 @@ import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl; import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.storage.NpcStorage; import lol.pyr.znpcsplus.storage.NpcStorage;
import lol.pyr.znpcsplus.storage.NpcStorageType; import lol.pyr.znpcsplus.storage.NpcStorageType;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -35,8 +36,9 @@ public class MigrateCommand implements CommandHandler {
private final NpcStorage currentStorage; private final NpcStorage currentStorage;
private final NpcStorageType currentStorageType; private final NpcStorageType currentStorageType;
private final NpcRegistryImpl npcRegistry; 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) { public MigrateCommand(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcStorage currentStorage, NpcStorageType currentStorageType, NpcRegistryImpl npcRegistry, NpcSerializerRegistryImpl serializerRegistry) {
this.configManager = configManager; this.configManager = configManager;
this.plugin = plugin; this.plugin = plugin;
this.packetFactory = packetFactory; this.packetFactory = packetFactory;
@ -47,6 +49,7 @@ public class MigrateCommand implements CommandHandler {
this.currentStorage = currentStorage; this.currentStorage = currentStorage;
this.currentStorageType = currentStorageType; this.currentStorageType = currentStorageType;
this.npcRegistry = npcRegistry; this.npcRegistry = npcRegistry;
this.serializerRegistry = serializerRegistry;
} }
@Override @Override
@ -63,7 +66,7 @@ public class MigrateCommand implements CommandHandler {
if (currentStorageType == from) { if (currentStorageType == from) {
fromStorage = currentStorage; fromStorage = currentStorage;
} else { } else {
fromStorage = from.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer); fromStorage = from.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, serializerRegistry);
if (fromStorage == null) { if (fromStorage == null) {
context.halt(Component.text("Failed to initialize the source storage. Please check the console for more information.", NamedTextColor.RED)); context.halt(Component.text("Failed to initialize the source storage. Please check the console for more information.", NamedTextColor.RED));
return; return;
@ -84,7 +87,7 @@ public class MigrateCommand implements CommandHandler {
if (currentStorageType == to) { if (currentStorageType == to) {
toStorage = currentStorage; toStorage = currentStorage;
} else { } else {
toStorage = to.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer); toStorage = to.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, serializerRegistry);
if (toStorage == null) { if (toStorage == null) {
context.halt(Component.text("Failed to initialize the destination storage. Please check the console for more information.", NamedTextColor.RED)); context.halt(Component.text("Failed to initialize the destination storage. Please check the console for more information.", NamedTextColor.RED));
return; return;

View file

@ -4,12 +4,12 @@ import lol.pyr.director.adventure.command.CommandContext;
import lol.pyr.director.adventure.command.CommandHandler; import lol.pyr.director.adventure.command.CommandHandler;
import lol.pyr.director.common.command.CommandExecutionException; import lol.pyr.director.common.command.CommandExecutionException;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.util.FutureUtil;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
public class SaveAllCommand implements CommandHandler { public class SaveAllCommand implements CommandHandler {
private final NpcRegistryImpl npcRegistry; private final NpcRegistryImpl npcRegistry;
@ -20,7 +20,7 @@ public class SaveAllCommand implements CommandHandler {
@Override @Override
public void run(CommandContext context) throws CommandExecutionException { public void run(CommandContext context) throws CommandExecutionException {
CompletableFuture.runAsync(() -> { FutureUtil.exceptionPrintingRunAsync(() -> {
npcRegistry.save(); npcRegistry.save();
context.send(Component.text("All NPCs have been saved to storage", NamedTextColor.GREEN)); context.send(Component.text("All NPCs have been saved to storage", NamedTextColor.GREEN));
}); });

View file

@ -24,7 +24,7 @@ public class HologramTrait extends SectionCitizensTrait {
if (linesSection != null) { if (linesSection != null) {
List<String> keys = new ArrayList<>(linesSection.getKeys(false)); List<String> keys = new ArrayList<>(linesSection.getKeys(false));
for (int i = keys.size() - 1; i >= 0; i--) { for (int i = keys.size() - 1; i >= 0; i--) {
String line = linesSection.getConfigurationSection(keys.get(i)).getString("text"); String line = linesSection.isConfigurationSection(keys.get(i)) ? linesSection.getConfigurationSection(keys.get(i)).getString("text") : linesSection.getString(keys.get(i));
if (line != null) { if (line != null) {
Component component = textSerializer.deserialize(line); Component component = textSerializer.deserialize(line);
npc.getHologram().addTextLineComponent(component); npc.getHologram().addTextLineComponent(component);

View file

@ -25,7 +25,7 @@ import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.scheduling.TaskScheduler; import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.SkinImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor; import lol.pyr.znpcsplus.skin.descriptor.NameFetchingDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor; import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor; import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
import lol.pyr.znpcsplus.util.BungeeConnector; import lol.pyr.znpcsplus.util.BungeeConnector;
@ -175,7 +175,7 @@ public class ZNpcImporter implements DataImporter {
} }
if (model.getSkinName() != null) { if (model.getSkinName() != null) {
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new FetchingDescriptor(skinCache, model.getSkinName())); npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new NameFetchingDescriptor(skinCache, model.getSkinName()));
} }
else if (model.getSkin() != null && model.getSignature() != null) { else if (model.getSkin() != null && model.getSignature() != null) {
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new SkinImpl(model.getSkin(), model.getSignature()))); npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new SkinImpl(model.getSkin(), model.getSignature())));
@ -189,12 +189,15 @@ public class ZNpcImporter implements DataImporter {
if (toggleValues.containsKey("mirror")) { if (toggleValues.containsKey("mirror")) {
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new MirrorDescriptor(skinCache)); npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new MirrorDescriptor(skinCache));
} }
if (toggleValues.containsKey("glow")) { if (toggleValues.containsKey("glow") && (boolean) toggleValues.get("glow")) {
if (!model.getGlowName().isEmpty())
try { try {
npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.valueOf((String) toggleValues.get("glow"))); npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.valueOf(model.getGlowName()));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.WHITE); npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.WHITE);
} }
else
npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.WHITE);
} }
} }

View file

@ -84,4 +84,8 @@ public class ZNpcsModel {
public String getSignature() { public String getSignature() {
return signature; return signature;
} }
public String getGlowName() {
return glowName;
}
} }

View file

@ -0,0 +1,76 @@
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());
}
}

View file

@ -47,16 +47,16 @@ public abstract class EntityPropertyImpl<T> implements EntityProperty<T> {
dependencies.add(property); dependencies.add(property);
} }
protected static <V> EntityData newEntityData(int index, EntityDataType<V> type, V value) { protected static <V> EntityData<V> newEntityData(int index, EntityDataType<V> type, V value) {
return new EntityData(index, type, value); return new EntityData<>(index, type, value);
} }
public List<EntityData> applyStandalone(Player player, PacketEntity packetEntity, boolean isSpawned) { public List<EntityData<?>> applyStandalone(Player player, PacketEntity packetEntity, boolean isSpawned) {
Map<Integer, EntityData> map = new HashMap<>(); Map<Integer, EntityData<?>> map = new HashMap<>();
apply(player, packetEntity, isSpawned, map); apply(player, packetEntity, isSpawned, map);
for (EntityPropertyImpl<?> property : dependencies) property.apply(player, packetEntity, isSpawned, map); for (EntityPropertyImpl<?> property : dependencies) property.apply(player, packetEntity, isSpawned, map);
return new ArrayList<>(map.values()); 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);
} }

View file

@ -2,6 +2,7 @@ package lol.pyr.znpcsplus.entity;
import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion; 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.data.EntityDataTypes;
import com.github.retrooper.packetevents.protocol.entity.pose.EntityPose; import com.github.retrooper.packetevents.protocol.entity.pose.EntityPose;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound; import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
@ -9,12 +10,12 @@ import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.protocol.nbt.NBTString; import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.protocol.player.EquipmentSlot; import com.github.retrooper.packetevents.protocol.player.EquipmentSlot;
import com.github.retrooper.packetevents.protocol.world.BlockFace; 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.EntityProperty;
import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry; import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.properties.*; 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.VillagerLevelProperty;
import lol.pyr.znpcsplus.entity.properties.villager.VillagerProfessionProperty; import lol.pyr.znpcsplus.entity.properties.villager.VillagerProfessionProperty;
import lol.pyr.znpcsplus.entity.properties.villager.VillagerTypeProperty; import lol.pyr.znpcsplus.entity.properties.villager.VillagerTypeProperty;
@ -32,18 +33,19 @@ import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* 1.8 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=7415">...</a> * 1.8 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2767708">...</a>
* 1.9 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=7968">...</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://wiki.vg/index.php?title=Entity_metadata&oldid=8241">...</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://wiki.vg/index.php?title=Entity_metadata&oldid=8534">...</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://wiki.vg/index.php?title=Entity_metadata&oldid=14048">...</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://wiki.vg/index.php?title=Entity_metadata&oldid=14800">...</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://wiki.vg/index.php?title=Entity_metadata&oldid=15240">...</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://wiki.vg/index.php?title=Entity_metadata&oldid=15991">...</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://wiki.vg/index.php?title=Entity_metadata&oldid=16539">...</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://wiki.vg/index.php?title=Entity_metadata&oldid=17521">...</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://wiki.vg/index.php?title=Entity_metadata&oldid=18191">...</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://wiki.vg/index.php?title=Entity_metadata">...</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>
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
@ -90,6 +92,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
registerEnumSerializer(Sound.class); registerEnumSerializer(Sound.class);
registerEnumSerializer(ArmadilloState.class); registerEnumSerializer(ArmadilloState.class);
registerEnumSerializer(WoldVariant.class); registerEnumSerializer(WoldVariant.class);
registerEnumSerializer(SkeletonType.class);
registerPrimitiveSerializers(Integer.class, Boolean.class, Double.class, Float.class, Long.class, Short.class, Byte.class, String.class); registerPrimitiveSerializers(Integer.class, Boolean.class, Double.class, Float.class, Long.class, Short.class, Byte.class, String.class);
@ -105,7 +108,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
*/ */
} }
public void registerTypes(ZNpcsPlusBootstrap plugin, PacketFactory packetFactory, LegacyComponentSerializer textSerializer, TaskScheduler taskScheduler) { public void registerTypes(PacketFactory packetFactory, LegacyComponentSerializer textSerializer, TaskScheduler taskScheduler) {
ServerVersion ver = PacketEvents.getAPI().getServerManager().getVersion(); ServerVersion ver = PacketEvents.getAPI().getServerManager().getVersion();
boolean legacyBooleans = ver.isOlderThan(ServerVersion.V_1_9); boolean legacyBooleans = ver.isOlderThan(ServerVersion.V_1_9);
boolean legacyNames = ver.isOlderThan(ServerVersion.V_1_9); boolean legacyNames = ver.isOlderThan(ServerVersion.V_1_9);
@ -124,11 +127,12 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
register(new DummyProperty<>("look", LookType.FIXED)); register(new DummyProperty<>("look", LookType.FIXED));
register(new DummyProperty<>("look_distance", configManager.getConfig().lookPropertyDistance())); 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<>("view_distance", configManager.getConfig().viewDistance()));
register(new DummyProperty<>("permission_required", false)); register(new DummyProperty<>("permission_required", false));
register(new ForceBodyRotationProperty(plugin, taskScheduler)); register(new ForceBodyRotationProperty(taskScheduler));
register(new DummyProperty<>("player_knockback", false)); register(new DummyProperty<>("player_knockback", false));
register(new DummyProperty<>("player_knockback_exempt_permission", String.class)); register(new DummyProperty<>("player_knockback_exempt_permission", String.class));
@ -151,6 +155,16 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
linkProperties("glow", "fire", "invisible"); linkProperties("glow", "fire", "invisible");
register(new BooleanProperty("silent", 4, false, legacyBooleans)); 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; final int tameableIndex;
if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) tameableIndex = 17; if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) tameableIndex = 17;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) tameableIndex = 16; else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) tameableIndex = 16;
@ -177,11 +191,13 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_9)) babyIndex = 11; else if (ver.isNewerThanOrEquals(ServerVersion.V_1_9)) babyIndex = 11;
else babyIndex = 12; else babyIndex = 12;
if (ver.isOlderThan(ServerVersion.V_1_9)) { if (ver.isOlderThan(ServerVersion.V_1_9)) {
register(new EncodedByteProperty<>("baby", false, babyIndex, obj -> (byte) (obj ? -1 : 0))); register(new LegacyBabyProperty(babyIndex));
} else { } else {
register(new BooleanProperty("baby", babyIndex, false, legacyBooleans)); register(new BooleanProperty("baby", babyIndex, false, legacyBooleans));
} }
register(new EntitySittingProperty(packetFactory, this));
// Player // Player
register(new DummyProperty<>("skin", SkinDescriptor.class, false)); register(new DummyProperty<>("skin", SkinDescriptor.class, false));
final int skinLayersIndex; final int skinLayersIndex;
@ -269,7 +285,9 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
else horseIndex = 16; else horseIndex = 16;
int horseEating = ver.isNewerThanOrEquals(ServerVersion.V_1_12) ? 0x10 : 0x20; int horseEating = ver.isNewerThanOrEquals(ServerVersion.V_1_12) ? 0x10 : 0x20;
register(new BitsetProperty("is_tame", horseIndex, 0x02, false, legacyBooleans)); 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_saddled", horseIndex, 0x04, false, legacyBooleans));
}
register(new BitsetProperty("is_eating", horseIndex, horseEating, false, legacyBooleans)); register(new BitsetProperty("is_eating", horseIndex, horseEating, false, legacyBooleans));
register(new BitsetProperty("is_rearing", horseIndex, horseEating << 1, false, legacyBooleans)); register(new BitsetProperty("is_rearing", horseIndex, horseEating << 1, false, legacyBooleans));
register(new BitsetProperty("has_mouth_open", horseIndex, horseEating << 2, false, legacyBooleans)); register(new BitsetProperty("has_mouth_open", horseIndex, horseEating << 2, false, legacyBooleans));
@ -337,10 +355,14 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
// Chested Horse // Chested Horse
if (ver.isOlderThan(ServerVersion.V_1_11)) { if (ver.isOlderThan(ServerVersion.V_1_11)) {
register(new BitsetProperty("has_chest", horseIndex, 0x08, false, legacyBooleans)); register(new BitsetProperty("has_chest", horseIndex, 0x08, false, legacyBooleans));
linkProperties("is_saddled", "has_chest", "is_eating", "is_rearing", "has_mouth_open"); linkProperties("is_tame", "is_saddled", "has_chest", "is_eating", "is_rearing", "has_mouth_open");
} else { } else {
register(new BooleanProperty("has_chest", horseVariantIndex, false, legacyBooleans)); register(new BooleanProperty("has_chest", horseVariantIndex, false, legacyBooleans));
linkProperties("is_saddled", "is_eating", "is_rearing", "has_mouth_open"); 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");
}
} }
// Slime, Magma Cube and Phantom // Slime, Magma Cube and Phantom
@ -436,6 +458,39 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
witherIndex += 3; // skip the first 3 indexes, will be used for the other properties later witherIndex += 3; // skip the first 3 indexes, will be used for the other properties later
register(new IntegerProperty("invulnerable_time", witherIndex, 0, false)); 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; if (!ver.isNewerThanOrEquals(ServerVersion.V_1_9)) return;
// Shulker // Shulker
int shulkerIndex; int shulkerIndex;
@ -483,7 +538,9 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) llamaIndex = 20; else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) llamaIndex = 20;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_14)) llamaIndex = 19; else if (ver.isNewerThanOrEquals(ServerVersion.V_1_14)) llamaIndex = 19;
else llamaIndex = 17; else llamaIndex = 17;
register(new EncodedIntegerProperty<DyeColor>("carpet_color", DyeColor.class, llamaIndex++, obj -> obj == null ? -1 : obj.ordinal()));
// 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<>("llama_variant", LlamaVariant.CREAMY, llamaIndex, Enum::ordinal)); register(new EncodedIntegerProperty<>("llama_variant", LlamaVariant.CREAMY, llamaIndex, Enum::ordinal));
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_12)) return; if (!ver.isNewerThanOrEquals(ServerVersion.V_1_12)) return;
@ -663,8 +720,20 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21)) return; if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21)) return;
register(new EquipmentProperty(packetFactory, "body", EquipmentSlot.BODY));
// Bogged // Bogged
register(new BooleanProperty("bogged_sheared", 16, false, legacyBooleans)); 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) { private void registerSerializer(PropertySerializer<?> serializer) {
@ -721,8 +790,8 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
} }
@Override @Override
public void registerDummy(String name, Class<?> type) { public void registerDummy(String name, Class<?> type, boolean playerModifiable) {
register(new DummyProperty<>(name, type)); register(new DummyProperty<>(name, type, playerModifiable));
} }
public EntityPropertyImpl<?> getByName(String name) { public EntityPropertyImpl<?> getByName(String name) {

View file

@ -8,27 +8,34 @@ import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.entity.PropertyHolder; import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.reflection.Reflections; import lol.pyr.znpcsplus.reflection.Reflections;
import lol.pyr.znpcsplus.util.FutureUtil;
import lol.pyr.znpcsplus.util.NpcLocation; import lol.pyr.znpcsplus.util.NpcLocation;
import lol.pyr.znpcsplus.util.Viewable;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.util.Collection; import java.util.*;
import java.util.Set; import java.util.concurrent.CompletableFuture;
import java.util.UUID;
public class PacketEntity implements PropertyHolder { public class PacketEntity implements PropertyHolder {
private final PacketFactory packetFactory; private final PacketFactory packetFactory;
private final PropertyHolder properties; private final PropertyHolder properties;
private final Viewable viewable;
private final int entityId; private final int entityId;
private final UUID uuid; private final UUID uuid;
private final EntityType type; private final EntityType type;
private NpcLocation location; private NpcLocation location;
public PacketEntity(PacketFactory packetFactory, PropertyHolder properties, EntityType type, NpcLocation location) { private PacketEntity vehicle;
private Integer vehicleId;
private List<Integer> passengers;
public PacketEntity(PacketFactory packetFactory, PropertyHolder properties, Viewable viewable, EntityType type, NpcLocation location) {
this.packetFactory = packetFactory; this.packetFactory = packetFactory;
this.properties = properties; this.properties = properties;
this.viewable = viewable;
this.entityId = reserveEntityID(); this.entityId = reserveEntityID();
this.uuid = UUID.randomUUID(); this.uuid = UUID.randomUUID();
this.type = type; this.type = type;
@ -51,22 +58,120 @@ public class PacketEntity implements PropertyHolder {
return type; return type;
} }
public void setLocation(NpcLocation location, Collection<Player> viewers) { public void setLocation(NpcLocation location) {
this.location = location; this.location = location;
for (Player viewer : viewers) packetFactory.teleportEntity(viewer, this); if (vehicle != null) {
vehicle.setLocation(location.withY(location.getY() - 0.9));
return;
}
for (Player viewer : viewable.getViewers()) packetFactory.teleportEntity(viewer, this);
} }
public void spawn(Player player) { public CompletableFuture<Void> spawn(Player player) {
if (type == EntityTypes.PLAYER) packetFactory.spawnPlayer(player, this, properties); return FutureUtil.exceptionPrintingRunAsync(() -> {
if (type == EntityTypes.PLAYER) packetFactory.spawnPlayer(player, this, properties).join();
else packetFactory.spawnEntity(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) { public void setHeadRotation(Player player, float yaw, float pitch) {
packetFactory.sendHeadRotation(player, this, yaw, 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) { public void despawn(Player player) {
packetFactory.destroyEntity(player, this, properties); packetFactory.destroyEntity(player, this, properties);
if (vehicle != null) vehicle.despawn(player);
} }
public void refreshMeta(Player player) { public void refreshMeta(Player player) {

View file

@ -31,13 +31,22 @@ public class BitsetProperty extends EntityPropertyImpl<Boolean> {
} }
@Override @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) {
EntityData oldData = properties.get(index); EntityData<?> oldData = properties.get(index);
boolean enabled = entity.getProperty(this); boolean enabled = entity.getProperty(this);
if (inverted) enabled = !enabled; if (inverted) enabled = !enabled;
properties.put(index, if (integer) {
integer ? newEntityData(index, EntityDataTypes.INT, (oldData == null ? 0 : (int) oldData.getValue()) | (enabled ? bitmask : 0)) : int oldValue = 0;
newEntityData(index, EntityDataTypes.BYTE, (byte) ((oldData == null ? 0 : (byte) oldData.getValue()) | (enabled ? bitmask : 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))));
}
} }
} }

View file

@ -25,7 +25,7 @@ public class BooleanProperty extends EntityPropertyImpl<Boolean> {
} }
@Override @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); boolean enabled = entity.getProperty(this);
if (inverted) enabled = !enabled; if (inverted) enabled = !enabled;
if (legacy) properties.put(index, newEntityData(index, EntityDataTypes.BYTE, (byte) (enabled ? 1 : 0))); if (legacy) properties.put(index, newEntityData(index, EntityDataTypes.BYTE, (byte) (enabled ? 1 : 0)));

View file

@ -20,7 +20,7 @@ public class CamelSittingProperty extends EntityPropertyImpl<Boolean> {
} }
@Override @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); boolean value = entity.getProperty(this);
if (value) { if (value) {
properties.put(poseIndex, newEntityData(poseIndex, EntityDataTypes.ENTITY_POSE, EntityPose.SITTING)); properties.put(poseIndex, newEntityData(poseIndex, EntityDataTypes.ENTITY_POSE, EntityPose.SITTING));

View file

@ -22,7 +22,7 @@ public class CustomTypeProperty<T, U> extends EntityPropertyImpl<T> {
} }
@Override @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)))); properties.put(index, newEntityData(index, type, decoder.decode(entity.getProperty(this))));
} }

View file

@ -1,7 +1,6 @@
package lol.pyr.znpcsplus.entity.properties; package lol.pyr.znpcsplus.entity.properties;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData; 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.protocol.entity.data.EntityDataTypes;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer; import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl; import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
@ -16,20 +15,21 @@ import java.util.Optional;
public class DinnerboneProperty extends EntityPropertyImpl<Boolean> { public class DinnerboneProperty extends EntityPropertyImpl<Boolean> {
private final boolean optional; private final boolean optional;
private final Object serialized; private final Object serialized;
private final EntityDataType<?> type;
public DinnerboneProperty(boolean legacy, boolean optional) { public DinnerboneProperty(boolean legacy, boolean optional) {
super("dinnerbone", false, Boolean.class); super("dinnerbone", false, Boolean.class);
this.optional = optional; this.optional = optional;
Component name = Component.text("Dinnerbone"); Component name = Component.text("Dinnerbone");
Object serialized = legacy ? AdventureSerializer.getLegacyGsonSerializer().serialize(name) : this.serialized = legacy ? AdventureSerializer.serializer().legacy().serialize(name) :
optional ? name : LegacyComponentSerializer.legacySection().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 @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(2, new EntityData(2, type, entity.getProperty(this) ? serialized : optional ? null : "")); 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 : ""));
}
} }
} }

View file

@ -28,6 +28,6 @@ public class DummyProperty<T> extends EntityPropertyImpl<T> {
} }
@Override @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) {
} }
} }

View file

@ -36,7 +36,7 @@ public class EncodedByteProperty<T> extends EntityPropertyImpl<T> {
} }
@Override @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); T value = entity.getProperty(this);
if (value == null) return; if (value == null) return;
properties.put(index, newEntityData(index, type, decoder.decode(value))); properties.put(index, newEntityData(index, type, decoder.decode(value)));

View file

@ -36,7 +36,7 @@ public class EncodedIntegerProperty<T> extends EntityPropertyImpl<T> {
} }
@Override @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); T value = entity.getProperty(this);
if (value == null) return; if (value == null) return;
properties.put(index, newEntityData(index, type, decoder.decode(value))); properties.put(index, newEntityData(index, type, decoder.decode(value)));

View file

@ -36,7 +36,7 @@ public class EncodedStringProperty<T> extends EntityPropertyImpl<T> {
} }
@Override @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); T value = entity.getProperty(this);
if (value == null) return; if (value == null) return;
properties.put(index, newEntityData(index, type, decoder.decode(value))); properties.put(index, newEntityData(index, type, decoder.decode(value)));

View file

@ -0,0 +1,37 @@
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);
}
}
}

View file

@ -22,7 +22,7 @@ public class EquipmentProperty extends EntityPropertyImpl<ItemStack> {
} }
@Override @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))); packetFactory.sendEquipment(player, entity, new Equipment(slot, entity.getProperty(this)));
} }
} }

View file

@ -1,7 +1,6 @@
package lol.pyr.znpcsplus.entity.properties; package lol.pyr.znpcsplus.entity.properties;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData; import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import lol.pyr.znpcsplus.ZNpcsPlusBootstrap;
import lol.pyr.znpcsplus.entity.PacketEntity; import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.scheduling.TaskScheduler; import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -9,17 +8,15 @@ import org.bukkit.entity.Player;
import java.util.Map; import java.util.Map;
public class ForceBodyRotationProperty extends DummyProperty<Boolean> { public class ForceBodyRotationProperty extends DummyProperty<Boolean> {
private final ZNpcsPlusBootstrap plugin;
private final TaskScheduler scheduler; private final TaskScheduler scheduler;
public ForceBodyRotationProperty(ZNpcsPlusBootstrap plugin, TaskScheduler scheduler) { public ForceBodyRotationProperty(TaskScheduler scheduler) {
super("force_body_rotation", false); super("force_body_rotation", false);
this.plugin = plugin;
this.scheduler = scheduler; this.scheduler = scheduler;
} }
@Override @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)) { if (entity.getProperty(this)) {
scheduler.runLaterAsync(() -> entity.swingHand(player, false), 2L); scheduler.runLaterAsync(() -> entity.swingHand(player, false), 2L);
scheduler.runLaterAsync(() -> entity.swingHand(player, false), 6L); scheduler.runLaterAsync(() -> entity.swingHand(player, false), 6L);

View file

@ -19,12 +19,19 @@ public class GlowProperty extends EntityPropertyImpl<NamedColor> {
} }
@Override @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); NamedColor value = entity.getProperty(this);
EntityData oldData = properties.get(0); EntityData<?> oldData = properties.get(0);
byte oldValue = oldData == null ? 0 : (byte) oldData.getValue(); // byte oldValue = oldData == null ? 0 : (byte) oldData.getValue();
byte oldValue = 0;
if (oldData != null && oldData.getValue() instanceof Number) {
oldValue = ((Number) oldData.getValue()).byteValue();
}
properties.put(0, newEntityData(0, EntityDataTypes.BYTE, (byte) (oldValue | (value == null ? 0 : 0x40)))); properties.put(0, newEntityData(0, EntityDataTypes.BYTE, (byte) (oldValue | (value == null ? 0 : 0x40))));
if (isSpawned) packetFactory.removeTeam(player, entity); // 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);
packetFactory.createTeam(player, entity, value); packetFactory.createTeam(player, entity, value);
} }
} }
}

View file

@ -0,0 +1,27 @@
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));
}
}

View file

@ -17,7 +17,7 @@ public class HologramItemProperty extends EntityPropertyImpl<ItemStack> {
} }
@Override @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(8, newEntityData(8, EntityDataTypes.ITEMSTACK, entity.getProperty(this)));
properties.put(5, newEntityData(5, EntityDataTypes.BOOLEAN, true)); properties.put(5, newEntityData(5, EntityDataTypes.BOOLEAN, true));
} }

View file

@ -18,9 +18,13 @@ public class HorseColorProperty extends EntityPropertyImpl<HorseColor> {
} }
@Override @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) {
EntityData oldData = properties.get(index); EntityData<?> oldData = properties.get(index);
HorseColor value = entity.getProperty(this); HorseColor value = entity.getProperty(this);
properties.put(index, newEntityData(index, EntityDataTypes.INT, value.ordinal() | (oldData == null ? 0 : ((int) oldData.getValue() & 0xFF00)))); 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));
} }
} }

View file

@ -18,9 +18,13 @@ public class HorseStyleProperty extends EntityPropertyImpl<HorseStyle> {
} }
@Override @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) {
EntityData oldData = properties.get(index); EntityData<?> oldData = properties.get(index);
HorseStyle value = entity.getProperty(this); HorseStyle value = entity.getProperty(this);
properties.put(index, newEntityData(index, EntityDataTypes.INT, (oldData == null ? 0 : ((int) oldData.getValue() & 0x00FF)) | (value.ordinal() << 8)));
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));
} }
} }

View file

@ -23,7 +23,7 @@ public class IntegerProperty extends EntityPropertyImpl<Integer> {
} }
@Override @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 ? properties.put(index, legacy ?
newEntityData(index, EntityDataTypes.BYTE, (byte) entity.getProperty(this).intValue()) : newEntityData(index, EntityDataTypes.BYTE, (byte) entity.getProperty(this).intValue()) :
newEntityData(index, EntityDataTypes.INT, entity.getProperty(this))); newEntityData(index, EntityDataTypes.INT, entity.getProperty(this)));

View file

@ -0,0 +1,29 @@
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)));
}
}
}

View file

@ -48,7 +48,7 @@ public class NBTProperty<T> extends EntityPropertyImpl<T> {
} }
@Override @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); T value = entity.getProperty(this);
if (value == null && !allowNull) return; if (value == null && !allowNull) return;
properties.put(index, newEntityData(index, type, decoder.decode(value))); properties.put(index, newEntityData(index, type, decoder.decode(value)));

View file

@ -27,14 +27,17 @@ public class NameProperty extends EntityPropertyImpl<Component> {
} }
@Override @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); Component value = entity.getProperty(this);
if (value != null) { if (value != null) {
value = PapiUtil.set(legacySerializer, player, value); value = PapiUtil.set(legacySerializer, player, value);
Object serialized = legacySerialization ? AdventureSerializer.getLegacyGsonSerializer().serialize(value) : if (legacySerialization) {
optional ? value : LegacyComponentSerializer.legacySection().serialize(value); properties.put(2, newEntityData(2, EntityDataTypes.STRING, AdventureSerializer.serializer().asJson(value)));
if (optional) properties.put(2, new EntityData(2, EntityDataTypes.OPTIONAL_ADV_COMPONENT, Optional.of(serialized))); } else if (optional) {
else properties.put(2, new EntityData(2, EntityDataTypes.STRING, serialized)); properties.put(2, newEntityData(2, EntityDataTypes.OPTIONAL_ADV_COMPONENT, Optional.of(value)));
} else {
properties.put(2, newEntityData(2, EntityDataTypes.STRING, LegacyComponentSerializer.legacySection().serialize(value)));
}
} }
if (legacySerialization) properties.put(3, newEntityData(3, EntityDataTypes.BYTE, (byte) (value != null ? 1 : 0))); if (legacySerialization) properties.put(3, newEntityData(3, EntityDataTypes.BYTE, (byte) (value != null ? 1 : 0)));

View file

@ -19,10 +19,10 @@ public class OptionalBlockPosProperty extends EntityPropertyImpl<Vector3i> {
} }
@Override @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); Vector3i value = entity.getProperty(this);
if (value == null) properties.put(index, new EntityData(index, EntityDataTypes.OPTIONAL_BLOCK_POSITION, Optional.empty())); 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, 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())))); Optional.of(new com.github.retrooper.packetevents.util.Vector3i(value.getX(), value.getY(), value.getZ()))));
} }
} }

View file

@ -1,7 +1,6 @@
package lol.pyr.znpcsplus.entity.properties; package lol.pyr.znpcsplus.entity.properties;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData; 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.protocol.entity.data.EntityDataTypes;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer; import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl; import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
@ -17,31 +16,38 @@ import java.util.Optional;
public class RabbitTypeProperty extends EntityPropertyImpl<RabbitType> { public class RabbitTypeProperty extends EntityPropertyImpl<RabbitType> {
private final int index; private final int index;
private final boolean legacyBooleans; private final boolean legacyBooleans;
private final boolean optional;
private final Object serialized; private final Object serialized;
private final EntityDataType<?> type;
public RabbitTypeProperty(int index, boolean legacyBooleans, boolean legacyNames, boolean optional) { public RabbitTypeProperty(int index, boolean legacyBooleans, boolean legacyNames, boolean optional) {
super("rabbit_type", RabbitType.BROWN, RabbitType.class); super("rabbit_type", RabbitType.BROWN, RabbitType.class);
this.index = index; this.index = index;
this.legacyBooleans = legacyBooleans; this.legacyBooleans = legacyBooleans;
this.optional = optional;
Component name = Component.text("Toast"); Component name = Component.text("Toast");
Object serialized = legacyNames ? AdventureSerializer.getLegacyGsonSerializer().serialize(name) : this.serialized = legacyNames ? AdventureSerializer.serializer().legacy().serialize(name) :
optional ? name : LegacyComponentSerializer.legacySection().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 @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); RabbitType rabbitType = entity.getProperty(this);
if (rabbitType == null) return; if (rabbitType == null) return;
if (!rabbitType.equals(RabbitType.TOAST)) { if (!rabbitType.equals(RabbitType.TOAST)) {
properties.put(index, legacyBooleans ? properties.put(index, legacyBooleans ?
newEntityData(index, EntityDataTypes.BYTE, (byte) rabbitType.getId()) : newEntityData(index, EntityDataTypes.BYTE, (byte) rabbitType.getId()) :
newEntityData(index, EntityDataTypes.INT, rabbitType.getId())); newEntityData(index, EntityDataTypes.INT, rabbitType.getId()));
properties.put(2, new EntityData(2, type, null)); if (optional) {
properties.put(2, new EntityData<>(2, EntityDataTypes.OPTIONAL_ADV_COMPONENT, Optional.empty()));
} else { } else {
properties.put(2, new EntityData(2, type, serialized)); 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));
}
} }
} }
} }

View file

@ -18,7 +18,7 @@ public class RotationProperty extends EntityPropertyImpl<Vector3f> {
} }
@Override @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); 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()))); properties.put(index, newEntityData(index, EntityDataTypes.ROTATION, new com.github.retrooper.packetevents.util.Vector3f(vec.getX(), vec.getY(), vec.getZ())));
} }

View file

@ -18,7 +18,7 @@ public class TargetNpcProperty extends EntityPropertyImpl<NpcEntryImpl> {
} }
@Override @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); NpcEntryImpl value = entity.getProperty(this);
if (value == null) return; if (value == null) return;
if (value.getNpc().getEntity().getEntityId() == entity.getEntityId()) return; if (value.getNpc().getEntity().getEntityId() == entity.getEntityId()) return;

View file

@ -25,15 +25,14 @@ public class TropicalFishVariantProperty<T> extends EntityPropertyImpl<T> {
} }
@Override @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); T value = entity.getProperty(this);
if (value == null) { if (value == null) return;
return;
} EntityData<?> oldData = properties.get(index);
EntityData oldData = properties.get(index);
TropicalFishVariant.Builder builder; TropicalFishVariant.Builder builder;
if (oldData != null && oldData.getType() == EntityDataTypes.INT && oldData.getValue() != null) { if (oldData != null && oldData.getType() == EntityDataTypes.INT && oldData.getValue() instanceof Integer) {
int oldVal = (int) oldData.getValue(); int oldVal = (Integer) oldData.getValue();
builder = TropicalFishVariant.Builder.fromInt(oldVal); builder = TropicalFishVariant.Builder.fromInt(oldVal);
} else { } else {
builder = new TropicalFishVariant.Builder(); builder = new TropicalFishVariant.Builder();

View file

@ -0,0 +1,63 @@
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;
}
}

View file

@ -21,8 +21,8 @@ public abstract class VillagerDataProperty<T> extends EntityPropertyImpl<T> {
} }
@Override @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) {
EntityData oldData = properties.get(index); EntityData<?> oldData = properties.get(index);
VillagerData old = oldData == null ? new VillagerData(VillagerTypes.PLAINS, VillagerProfessions.NONE, 1) : (VillagerData) oldData.getValue(); 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)))); properties.put(index, newEntityData(index, EntityDataTypes.VILLAGER_DATA, apply(old, entity.getProperty(this))));
} }

View file

@ -6,6 +6,7 @@ import lol.pyr.znpcsplus.api.hologram.Hologram;
import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.util.FutureUtil;
import lol.pyr.znpcsplus.util.NpcLocation; import lol.pyr.znpcsplus.util.NpcLocation;
import lol.pyr.znpcsplus.util.Viewable; import lol.pyr.znpcsplus.util.Viewable;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -16,6 +17,8 @@ import org.bukkit.entity.Player;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class HologramImpl extends Viewable implements Hologram { public class HologramImpl extends Viewable implements Hologram {
private final ConfigManager configManager; private final ConfigManager configManager;
@ -38,14 +41,15 @@ public class HologramImpl extends Viewable implements Hologram {
} }
public void addTextLineComponent(Component line) { public void addTextLineComponent(Component line) {
HologramText newLine = new HologramText(propertyRegistry, packetFactory, null, line); HologramText newLine = new HologramText(this, propertyRegistry, packetFactory, null, line);
lines.add(newLine); lines.add(newLine);
relocateLines(newLine); relocateLines();
for (Player viewer : getViewers()) newLine.show(viewer.getPlayer()); for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
} }
public void addTextLine(String line) { public void addTextLine(String line) {
addTextLineComponent(textSerializer.deserialize(textSerializer.serialize(MiniMessage.miniMessage().deserialize(line)))); Component component = line.contains("§") ? Component.text(line) : MiniMessage.miniMessage().deserialize(line);
addTextLineComponent(textSerializer.deserialize(textSerializer.serialize(component)));
} }
public void addItemLineStack(org.bukkit.inventory.ItemStack item) { public void addItemLineStack(org.bukkit.inventory.ItemStack item) {
@ -57,9 +61,9 @@ public class HologramImpl extends Viewable implements Hologram {
} }
public void addItemLinePEStack(ItemStack item) { public void addItemLinePEStack(ItemStack item) {
HologramItem newLine = new HologramItem(propertyRegistry, packetFactory, null, item); HologramItem newLine = new HologramItem(this, propertyRegistry, packetFactory, null, item);
lines.add(newLine); lines.add(newLine);
relocateLines(newLine); relocateLines();
for (Player viewer : getViewers()) newLine.show(viewer.getPlayer()); for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
} }
@ -105,9 +109,9 @@ public class HologramImpl extends Viewable implements Hologram {
} }
public void insertTextLineComponent(int index, Component line) { public void insertTextLineComponent(int index, Component line) {
HologramText newLine = new HologramText(propertyRegistry, packetFactory, null, line); HologramText newLine = new HologramText(this, propertyRegistry, packetFactory, null, line);
lines.add(index, newLine); lines.add(index, newLine);
relocateLines(newLine); relocateLines();
for (Player viewer : getViewers()) newLine.show(viewer.getPlayer()); for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
} }
@ -120,9 +124,9 @@ public class HologramImpl extends Viewable implements Hologram {
} }
public void insertItemLinePEStack(int index, ItemStack item) { public void insertItemLinePEStack(int index, ItemStack item) {
HologramItem newLine = new HologramItem(propertyRegistry, packetFactory, null, item); HologramItem newLine = new HologramItem(this, propertyRegistry, packetFactory, null, item);
lines.add(index, newLine); lines.add(index, newLine);
relocateLines(newLine); relocateLines();
for (Player viewer : getViewers()) newLine.show(viewer.getPlayer()); for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
} }
@ -144,8 +148,10 @@ public class HologramImpl extends Viewable implements Hologram {
} }
@Override @Override
protected void UNSAFE_show(Player player) { protected CompletableFuture<Void> UNSAFE_show(Player player) {
for (HologramLine<?> line : lines) line.show(player); return FutureUtil.allOf(lines.stream()
.map(line -> line.show(player))
.collect(Collectors.toList()));
} }
@Override @Override
@ -178,14 +184,10 @@ public class HologramImpl extends Viewable implements Hologram {
} }
private void relocateLines() { private void relocateLines() {
relocateLines(null);
}
private void relocateLines(HologramLine<?> newLine) {
final double lineSpacing = configManager.getConfig().lineSpacing(); final double lineSpacing = configManager.getConfig().lineSpacing();
double height = location.getY() + (lines.size() - 1) * lineSpacing + getOffset(); double height = location.getY() + (lines.size() - 1) * lineSpacing + getOffset();
for (HologramLine<?> line : lines) { for (HologramLine<?> line : lines) {
line.setLocation(location.withY(height), line == newLine ? Collections.emptySet() : getViewers()); line.setLocation(location.withY(height));
height -= lineSpacing; height -= lineSpacing;
} }
} }

View file

@ -15,13 +15,11 @@ import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.util.NpcLocation; import lol.pyr.znpcsplus.util.NpcLocation;
import org.bukkit.entity.Player; import lol.pyr.znpcsplus.util.Viewable;
import java.util.Collection;
public class HologramItem extends HologramLine<ItemStack> { public class HologramItem extends HologramLine<ItemStack> {
public HologramItem(EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, ItemStack item) { public HologramItem(Viewable viewable, EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, ItemStack item) {
super(item, packetFactory, EntityTypes.ITEM, location); super(viewable, item, packetFactory, EntityTypes.ITEM, location);
addProperty(propertyRegistry.getByName("holo_item")); addProperty(propertyRegistry.getByName("holo_item"));
} }
@ -33,8 +31,8 @@ public class HologramItem extends HologramLine<ItemStack> {
} }
@Override @Override
public void setLocation(NpcLocation location, Collection<Player> viewers) { public void setLocation(NpcLocation location) {
super.setLocation(location.withY(location.getY() + 2.05), viewers); super.setLocation(location.withY(location.getY() + 2.05));
} }
public static boolean ensureValidItemInput(String in) { public static boolean ensureValidItemInput(String in) {

View file

@ -7,21 +7,22 @@ import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.entity.PacketEntity; import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.util.NpcLocation; import lol.pyr.znpcsplus.util.NpcLocation;
import lol.pyr.znpcsplus.util.Viewable;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture;
public class HologramLine<M> implements PropertyHolder { public class HologramLine<M> implements PropertyHolder {
private M value; private M value;
private final PacketEntity entity; private final PacketEntity entity;
private final Set<EntityProperty<?>> properties; private final Set<EntityProperty<?>> properties;
public HologramLine(M value, PacketFactory packetFactory, EntityType type, NpcLocation location) { public HologramLine(Viewable viewable, M value, PacketFactory packetFactory, EntityType type, NpcLocation location) {
this.value = value; this.value = value;
this.entity = new PacketEntity(packetFactory, this, type, location); this.entity = new PacketEntity(packetFactory, this, viewable, type, location);
this.properties = new HashSet<>(); this.properties = new HashSet<>();
} }
@ -37,16 +38,16 @@ public class HologramLine<M> implements PropertyHolder {
entity.refreshMeta(player); entity.refreshMeta(player);
} }
protected void show(Player player) { protected CompletableFuture<Void> show(Player player) {
entity.spawn(player); return entity.spawn(player);
} }
protected void hide(Player player) { protected void hide(Player player) {
entity.despawn(player); entity.despawn(player);
} }
public void setLocation(NpcLocation location, Collection<Player> viewers) { public void setLocation(NpcLocation location) {
entity.setLocation(location, viewers); entity.setLocation(location);
} }
public int getEntityId() { public int getEntityId() {

View file

@ -5,24 +5,26 @@ import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.util.NpcLocation; import lol.pyr.znpcsplus.util.NpcLocation;
import lol.pyr.znpcsplus.util.Viewable;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public class HologramText extends HologramLine<Component> { public class HologramText extends HologramLine<Component> {
private static final Component BLANK = Component.text("%blank%"); private static final Component BLANK = Component.text("%blank%");
public HologramText(EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, Component text) { public HologramText(Viewable viewable, EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, Component text) {
super(text, packetFactory, EntityTypes.ARMOR_STAND, location); super(viewable, text, packetFactory, EntityTypes.ARMOR_STAND, location);
addProperty(propertyRegistry.getByName("name")); addProperty(propertyRegistry.getByName("name"));
addProperty(propertyRegistry.getByName("invisible")); addProperty(propertyRegistry.getByName("invisible"));
} }
@Override @Override
public void show(Player player) { public CompletableFuture<Void> show(Player player) {
if (!getValue().equals(BLANK)) { if (getValue().equals(BLANK)) return CompletableFuture.completedFuture(null);
super.show(player); return super.show(player);
}
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View file

@ -32,7 +32,7 @@ public class SwitchServerAction extends InteractionActionImpl {
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, .hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT,
Component.text("Click to edit this action", NamedTextColor.GRAY))) Component.text("Click to edit this action", NamedTextColor.GRAY)))
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND,
"/" + context.getLabel() + " action edit " + id + " " + index + " switcserver " + getInteractionType().name() + " " + getCooldown()/1000 + " " + getDelay() + " " + server)) "/" + context.getLabel() + " action edit " + id + " " + index + " switchserver " + getInteractionType().name() + " " + getCooldown()/1000 + " " + getDelay() + " " + server))
.append(Component.text(" | ", NamedTextColor.GRAY)) .append(Component.text(" | ", NamedTextColor.GRAY))
.append(Component.text("[DELETE]", NamedTextColor.RED) .append(Component.text("[DELETE]", NamedTextColor.RED)
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, .hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT,

View file

@ -20,9 +20,12 @@ import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class NpcImpl extends Viewable implements Npc { public class NpcImpl extends Viewable implements Npc {
@ -38,6 +41,8 @@ public class NpcImpl extends Viewable implements Npc {
private final Map<EntityPropertyImpl<?>, Object> propertyMap = new HashMap<>(); private final Map<EntityPropertyImpl<?>, Object> propertyMap = new HashMap<>();
private final List<InteractionAction> actions = new ArrayList<>(); 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) { 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); this(uuid, propertyRegistry, configManager, packetFactory, textSerializer, world.getName(), type, location);
} }
@ -48,14 +53,14 @@ public class NpcImpl extends Viewable implements Npc {
this.type = type; this.type = type;
this.location = location; this.location = location;
this.uuid = uuid; this.uuid = uuid;
entity = new PacketEntity(packetFactory, this, type.getType(), location); entity = new PacketEntity(packetFactory, this, this, type.getType(), location);
hologram = new HologramImpl(propertyRegistry, configManager, packetFactory, textSerializer, location.withY(location.getY() + type.getHologramOffset())); hologram = new HologramImpl(propertyRegistry, configManager, packetFactory, textSerializer, location.withY(location.getY() + type.getHologramOffset()));
} }
public void setType(NpcTypeImpl type) { public void setType(NpcTypeImpl type) {
UNSAFE_hideAll(); UNSAFE_hideAll();
this.type = type; this.type = type;
entity = new PacketEntity(packetFactory, this, type.getType(), entity.getLocation()); entity = new PacketEntity(packetFactory, this, this, type.getType(), entity.getLocation());
hologram.setLocation(location.withY(location.getY() + type.getHologramOffset())); hologram.setLocation(location.withY(location.getY() + type.getHologramOffset()));
UNSAFE_showAll(); UNSAFE_showAll();
} }
@ -85,20 +90,34 @@ public class NpcImpl extends Viewable implements Npc {
public void setLocation(NpcLocation location) { public void setLocation(NpcLocation location) {
this.location = location; this.location = location;
entity.setLocation(location, getViewers()); playerLookMap.clear();
playerLookMap.putAll(getViewers().stream().collect(Collectors.toMap(Player::getUniqueId, player -> new float[]{location.getYaw(), location.getPitch()})));
entity.setLocation(location);
hologram.setLocation(location.withY(location.getY() + type.getHologramOffset())); hologram.setLocation(location.withY(location.getY() + type.getHologramOffset()));
} }
public void setHeadRotation(Player player, float yaw, float pitch) { 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); entity.setHeadRotation(player, yaw, pitch);
} }
public void setHeadRotation(float yaw, float pitch) { public void setHeadRotation(float yaw, float pitch) {
for (Player player : getViewers()) { 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); 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() { public HologramImpl getHologram() {
return hologram; return hologram;
} }
@ -125,13 +144,14 @@ public class NpcImpl extends Viewable implements Npc {
} }
@Override @Override
protected void UNSAFE_show(Player player) { protected CompletableFuture<Void> UNSAFE_show(Player player) {
entity.spawn(player); playerLookMap.put(player.getUniqueId(), new float[]{location.getYaw(), location.getPitch()});
hologram.show(player); return CompletableFuture.allOf(entity.spawn(player), hologram.show(player));
} }
@Override @Override
protected void UNSAFE_hide(Player player) { protected void UNSAFE_hide(Player player) {
playerLookMap.remove(player.getUniqueId());
entity.despawn(player); entity.despawn(player);
hologram.hide(player); hologram.hide(player);
} }
@ -139,7 +159,7 @@ public class NpcImpl extends Viewable implements Npc {
private <T> void UNSAFE_refreshProperty(EntityPropertyImpl<T> property) { private <T> void UNSAFE_refreshProperty(EntityPropertyImpl<T> property) {
if (!type.isAllowedProperty(property)) return; if (!type.isAllowedProperty(property)) return;
for (Player viewer : getViewers()) { 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); if (!data.isEmpty()) packetFactory.sendMetadata(viewer, entity, data);
} }
} }
@ -212,7 +232,8 @@ public class NpcImpl extends Viewable implements Npc {
} }
@Override @Override
public void addAction(InteractionAction action) { public void addAction(InteractionAction action) throws IllegalArgumentException {
if (action == null) throw new IllegalArgumentException("action can not be null");
actions.add(action); actions.add(action);
} }
@ -222,7 +243,8 @@ public class NpcImpl extends Viewable implements Npc {
} }
@Override @Override
public void editAction(int index, InteractionAction action) { public void editAction(int index, InteractionAction action) throws IllegalArgumentException {
if (action == null) throw new IllegalArgumentException("action can not be null");
actions.set(index, action); actions.set(index, action);
} }
@ -246,4 +268,29 @@ public class NpcImpl extends Viewable implements Npc {
public void swingHand(boolean offHand) { public void swingHand(boolean offHand) {
for (Player viewer : getViewers()) entity.swingHand(viewer, 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);
}
} }

View file

@ -14,6 +14,7 @@ import lol.pyr.znpcsplus.hologram.HologramText;
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl; import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.scheduling.TaskScheduler; import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.storage.NpcStorage; import lol.pyr.znpcsplus.storage.NpcStorage;
import lol.pyr.znpcsplus.storage.NpcStorageType; import lol.pyr.znpcsplus.storage.NpcStorageType;
import lol.pyr.znpcsplus.util.NpcLocation; import lol.pyr.znpcsplus.util.NpcLocation;
@ -35,13 +36,13 @@ public class NpcRegistryImpl implements NpcRegistry {
private final Map<String, NpcEntryImpl> npcIdLookupMap = new HashMap<>(); private final Map<String, NpcEntryImpl> npcIdLookupMap = new HashMap<>();
private final Map<UUID, NpcEntryImpl> npcUuidLookupMap = 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, LegacyComponentSerializer textSerializer) { public NpcRegistryImpl(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, TaskScheduler scheduler, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, NpcSerializerRegistryImpl serializerRegistry, LegacyComponentSerializer textSerializer) {
this.textSerializer = textSerializer; this.textSerializer = textSerializer;
this.propertyRegistry = propertyRegistry; this.propertyRegistry = propertyRegistry;
storage = configManager.getConfig().storageType().create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer); storage = configManager.getConfig().storageType().create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, serializerRegistry);
if (storage == null) { if (storage == null) {
Bukkit.getLogger().warning("Failed to initialize storage, falling back to YAML"); Bukkit.getLogger().warning("Failed to initialize storage, falling back to YAML");
storage = NpcStorageType.YAML.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer); storage = NpcStorageType.YAML.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, serializerRegistry);
} }
this.packetFactory = packetFactory; this.packetFactory = packetFactory;
this.configManager = configManager; this.configManager = configManager;
@ -52,7 +53,13 @@ public class NpcRegistryImpl implements NpcRegistry {
} }
} }
@Override
public void register(NpcEntry entry) {
register((NpcEntryImpl) entry);
}
private void register(NpcEntryImpl entry) { private void register(NpcEntryImpl entry) {
if (entry == null) throw new NullPointerException();
unregister(npcIdLookupMap.put(entry.getId(), entry)); unregister(npcIdLookupMap.put(entry.getId(), entry));
unregister(npcUuidLookupMap.put(entry.getNpc().getUuid(), entry)); unregister(npcUuidLookupMap.put(entry.getNpc().getUuid(), entry));
npcList.add(entry); npcList.add(entry);
@ -195,6 +202,14 @@ public class NpcRegistryImpl implements NpcRegistry {
storage.deleteNpc(entry); 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) { public void switchIds(String oldId, String newId) {
NpcEntryImpl entry = getById(oldId); NpcEntryImpl entry = getById(oldId);
delete(oldId); delete(oldId);

View file

@ -114,12 +114,15 @@ public class NpcTypeImpl implements NpcType {
public NpcTypeImpl build() { public NpcTypeImpl build() {
ServerVersion version = PacketEvents.getAPI().getServerManager().getVersion(); ServerVersion version = PacketEvents.getAPI().getServerManager().getVersion();
addProperties("fire", "invisible", "silent", "look", "look_distance", "view_distance", addProperties("fire", "invisible", "silent", "look", "look_distance", "look_return", "view_distance",
"potion_color", "potion_ambient", "display_name", "permission_required", "potion_color", "potion_ambient", "display_name", "permission_required",
"player_knockback", "player_knockback_exempt_permission", "player_knockback_distance", "player_knockback_vertical", "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_horizontal", "player_knockback_cooldown", "player_knockback_sound", "player_knockback_sound_name",
"player_knockback_sound_volume", "player_knockback_sound_pitch"); "player_knockback_sound_volume", "player_knockback_sound_pitch");
if (!type.equals(EntityTypes.PLAYER)) addProperties("dinnerbone"); 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 // 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_9)) addProperties("glow");
if (version.isNewerThanOrEquals(ServerVersion.V_1_14)) { if (version.isNewerThanOrEquals(ServerVersion.V_1_14)) {
@ -143,6 +146,9 @@ public class NpcTypeImpl implements NpcType {
} else if (version.isOlderThan(ServerVersion.V_1_11) && type.equals(EntityTypes.HORSE)) { } else if (version.isOlderThan(ServerVersion.V_1_11) && type.equals(EntityTypes.HORSE)) {
addProperties("has_chest"); 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)) { if (EntityTypes.isTypeInstanceOf(type, EntityTypes.ABSTRACT_EVO_ILLU_ILLAGER)) {
addProperties("spell"); addProperties("spell");
} }
@ -174,6 +180,35 @@ public class NpcTypeImpl implements NpcType {
if (version.isNewerThanOrEquals(ServerVersion.V_1_20_5)) { if (version.isNewerThanOrEquals(ServerVersion.V_1_20_5)) {
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.WOLF)) { if (EntityTypes.isTypeInstanceOf(type, EntityTypes.WOLF)) {
addProperties("wolf_variant"); 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); return new NpcTypeImpl(name, type, hologramOffset, new HashSet<>(allowedProperties), defaultProperties);

View file

@ -36,7 +36,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "player", EntityTypes.PLAYER) register(builder(p, "player", EntityTypes.PLAYER)
.setHologramOffset(-0.15D) .setHologramOffset(-0.15D)
.addEquipmentProperties() .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") .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")
.addDefaultProperty("skin_cape", true) .addDefaultProperty("skin_cape", true)
.addDefaultProperty("skin_jacket", true) .addDefaultProperty("skin_jacket", true)
.addDefaultProperty("skin_left_sleeve", true) .addDefaultProperty("skin_left_sleeve", true)
@ -82,7 +82,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "enderman", EntityTypes.ENDERMAN) register(builder(p, "enderman", EntityTypes.ENDERMAN)
.setHologramOffset(0.925) .setHologramOffset(0.925)
.addProperties("enderman_held_block", "enderman_screaming", "enderman_staring")); .addProperties("enderman_held_block", "enderman_screaming", "enderman_staring", "entity_sitting"));
register(builder(p, "endermite", EntityTypes.ENDERMITE) register(builder(p, "endermite", EntityTypes.ENDERMITE)
.setHologramOffset(-1.675)); .setHologramOffset(-1.675));
@ -93,7 +93,8 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "giant", EntityTypes.GIANT) register(builder(p, "giant", EntityTypes.GIANT)
.setHologramOffset(10.025) .setHologramOffset(10.025)
.addEquipmentProperties()); .addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "guardian", EntityTypes.GUARDIAN) register(builder(p, "guardian", EntityTypes.GUARDIAN)
.setHologramOffset(-1.125) .setHologramOffset(-1.125)
@ -133,7 +134,8 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "skeleton", EntityTypes.SKELETON) register(builder(p, "skeleton", EntityTypes.SKELETON)
.setHologramOffset(0.015) .setHologramOffset(0.015)
.addEquipmentProperties()); .addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "skeleton_horse", EntityTypes.SKELETON_HORSE) register(builder(p, "skeleton_horse", EntityTypes.SKELETON_HORSE)
.setHologramOffset(-0.375)); .setHologramOffset(-0.375));
@ -169,14 +171,16 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "zombie", EntityTypes.ZOMBIE) register(builder(p, "zombie", EntityTypes.ZOMBIE)
.setHologramOffset(-0.025) .setHologramOffset(-0.025)
.addEquipmentProperties()); .addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "zombie_horse", EntityTypes.ZOMBIE_HORSE) register(builder(p, "zombie_horse", EntityTypes.ZOMBIE_HORSE)
.setHologramOffset(-0.375)); .setHologramOffset(-0.375));
register(builder(p, "zombified_piglin", EntityTypes.ZOMBIFIED_PIGLIN) register(builder(p, "zombified_piglin", EntityTypes.ZOMBIFIED_PIGLIN)
.setHologramOffset(-0.025) .setHologramOffset(-0.025)
.addEquipmentProperties()); .addEquipmentProperties()
.addProperties("entity_sitting"));
if (!version.isNewerThanOrEquals(ServerVersion.V_1_9)) return; if (!version.isNewerThanOrEquals(ServerVersion.V_1_9)) return;
@ -203,21 +207,21 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "husk", EntityTypes.HUSK) register(builder(p, "husk", EntityTypes.HUSK)
.setHologramOffset(-0.025) .setHologramOffset(-0.025)
.addEquipmentProperties()); .addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "polar_bear", EntityTypes.POLAR_BEAR)
.setHologramOffset(-0.575));
register(builder(p, "stray", EntityTypes.STRAY) register(builder(p, "stray", EntityTypes.STRAY)
.setHologramOffset(0.015) .setHologramOffset(0.015)
.addEquipmentProperties()); .addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "evoker", EntityTypes.EVOKER) register(builder(p, "evoker", EntityTypes.EVOKER)
.setHologramOffset(-0.025)); .setHologramOffset(-0.025)
.addProperties("entity_sitting"));
register(builder(p, "llama", EntityTypes.LLAMA) register(builder(p, "llama", EntityTypes.LLAMA)
.setHologramOffset(-0.105) .setHologramOffset(-0.105)
.addProperties("carpet_color", "llama_variant")); .addProperties("carpet_color", "llama_variant", "body"));
register(builder(p, "vex", EntityTypes.VEX) register(builder(p, "vex", EntityTypes.VEX)
.setHologramOffset(-1.175) .setHologramOffset(-1.175)
@ -225,20 +229,23 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "vindicator", EntityTypes.VINDICATOR) register(builder(p, "vindicator", EntityTypes.VINDICATOR)
.setHologramOffset(-0.025) .setHologramOffset(-0.025)
.addProperties("celebrating")); .addProperties("celebrating", "entity_sitting"));
register(builder(p, "wither_skeleton", EntityTypes.WITHER_SKELETON) register(builder(p, "wither_skeleton", EntityTypes.WITHER_SKELETON)
.setHologramOffset(0.425) .setHologramOffset(0.425)
.addEquipmentProperties()); .addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "zombie_villager", EntityTypes.ZOMBIE_VILLAGER) register(builder(p, "zombie_villager", EntityTypes.ZOMBIE_VILLAGER)
.setHologramOffset(-0.025) .setHologramOffset(-0.025)
.addEquipmentProperties()); .addEquipmentProperties()
.addProperties("entity_sitting"));
if (!version.isNewerThanOrEquals(ServerVersion.V_1_12)) return; if (!version.isNewerThanOrEquals(ServerVersion.V_1_12)) return;
register(builder(p, "illusioner", EntityTypes.ILLUSIONER) register(builder(p, "illusioner", EntityTypes.ILLUSIONER)
.setHologramOffset(-0.025)); .setHologramOffset(-0.025)
.addProperties("entity_sitting"));
register(builder(p, "parrot", EntityTypes.PARROT) register(builder(p, "parrot", EntityTypes.PARROT)
.setHologramOffset(-1.075) .setHologramOffset(-1.075)
@ -255,7 +262,8 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "drowned", EntityTypes.DROWNED) register(builder(p, "drowned", EntityTypes.DROWNED)
.setHologramOffset(-0.025) .setHologramOffset(-0.025)
.addEquipmentProperties()); .addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "phantom", EntityTypes.PHANTOM) register(builder(p, "phantom", EntityTypes.PHANTOM)
.setHologramOffset(-1.475)); .setHologramOffset(-1.475));
@ -291,14 +299,14 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "pillager", EntityTypes.PILLAGER) register(builder(p, "pillager", EntityTypes.PILLAGER)
.setHologramOffset(-0.025) .setHologramOffset(-0.025)
.addHandProperties() .addHandProperties()
.addProperties("pillager_charging")); .addProperties("pillager_charging", "entity_sitting"));
register(builder(p, "ravager", EntityTypes.RAVAGER) register(builder(p, "ravager", EntityTypes.RAVAGER)
.setHologramOffset(0.225)); .setHologramOffset(0.225));
register(builder(p, "trader_llama", EntityTypes.TRADER_LLAMA) register(builder(p, "trader_llama", EntityTypes.TRADER_LLAMA)
.setHologramOffset(-0.105) .setHologramOffset(-0.105)
.addProperties("llama_variant")); .addProperties("carpet_color", "llama_variant", "body"));
register(builder(p, "wandering_trader", EntityTypes.WANDERING_TRADER) register(builder(p, "wandering_trader", EntityTypes.WANDERING_TRADER)
.setHologramOffset(-0.025) .setHologramOffset(-0.025)
@ -319,11 +327,12 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "piglin", EntityTypes.PIGLIN) register(builder(p, "piglin", EntityTypes.PIGLIN)
.setHologramOffset(-0.025) .setHologramOffset(-0.025)
.addEquipmentProperties() .addEquipmentProperties()
.addProperties("piglin_baby", "piglin_charging_crossbow", "piglin_dancing")); .addProperties("piglin_baby", "piglin_charging_crossbow", "piglin_dancing", "entity_sitting"));
register(builder(p, "piglin_brute", EntityTypes.PIGLIN_BRUTE) register(builder(p, "piglin_brute", EntityTypes.PIGLIN_BRUTE)
.setHologramOffset(-0.025) .setHologramOffset(-0.025)
.addEquipmentProperties()); .addEquipmentProperties()
.addProperties("entity_sitting"));
register(builder(p, "strider", EntityTypes.STRIDER) register(builder(p, "strider", EntityTypes.STRIDER)
.setHologramOffset(-0.275) .setHologramOffset(-0.275)
@ -383,10 +392,16 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "bogged", EntityTypes.BOGGED) register(builder(p, "bogged", EntityTypes.BOGGED)
.setHologramOffset(0.015) .setHologramOffset(0.015)
.addProperties("bogged_sheared")); .addProperties("bogged_sheared", "entity_sitting"));
register(builder(p, "breeze", EntityTypes.BREEZE) register(builder(p, "breeze", EntityTypes.BREEZE)
.setHologramOffset(-0.205)); .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() { public Collection<NpcType> getAll() {

View file

@ -2,6 +2,7 @@ package lol.pyr.znpcsplus.packets;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData; import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.protocol.player.Equipment; 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.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.entity.PacketEntity; import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.util.NamedColor; import lol.pyr.znpcsplus.util.NamedColor;
@ -11,7 +12,7 @@ import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public interface PacketFactory { public interface PacketFactory {
void spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties); CompletableFuture<Void> spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties);
void spawnEntity(Player player, PacketEntity entity, PropertyHolder properties); void spawnEntity(Player player, PacketEntity entity, PropertyHolder properties);
void destroyEntity(Player player, PacketEntity entity, PropertyHolder properties); void destroyEntity(Player player, PacketEntity entity, PropertyHolder properties);
void teleportEntity(Player player, PacketEntity entity); void teleportEntity(Player player, PacketEntity entity);
@ -21,7 +22,10 @@ public interface PacketFactory {
void removeTeam(Player player, PacketEntity entity); void removeTeam(Player player, PacketEntity entity);
void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties); void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties);
void sendEquipment(Player player, PacketEntity entity, Equipment equipment); 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 sendHeadRotation(Player player, PacketEntity entity, float yaw, float pitch);
void sendHandSwing(Player player, PacketEntity entity, boolean offHand); 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);
} }

View file

@ -1,6 +1,7 @@
package lol.pyr.znpcsplus.packets; package lol.pyr.znpcsplus.packets;
import com.github.retrooper.packetevents.PacketEventsAPI; 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.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSpawnEntity; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSpawnEntity;
import lol.pyr.znpcsplus.api.entity.PropertyHolder; import lol.pyr.znpcsplus.api.entity.PropertyHolder;
@ -27,6 +28,7 @@ public class V1_17PacketFactory extends V1_8PacketFactory {
sendPacket(player, new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(), 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()))); npcLocationToVector(location), location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.of(new Vector3d())));
sendAllMetadata(player, entity, properties); 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))); createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class)));
} }
} }

View file

@ -16,6 +16,7 @@ import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public class V1_20_2PacketFactory extends V1_19_3PacketFactory { public class V1_20_2PacketFactory extends V1_19_3PacketFactory {
@ -27,14 +28,15 @@ public class V1_20_2PacketFactory extends V1_19_3PacketFactory {
} }
@Override @Override
public void spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties) { public CompletableFuture<Void> spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties) {
addTabPlayer(player, entity, properties).thenAccept(ignored -> { return addTabPlayer(player, entity, properties).thenAccept(ignored -> {
createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class))); createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class)));
NpcLocation location = entity.getLocation(); NpcLocation location = entity.getLocation();
sendPacket(player, new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(), 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()))); npcLocationToVector(location), location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.of(new Vector3d())));
sendPacket(player, new WrapperPlayServerEntityHeadLook(entity.getEntityId(), location.getYaw())); sendPacket(player, new WrapperPlayServerEntityHeadLook(entity.getEntityId(), location.getYaw()));
sendAllMetadata(player, entity, properties); sendAllMetadata(player, entity, properties);
sendAllAttributes(player, entity, properties);
scheduler.runLaterAsync(() -> removeTabPlayer(player, entity), configManager.getConfig().tabHideDelay()); scheduler.runLaterAsync(() -> removeTabPlayer(player, entity), configManager.getConfig().tabHideDelay());
}); });
} }

View file

@ -18,6 +18,7 @@ import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl; import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.entity.PacketEntity; import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.entity.properties.attributes.AttributeProperty;
import lol.pyr.znpcsplus.scheduling.TaskScheduler; import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor; import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
import lol.pyr.znpcsplus.util.NamedColor; import lol.pyr.znpcsplus.util.NamedColor;
@ -47,14 +48,15 @@ public class V1_8PacketFactory implements PacketFactory {
} }
@Override @Override
public void spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties) { public CompletableFuture<Void> spawnPlayer(Player player, PacketEntity entity, PropertyHolder properties) {
addTabPlayer(player, entity, properties).thenAccept(ignored -> { return addTabPlayer(player, entity, properties).thenAccept(ignored -> {
createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class))); createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class)));
NpcLocation location = entity.getLocation(); NpcLocation location = entity.getLocation();
sendPacket(player, new WrapperPlayServerSpawnPlayer(entity.getEntityId(), sendPacket(player, new WrapperPlayServerSpawnPlayer(entity.getEntityId(),
entity.getUuid(), npcLocationToVector(location), location.getYaw(), location.getPitch(), Collections.emptyList())); entity.getUuid(), npcLocationToVector(location), location.getYaw(), location.getPitch(), Collections.emptyList()));
sendPacket(player, new WrapperPlayServerEntityHeadLook(entity.getEntityId(), location.getYaw())); sendPacket(player, new WrapperPlayServerEntityHeadLook(entity.getEntityId(), location.getYaw()));
sendAllMetadata(player, entity, properties); sendAllMetadata(player, entity, properties);
sendAllAttributes(player, entity, properties);
scheduler.runLaterAsync(() -> removeTabPlayer(player, entity), configManager.getConfig().tabHideDelay()); scheduler.runLaterAsync(() -> removeTabPlayer(player, entity), configManager.getConfig().tabHideDelay());
}); });
} }
@ -70,6 +72,7 @@ public class V1_8PacketFactory implements PacketFactory {
new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(), npcLocationToVector(location), new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(), npcLocationToVector(location),
location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.empty())); location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.empty()));
sendAllMetadata(player, entity, properties); sendAllMetadata(player, entity, properties);
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.LIVINGENTITY)) sendAllAttributes(player, entity, properties);
createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class))); createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class)));
} }
@ -132,13 +135,13 @@ public class V1_8PacketFactory implements PacketFactory {
@Override @Override
public void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties) { 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); for (EntityProperty<?> property : properties.getAppliedProperties()) ((EntityPropertyImpl<?>) property).apply(player, entity, false, datas);
sendMetadata(player, entity, new ArrayList<>(datas.values())); sendMetadata(player, entity, new ArrayList<>(datas.values()));
} }
@Override @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)); sendPacket(player, new WrapperPlayServerEntityMetadata(entity.getEntityId(), data));
} }
@ -153,6 +156,11 @@ public class V1_8PacketFactory implements PacketFactory {
sendPacket(player, new WrapperPlayServerEntityEquipment(entity.getEntityId(), Collections.singletonList(equipment))); 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) { protected void sendPacket(Player player, PacketWrapper<?> packet) {
packetEvents.getPlayerManager().sendPacket(player, packet); packetEvents.getPlayerManager().sendPacket(player, packet);
} }
@ -172,7 +180,7 @@ public class V1_8PacketFactory implements PacketFactory {
return future; 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); map.put(data.getIndex(), data);
} }
@ -182,4 +190,19 @@ public class V1_8PacketFactory implements PacketFactory {
WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_OFF_HAND : WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_OFF_HAND :
WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_MAIN_ARM)); 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)));
}
} }

View file

@ -0,0 +1,33 @@
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);
}
}

View file

@ -0,0 +1,154 @@
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;
}
}

View file

@ -3,14 +3,16 @@ package lol.pyr.znpcsplus.skin;
import com.github.retrooper.packetevents.protocol.player.TextureProperty; import com.github.retrooper.packetevents.protocol.player.TextureProperty;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor; import lol.pyr.znpcsplus.skin.descriptor.NameFetchingDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor; import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor; import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.UUIDFetchingDescriptor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public interface BaseSkinDescriptor extends SkinDescriptor { public interface BaseSkinDescriptor extends SkinDescriptor {
@ -22,7 +24,14 @@ public interface BaseSkinDescriptor extends SkinDescriptor {
static BaseSkinDescriptor deserialize(MojangSkinCache skinCache, String str) { static BaseSkinDescriptor deserialize(MojangSkinCache skinCache, String str) {
String[] arr = str.split(";"); String[] arr = str.split(";");
if (arr[0].equalsIgnoreCase("mirror")) return new MirrorDescriptor(skinCache); if (arr[0].equalsIgnoreCase("mirror")) return new MirrorDescriptor(skinCache);
else if (arr[0].equalsIgnoreCase("fetching")) return new FetchingDescriptor(skinCache, String.join(";", Arrays.copyOfRange(arr, 1, arr.length))); 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("prefetched")) { else if (arr[0].equalsIgnoreCase("prefetched")) {
List<TextureProperty> properties = new ArrayList<>(); List<TextureProperty> properties = new ArrayList<>();
for (int i = 0; i < (arr.length - 1) / 3; i++) { for (int i = 0; i < (arr.length - 1) / 3; i++) {

View file

@ -3,12 +3,14 @@ package lol.pyr.znpcsplus.skin;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.api.skin.SkinDescriptorFactory; import lol.pyr.znpcsplus.api.skin.SkinDescriptorFactory;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor; import lol.pyr.znpcsplus.skin.descriptor.NameFetchingDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor; import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor; import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.UUIDFetchingDescriptor;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.UUID;
public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory { public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory {
private final MojangSkinCache skinCache; private final MojangSkinCache skinCache;
@ -26,7 +28,12 @@ public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory {
@Override @Override
public SkinDescriptor createRefreshingDescriptor(String playerName) { public SkinDescriptor createRefreshingDescriptor(String playerName) {
return new FetchingDescriptor(skinCache, playerName); return new NameFetchingDescriptor(skinCache, playerName);
}
@Override
public SkinDescriptor createRefreshingDescriptor(UUID playerUUID) {
return new UUIDFetchingDescriptor(skinCache, playerUUID);
} }
@Override @Override
@ -52,4 +59,9 @@ public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory {
public SkinDescriptor createUrlDescriptor(URL url, String variant) { public SkinDescriptor createUrlDescriptor(URL url, String variant) {
return PrefetchedDescriptor.fromUrl(skinCache, url, variant).join(); return PrefetchedDescriptor.fromUrl(skinCache, url, variant).join();
} }
@Override
public SkinDescriptor createFileDescriptor(String path) {
return PrefetchedDescriptor.fromFile(skinCache, path).join();
}
} }

View file

@ -5,6 +5,7 @@ import com.google.gson.JsonParser;
import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.reflection.Reflections; import lol.pyr.znpcsplus.reflection.Reflections;
import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.SkinImpl;
import lol.pyr.znpcsplus.util.FutureUtil;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -14,6 +15,7 @@ import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -26,9 +28,12 @@ public class MojangSkinCache {
private final Map<String, SkinImpl> cache = new ConcurrentHashMap<>(); private final Map<String, SkinImpl> cache = new ConcurrentHashMap<>();
private final Map<String, CachedId> idCache = new ConcurrentHashMap<>(); private final Map<String, CachedId> idCache = new ConcurrentHashMap<>();
private final File skinsFolder;
public MojangSkinCache(ConfigManager configManager) { public MojangSkinCache(ConfigManager configManager, File skinsFolder) {
this.configManager = configManager; this.configManager = configManager;
this.skinsFolder = skinsFolder;
if (!skinsFolder.exists()) skinsFolder.mkdirs();
} }
public void cleanCache() { public void cleanCache() {
@ -42,8 +47,8 @@ public class MojangSkinCache {
if (idCache.containsKey(name.toLowerCase())) return fetchByUUID(idCache.get(name.toLowerCase()).getId()); if (idCache.containsKey(name.toLowerCase())) return fetchByUUID(idCache.get(name.toLowerCase()).getId());
return CompletableFuture.supplyAsync(() -> { return FutureUtil.exceptionPrintingSupplyAsync(() -> {
URL url = parseUrl("https://api.mojang.com/users/profiles/minecraft/" + name); URL url = parseUrl("https://api.minecraftservices.com/minecraft/profile/lookup/name/" + name);
HttpURLConnection connection = null; HttpURLConnection connection = null;
try { try {
connection = (HttpURLConnection) url.openConnection(); connection = (HttpURLConnection) url.openConnection();
@ -75,7 +80,7 @@ public class MojangSkinCache {
if (idCache.containsKey(name.toLowerCase())) return fetchByUUID(idCache.get(name.toLowerCase()).getId()); if (idCache.containsKey(name.toLowerCase())) return fetchByUUID(idCache.get(name.toLowerCase()).getId());
return CompletableFuture.supplyAsync(() -> { return FutureUtil.exceptionPrintingSupplyAsync(() -> {
URL url = parseUrl("https://api.ashcon.app/mojang/v2/user/" + name); URL url = parseUrl("https://api.ashcon.app/mojang/v2/user/" + name);
HttpURLConnection connection = null; HttpURLConnection connection = null;
try { try {
@ -106,7 +111,7 @@ public class MojangSkinCache {
} }
public CompletableFuture<SkinImpl> fetchByUrl(URL url, String variant) { public CompletableFuture<SkinImpl> fetchByUrl(URL url, String variant) {
return CompletableFuture.supplyAsync(() -> { return FutureUtil.exceptionPrintingSupplyAsync(() -> {
URL apiUrl = parseUrl("https://api.mineskin.org/generate/url"); URL apiUrl = parseUrl("https://api.mineskin.org/generate/url");
HttpURLConnection connection = null; HttpURLConnection connection = null;
try { try {
@ -142,6 +147,58 @@ 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) { public boolean isNameFullyCached(String s) {
String name = s.toLowerCase(); String name = s.toLowerCase();
if (!idCache.containsKey(name)) return false; if (!idCache.containsKey(name)) return false;
@ -170,7 +227,7 @@ public class MojangSkinCache {
if (!skin.isExpired()) return CompletableFuture.completedFuture(skin); if (!skin.isExpired()) return CompletableFuture.completedFuture(skin);
} }
return CompletableFuture.supplyAsync(() -> { return FutureUtil.exceptionPrintingSupplyAsync(() -> {
URL url = parseUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false"); URL url = parseUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false");
HttpURLConnection connection = null; HttpURLConnection connection = null;
try { try {
@ -213,4 +270,8 @@ public class MojangSkinCache {
throw new RuntimeException(exception); throw new RuntimeException(exception);
} }
} }
public File getSkinsFolder() {
return skinsFolder;
}
} }

View file

@ -9,11 +9,11 @@ import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class FetchingDescriptor implements BaseSkinDescriptor, SkinDescriptor { public class NameFetchingDescriptor implements BaseSkinDescriptor, SkinDescriptor {
private final MojangSkinCache skinCache; private final MojangSkinCache skinCache;
private final String name; private final String name;
public FetchingDescriptor(MojangSkinCache skinCache, String name) { public NameFetchingDescriptor(MojangSkinCache skinCache, String name) {
this.skinCache = skinCache; this.skinCache = skinCache;
this.name = name; this.name = name;
} }

View file

@ -5,8 +5,10 @@ import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor; import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
import lol.pyr.znpcsplus.skin.SkinImpl; import lol.pyr.znpcsplus.skin.SkinImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.util.FutureUtil;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.io.FileNotFoundException;
import java.net.URL; import java.net.URL;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -18,11 +20,21 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
} }
public static CompletableFuture<PrefetchedDescriptor> forPlayer(MojangSkinCache cache, String name) { public static CompletableFuture<PrefetchedDescriptor> forPlayer(MojangSkinCache cache, String name) {
return CompletableFuture.supplyAsync(() -> new PrefetchedDescriptor(cache.fetchByName(name).join())); return FutureUtil.exceptionPrintingSupplyAsync(() -> new PrefetchedDescriptor(cache.fetchByName(name).join()));
} }
public static CompletableFuture<PrefetchedDescriptor> fromUrl(MojangSkinCache cache, URL url, String variant) { public static CompletableFuture<PrefetchedDescriptor> fromUrl(MojangSkinCache cache, URL url, String variant) {
return CompletableFuture.supplyAsync(() -> new PrefetchedDescriptor(cache.fetchByUrl(url, variant).join())); 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);
}
});
} }
@Override @Override

View file

@ -0,0 +1,42 @@
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();
}
}

View file

@ -6,6 +6,7 @@ import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl; import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl; import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.storage.mysql.MySQLStorage; import lol.pyr.znpcsplus.storage.mysql.MySQLStorage;
import lol.pyr.znpcsplus.storage.sqlite.SQLiteStorage; import lol.pyr.znpcsplus.storage.sqlite.SQLiteStorage;
import lol.pyr.znpcsplus.storage.yaml.YamlStorage; import lol.pyr.znpcsplus.storage.yaml.YamlStorage;
@ -16,13 +17,13 @@ import java.io.File;
public enum NpcStorageType { public enum NpcStorageType {
YAML { YAML {
@Override @Override
public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) { public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcSerializerRegistryImpl serializerRegistry) {
return new YamlStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer, new File(plugin.getDataFolder(), "data")); return new YamlStorage(serializerRegistry, new File(plugin.getDataFolder(), "data"));
} }
}, },
SQLITE { SQLITE {
@Override @Override
public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) { public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcSerializerRegistryImpl serializerRegistry) {
try { try {
return new SQLiteStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer, new File(plugin.getDataFolder(), "znpcsplus.sqlite")); return new SQLiteStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer, new File(plugin.getDataFolder(), "znpcsplus.sqlite"));
} catch (Exception e) { } catch (Exception e) {
@ -33,7 +34,7 @@ public enum NpcStorageType {
}, },
MYSQL { MYSQL {
@Override @Override
public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) { public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcSerializerRegistryImpl serializerRegistry) {
try { try {
return new MySQLStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer); return new MySQLStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
} catch (Exception e) { } catch (Exception e) {
@ -43,5 +44,5 @@ public enum NpcStorageType {
} }
}; };
public abstract NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer); public abstract NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcSerializerRegistryImpl serializerRegistry);
} }

View file

@ -1,47 +1,28 @@
package lol.pyr.znpcsplus.storage.yaml; package lol.pyr.znpcsplus.storage.yaml;
import lol.pyr.znpcsplus.api.entity.EntityProperty; 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.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcImpl; import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.storage.NpcStorage; import lol.pyr.znpcsplus.storage.NpcStorage;
import lol.pyr.znpcsplus.util.NpcLocation; 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.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File; import java.io.File;
import java.util.*; import java.util.ArrayList;
import java.util.logging.Level; import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors;
public class YamlStorage implements NpcStorage { public class YamlStorage implements NpcStorage {
private final static Logger logger = Logger.getLogger("YamlStorage"); 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 File folder;
private final NpcSerializer<YamlConfiguration> yamlSerializer;
public YamlStorage(PacketFactory packetFactory, ConfigManager configManager, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, File folder) { public YamlStorage(NpcSerializerRegistryImpl serializerRegistry, File folder) {
this.packetFactory = packetFactory; this.yamlSerializer = serializerRegistry.getSerializer(YamlConfiguration.class);
this.configManager = configManager;
this.actionRegistry = actionRegistry;
this.typeRegistry = typeRegistry;
this.propertyRegistry = propertyRegistry;
this.textSerializer = textSerializer;
this.folder = folder; this.folder = folder;
if (!this.folder.exists()) this.folder.mkdirs(); if (!this.folder.exists()) this.folder.mkdirs();
} }
@ -53,45 +34,7 @@ public class YamlStorage implements NpcStorage {
List<NpcEntryImpl> npcs = new ArrayList<>(files.length); List<NpcEntryImpl> npcs = new ArrayList<>(files.length);
for (File file : files) if (file.isFile() && file.getName().toLowerCase().endsWith(".yml")) try { for (File file : files) if (file.isFile() && file.getName().toLowerCase().endsWith(".yml")) try {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file); YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
UUID uuid = config.contains("uuid") ? UUID.fromString(config.getString("uuid")) : UUID.randomUUID(); npcs.add((NpcEntryImpl) yamlSerializer.deserialize(config));
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) { } catch (Throwable t) {
logger.severe("Failed to load npc file: " + file.getName()); logger.severe("Failed to load npc file: " + file.getName());
t.printStackTrace(); t.printStackTrace();
@ -102,43 +45,7 @@ public class YamlStorage implements NpcStorage {
@Override @Override
public void saveNpcs(Collection<NpcEntryImpl> npcs) { public void saveNpcs(Collection<NpcEntryImpl> npcs) {
for (NpcEntryImpl entry : npcs) try { for (NpcEntryImpl entry : npcs) try {
YamlConfiguration config = new YamlConfiguration(); YamlConfiguration config = yamlSerializer.serialize(entry);
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)); config.save(fileFor(entry));
} catch (Exception exception) { } catch (Exception exception) {
logger.severe("Failed to save npc with id " + entry.getId()); logger.severe("Failed to save npc with id " + entry.getId());

View file

@ -33,6 +33,7 @@ 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<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<LookType> lookProperty = propertyRegistry.getByName("look", LookType.class);
EntityPropertyImpl<Double> lookDistanceProperty = propertyRegistry.getByName("look_distance", Double.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> permissionRequiredProperty = propertyRegistry.getByName("permission_required", Boolean.class);
EntityPropertyImpl<Boolean> playerKnockbackProperty = propertyRegistry.getByName("player_knockback", Boolean.class); EntityPropertyImpl<Boolean> playerKnockbackProperty = propertyRegistry.getByName("player_knockback", Boolean.class);
EntityPropertyImpl<String> playerKnockbackExemptPermissionProperty = propertyRegistry.getByName("player_knockback_exempt_permission", String.class); EntityPropertyImpl<String> playerKnockbackExemptPermissionProperty = propertyRegistry.getByName("player_knockback_exempt_permission", String.class);
@ -45,6 +46,7 @@ public class NpcProcessorTask extends BukkitRunnable {
EntityPropertyImpl<Float> playerKnockbackSoundVolumeProperty = propertyRegistry.getByName("player_knockback_sound_volume", Float.class); EntityPropertyImpl<Float> playerKnockbackSoundVolumeProperty = propertyRegistry.getByName("player_knockback_sound_volume", Float.class);
EntityPropertyImpl<Float> playerKnockbackSoundPitchProperty = propertyRegistry.getByName("player_knockback_sound_pitch", Float.class); EntityPropertyImpl<Float> playerKnockbackSoundPitchProperty = propertyRegistry.getByName("player_knockback_sound_pitch", Float.class);
double lookDistance; double lookDistance;
boolean lookReturn;
boolean permissionRequired; boolean permissionRequired;
boolean playerKnockback; boolean playerKnockback;
String playerKnockbackExemptPermission = null; String playerKnockbackExemptPermission = null;
@ -64,6 +66,7 @@ public class NpcProcessorTask extends BukkitRunnable {
Player closest = null; Player closest = null;
LookType lookType = npc.getProperty(lookProperty); LookType lookType = npc.getProperty(lookProperty);
lookDistance = NumberConversions.square(npc.getProperty(lookDistanceProperty)); lookDistance = NumberConversions.square(npc.getProperty(lookDistanceProperty));
lookReturn = npc.getProperty(lookReturnProperty);
permissionRequired = npc.getProperty(permissionRequiredProperty); permissionRequired = npc.getProperty(permissionRequiredProperty);
playerKnockback = npc.getProperty(playerKnockbackProperty); playerKnockback = npc.getProperty(playerKnockbackProperty);
if (playerKnockback) { if (playerKnockback) {
@ -106,9 +109,13 @@ public class NpcProcessorTask extends BukkitRunnable {
closestDist = distance; closestDist = distance;
closest = player; closest = player;
} }
if (lookType.equals(LookType.PER_PLAYER) && lookDistance >= distance) { if (lookType.equals(LookType.PER_PLAYER)) {
if (lookDistance >= distance) {
NpcLocation expected = npc.getLocation().lookingAt(player.getLocation().add(0, -npc.getType().getHologramOffset(), 0)); NpcLocation expected = npc.getLocation().lookingAt(player.getLocation().add(0, -npc.getType().getHologramOffset(), 0));
if (!expected.equals(npc.getLocation())) npc.setHeadRotation(player, expected.getYaw(), expected.getPitch()); npc.setHeadRotation(player, expected.getYaw(), expected.getPitch());
} else if (lookReturn) {
npc.setHeadRotation(player, npc.getLocation().getYaw(), npc.getLocation().getPitch());
}
} }
// player knockback // player knockback
@ -132,7 +139,11 @@ public class NpcProcessorTask extends BukkitRunnable {
if (closest != null && lookDistance >= closestDist) { if (closest != null && lookDistance >= closestDist) {
NpcLocation expected = npc.getLocation().lookingAt(closest.getLocation().add(0, -npc.getType().getHologramOffset(), 0)); NpcLocation expected = npc.getLocation().lookingAt(closest.getLocation().add(0, -npc.getType().getHologramOffset(), 0));
if (!expected.equals(npc.getLocation())) npc.setHeadRotation(expected.getYaw(), expected.getPitch()); 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());
} }
} }
} }

View file

@ -0,0 +1,34 @@
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;
});
}
}

View file

@ -4,55 +4,95 @@ import org.bukkit.entity.Player;
import java.lang.ref.Reference; import java.lang.ref.Reference;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.*;
import java.util.Collections; import java.util.concurrent.CompletableFuture;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public abstract class Viewable { public abstract class Viewable {
private final static List<WeakReference<Viewable>> all = new ArrayList<>(); private final static List<WeakReference<Viewable>> all = Collections.synchronizedList(new ArrayList<>());
public static List<Viewable> all() { public static List<Viewable> all() {
synchronized (all) {
all.removeIf(reference -> reference.get() == null); all.removeIf(reference -> reference.get() == null);
return all.stream() return all.stream()
.map(Reference::get) .map(Reference::get)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
}
private boolean queueRunning = false;
private final Queue<Runnable> visibilityTaskQueue = new ConcurrentLinkedQueue<>();
private final Set<Player> viewers = ConcurrentHashMap.newKeySet(); private final Set<Player> viewers = ConcurrentHashMap.newKeySet();
public Viewable() { public Viewable() {
all.add(new WeakReference<>(this)); 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() { public void delete() {
queueVisibilityTask(() -> {
UNSAFE_hideAll(); UNSAFE_hideAll();
viewers.clear(); viewers.clear();
synchronized (all) {
all.removeIf(reference -> reference.get() == null || reference.get() == this);
}
});
} }
public void respawn() { public CompletableFuture<Void> respawn() {
CompletableFuture<Void> future = new CompletableFuture<>();
queueVisibilityTask(() -> {
UNSAFE_hideAll(); UNSAFE_hideAll();
UNSAFE_showAll(); UNSAFE_showAll().join();
future.complete(null);
});
return future;
} }
public void respawn(Player player) { public CompletableFuture<Void> respawn(Player player) {
if (!viewers.contains(player)) return; hide(player);
UNSAFE_hide(player); return show(player);
UNSAFE_show(player);
} }
public void show(Player player) { public CompletableFuture<Void> show(Player player) {
if (viewers.contains(player)) return; CompletableFuture<Void> future = new CompletableFuture<>();
queueVisibilityTask(() -> {
if (viewers.contains(player)) {
future.complete(null);
return;
}
viewers.add(player); viewers.add(player);
UNSAFE_show(player); UNSAFE_show(player).join();
future.complete(null);
});
return future;
} }
public void hide(Player player) { public void hide(Player player) {
queueVisibilityTask(() -> {
if (!viewers.contains(player)) return; if (!viewers.contains(player)) return;
viewers.remove(player); viewers.remove(player);
UNSAFE_hide(player); UNSAFE_hide(player);
});
} }
public void UNSAFE_removeViewer(Player player) { public void UNSAFE_removeViewer(Player player) {
@ -63,8 +103,10 @@ public abstract class Viewable {
for (Player viewer : viewers) UNSAFE_hide(viewer); for (Player viewer : viewers) UNSAFE_hide(viewer);
} }
protected void UNSAFE_showAll() { protected CompletableFuture<Void> UNSAFE_showAll() {
for (Player viewer : viewers) UNSAFE_show(viewer); return FutureUtil.allOf(viewers.stream()
.map(this::UNSAFE_show)
.collect(Collectors.toList()));
} }
public Set<Player> getViewers() { public Set<Player> getViewers() {
@ -75,7 +117,7 @@ public abstract class Viewable {
return viewers.contains(player); return viewers.contains(player);
} }
protected abstract void UNSAFE_show(Player player); protected abstract CompletableFuture<Void> UNSAFE_show(Player player);
protected abstract void UNSAFE_hide(Player player); protected abstract void UNSAFE_hide(Player player);
} }