Compare commits

...

75 commits

Author SHA1 Message Date
1c927c6b32 Merge remote-tracking branch 'upstream/2.X' into 2.X
# Conflicts:
#	build.gradle
#	gradle/wrapper/gradle-wrapper.properties
#	plugin/build.gradle
2025-07-27 00:45:57 +02:00
Pyr
f0a03ec290
Merge pull request #193 from 3328429240/2.X
Increase compatibility with 1.21.8
2025-07-22 20:45:34 +02:00
繁华如三千东流水
10f14eefe0
Delete .promptx directory 2025-07-22 20:28:57 +08:00
3328429240
365d0e7ca5 refactor: Remove redundant V1_21_8PacketFactory class
The setupPacketFactory function automatically selects the highest compatible
version using fallback logic, making the empty V1_21_8PacketFactory unnecessary.
Minecraft 1.21.8 will now use V1_21_3PacketFactory automatically.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-22 20:26:57 +08:00
3328429240
fbadeac502 feat: Add support for Minecraft 1.21.8
- Upgrade PacketEvents from 2.9.1 to 2.9.3 for 1.21.8 compatibility
- Add V1_21_8PacketFactory with full protocol support
- Update version support range in README.md to include 1.21.8
- Update test configuration to use Minecraft 1.21.8

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-22 20:22:38 +08:00
Pyr
3487aacbab
Merge pull request #190 from 3328429240/2.X
Increase compatibility with 1.21.7
2025-07-10 16:08:03 +02:00
3328429240
a9d9cb3907 Remove empty V1_21_7PacketFactory class
The empty V1_21_7PacketFactory class has been removed as it's not needed.
The setupPacketFactory function automatically selects the latest available
version without requiring every version to be explicitly provided.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-10 21:29:59 +08:00
3328429240
63d763015a Merge master branch updates into 2.X
Update Gradle to 8.5 and PacketEvents to 2.9.1
Add V1_21_7PacketFactory support

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-10 18:26:28 +08:00
3328429240
e3422f22c6 Initial commit 2025-07-10 18:10:49 +08:00
D3v1s0m
1974b4b34b
fix: skin name to uuid lookup endpoint 2025-06-13 17:19:43 +05:30
Pyr
63db907d32
Merge pull request #170 from Rainnny7/2.X
Support for legacy section symbols in holograms
2025-05-15 21:58:32 +02:00
D3v1s0m
95432d438e
chore: bump PacketEvents to 2.8.0 fix: baby property and a mem leak add: zombie related properties 2025-05-15 23:33:06 +05:30
D3v1s0m
fe70e7ca9c
fix: entity_sitting property when npc respawns 2025-05-08 22:25:18 +05:30
D3v1s0m
a87f1657cc
chore: add development build in version command 2025-04-18 12:28:26 +05:30
D3v1s0m
643b143868
fix: citizens hologram import 2025-04-18 12:27:21 +05:30
D3v1s0m
c34a665692
chore: Add method to delete npc using UUID 2025-04-18 12:26:23 +05:30
Pyrbu
affa0f4c84 fix viewable thread-safety 2025-04-02 23:35:14 +02:00
Pyr
ffe3d3dc35
Merge pull request #175 from Mqzn/2.X
Added skin descriptor fetching by UUID
2025-03-27 19:53:28 +01:00
Mqzn
04b57fc8ea Removed unnecessary code 2025-03-27 20:31:33 +02:00
Mqzn
d9ed69eecd Made it compatible with old npc files 2025-03-22 19:44:32 +02:00
Mqzn
33e79e8543 Adapted the serialization process to the new fetching descriptors 2025-03-22 16:03:11 +02:00
Mqzn
0258450383 Added skin descriptor fetching by UUID 2025-03-20 15:05:58 +02:00
Rainnny7
e663c19254 Support for legacy section symbols in holograms 2025-02-23 16:08:03 -05:00
Pyrbu
ca8a1d6499 switch viewers list back to a concurrent set 2025-01-08 02:22:28 +01:00
Pyrbu
86c5b9c325 fix carpet_color on 1.20-1.20.4 2024-12-29 17:18:59 +01:00
Pyrbu
dd8899163c move body property to the right place 2024-12-29 14:00:35 +01:00
Pyrbu
a4e3605d99 Merge remote-tracking branch 'origin/2.X' into 2.X 2024-12-29 13:51:48 +01:00
Pyrbu
ad8138f983 fix llama properties on 1.20+ 2024-12-29 13:51:33 +01:00
Pyrbu
c0bcbcf7a3 expose file descriptor in api 2024-12-29 13:49:52 +01:00
Pyr
65d768667c
Merge pull request #167 from envizar/command 2024-12-29 11:45:03 +00:00
envizar
ec60af7186 This should be equalsIgnoreCase 2024-12-29 14:43:19 +03:00
envizar
dfdcc54313 default type = player in create command 2024-12-29 14:29:17 +03:00
envizar
d1b890a912 toggle command with arg 2024-12-29 14:20:30 +03:00
Pyr
f1cedb3836
Merge pull request #166 from D3v1s0m/feat/health_attribute
Health properties and version command
2024-12-28 09:33:40 +00:00
D3v1s0m
ba75abcebc
feat: added version command 2024-12-27 19:30:50 +05:30
D3v1s0m
97fd7bfd76
feat: added attribute_max_health and health properties 2024-12-27 19:15:36 +05:30
D3v1s0m
218c4e48cc
fix: glow import for znpcs 2024-12-27 18:26:45 +05:30
D3v1s0m
b8dda22154
feat: added look_return property 2024-12-27 18:25:22 +05:30
Pyrbu
9d1d8f9bb0 bump version 2024-12-27 12:02:36 +01:00
Pyrbu
55104b56f3 make save true by default in yaml deserializer 2024-12-27 11:58:38 +01:00
Pyrbu
b3b8635477 expose save and reload in npc registry to the api 2024-12-24 16:14:00 +01:00
Pyrbu
c780c0ec78 add npc serialization methods to the api 2024-12-24 16:11:13 +01:00
Pyr
d1227500ce
Merge pull request #161 from D3v1s0m/feat/file-skin-type
Feature: File skin type
2024-12-18 06:59:08 +00:00
Pyrbu
27096e532b remove comment and unused imports 2024-12-18 07:29:37 +01:00
Pyrbu
e621ac6908 add exception printing by default to all futures 2024-12-18 07:08:46 +01:00
Pyrbu
e30d6a5782 fix concurrency issues in Viewable by making all view tasks execute one by one through a queue 2024-12-18 07:05:46 +01:00
Pyrbu
33c27e903c fix duplicate team creation with glow property 2024-12-18 05:23:26 +01:00
D3v1s0m
8cd7282bb5
feat: added skeleton_type property 2024-12-17 23:55:05 +04:00
D3v1s0m
255a938dda
feat: file skin type to use files for npc skin 2024-12-16 22:42:27 +04:00
Pyr
439f152ef5
Merge pull request #159 from AshleyThew/npc-passengers
Add passengers to npc api
2024-12-15 09:49:22 +00:00
Pyrbu
1d61fc731b Merge remote-tracking branch 'ashleythew/npc-passengers' into npc-passengers 2024-12-15 10:46:48 +01:00
Pyrbu
9303d74f8f remove whitespace 2024-12-15 10:46:05 +01:00
Pyrbu
9477d4b500 make passenger list not null because its more clean to use the api that way 2024-12-15 10:46:05 +01:00
Pyrbu
ca89bfe32d move annotations dependency to main gradle file because both subprojects use it 2024-12-15 10:46:05 +01:00
Dablakbandit
238a1fe224 Add vehicles by entity id
Add vehicles by entity id for cases where the entity is an existing server npc
2024-12-15 10:46:05 +01:00
Dablakbandit
c5bf8dea0b Add passengers to npc api
Add passengers to npc api & implementation
Add jetbrains annotations to api to tag with nullables
2024-12-15 10:46:05 +01:00
Pyrbu
31518c2136 remove whitespace 2024-12-15 10:45:25 +01:00
Pyrbu
aa5e62ab0d make passenger list not null because its more clean to use the api that way 2024-12-15 10:40:28 +01:00
Pyrbu
ad50d31194 move annotations dependency to main gradle file because both subprojects use it 2024-12-15 10:36:33 +01:00
Pyr
84ee701029
Merge pull request #160 from AshleyThew/wiki-links
Update wiki.vg links to minecraft wiki
2024-12-15 09:30:53 +00:00
Dablakbandit
cea0ffb8ac Add vehicles by entity id
Add vehicles by entity id for cases where the entity is an existing server npc
2024-12-15 01:06:09 +13:00
Dablakbandit
a49c43c04e Update wiki.vg links 2024-12-14 23:24:26 +13:00
Dablakbandit
b107a3b011 Add passengers to npc api
Add passengers to npc api & implementation
Add jetbrains annotations to api to tag with nullables
2024-12-14 21:49:21 +13:00
Pyr
051a9895ac
Merge pull request #157 from D3v1s0m/2.X
Enhancements: Sitting Entities, Creaking NPCs, and Updated PacketEvents
2024-12-13 07:14:34 +00:00
D3v1s0m
79c0897de9
fix: remove entity_sitting property from creaking 2024-12-13 11:02:56 +04:00
D3v1s0m
41244fb86b
fix: disabling entity_sitting property 2024-12-13 10:58:52 +04:00
D3v1s0m
f5d1a1914a
chore: added entity_sitting property to more npc types 2024-12-13 10:54:29 +04:00
D3v1s0m
cf139d9051
Merge remote-tracking branch 'ZNPCsPlus/2.X' into 2.X 2024-12-13 10:13:43 +04:00
Pyrbu
670bc9623b make entity_sitting only available to player since it doesn't really do anything for any other npc type 2024-12-13 01:38:19 +01:00
Pyrbu
aed6ee178c remove metadata and add direct vehicle support to PacketEntity 2024-12-13 01:37:53 +01:00
Pyrbu
f8d5700b9a make set property private on ArmorStandVehicleEntity 2024-12-13 00:57:36 +01:00
D3v1s0m
e20bd9ba57
fix: multiple polar bear npc type registrations 2024-12-12 16:12:52 +04:00
D3v1s0m
7fbe42e207
feat: Creaking NPC Type and its properties 2024-12-12 14:30:22 +04:00
D3v1s0m
8aed2ba5dc
chore: bump packetevents version 2024-12-12 14:27:29 +04:00
D3v1s0m
a7bf542eb3
Added entity_sitting property to allow player and some other entities to sit 2024-12-10 10:56:20 +04:00
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?
- 100% Packet Based - Nothing is ran on the main thread
- 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
- Intuitive command system
### Requirements, Extensions & Supported Software
Requirements:
- Java 8+
- Minecraft 1.8 - 1.21
- Minecraft 1.8 - 1.21.8
Supported Softwares:
- Spigot ([Website](https://www.spigotmc.org/))
@ -40,7 +40,7 @@ Open an issue in the GitHub [issue tracker](https://github.com/Pyrbu/ZNPCsPlus/i
## Credits
- [PacketEvents 2.0](https://github.com/retrooper/packetevents) - Packet library
- [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
- [Mineskin.org](https://mineskin.org/) - Website for raw skin file uploads
- [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.npc.NpcRegistry;
import lol.pyr.znpcsplus.api.npc.NpcTypeRegistry;
import lol.pyr.znpcsplus.api.serialization.NpcSerializerRegistry;
import lol.pyr.znpcsplus.api.skin.SkinDescriptorFactory;
/**
@ -46,4 +47,10 @@ public interface NpcApi {
* @return the skin descriptor factory
*/
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 org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.ServicePriority;
/**
* 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
* You probably shouldn't call this method under any circumstances
*
* @param plugin Instance of the ZNPCsPlus plugin
* @param api Instance of the ZNPCsPlus entity property registry
*/
public static void register(EntityPropertyRegistry api) {
public static void register(Plugin plugin, EntityPropertyRegistry 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);
/**
* Register a dummy property that can be used to store unique information per npc<br>
* Note: Properties registered this way will be player-modifiable by default
*
* @param name The name of the new property
* @param type The type of the new property
* @deprecated Use {@link #registerDummy(String, Class, boolean)} instead
*/
@Deprecated
default void registerDummy(String name, Class<?> type) {
registerDummy(name, type, true);
}
/**
* Register a dummy property that can be used to store unique information per npc
*
* @param name The name of the new property
* @param type The type of the new property
* @param playerModifiable Whether this property can be modified by players using commands
*/
void registerDummy(String name, Class<?> type);
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 org.bukkit.World;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
* Base class for all NPCs
@ -135,14 +137,16 @@ public interface Npc extends PropertyHolder {
/**
* Shows this NPC to a player
* @param player The {@link Player} to show to
* @return A future that completes when the npc is fully shown to the player
*/
void show(Player player);
CompletableFuture<Void> show(Player player);
/**
* Respawns this NPC for a player
* @param player The {@link Player} to respawn for
* @return A future that completes when the npc is fully respawned
*/
void respawn(Player player);
CompletableFuture<Void> respawn(Player 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
*/
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
*/
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;
import java.net.URL;
import java.util.UUID;
/**
* Factory for creating skin descriptors.
@ -8,8 +9,10 @@ import java.net.URL;
public interface SkinDescriptorFactory {
SkinDescriptor createMirrorDescriptor();
SkinDescriptor createRefreshingDescriptor(String playerName);
SkinDescriptor createRefreshingDescriptor(UUID playerUUID);
SkinDescriptor createStaticDescriptor(String playerName);
SkinDescriptor createStaticDescriptor(String texture, String signature);
SkinDescriptor createUrlDescriptor(String url, String variant);
SkinDescriptor createUrlDescriptor(URL url, String variant);
SkinDescriptor createFileDescriptor(String path);
}

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 {
compileOnly "org.jetbrains:annotations:26.0.1"
compileOnly "org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT"
}

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists

View file

@ -9,8 +9,9 @@ processResources {
dependencies {
compileOnly "me.clip:placeholderapi:2.11.6" // Placeholder support
implementation "com.google.code.gson:gson:2.12.1" // JSON parsing
implementation "com.github.retrooper:packetevents-spigot:2.7.1-SNAPSHOT" // Packets
implementation "com.google.code.gson:gson:2.10.1" // JSON parsing
implementation "org.bstats:bstats-bukkit:3.0.2" // Plugin stats
implementation "com.github.retrooper:packetevents-spigot:2.9.3" // Packets
implementation "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs
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.SpigotScheduler;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.skin.cache.SkinCacheCleanTask;
import lol.pyr.znpcsplus.storage.NpcStorageType;
@ -92,10 +93,10 @@ public class ZNpcsPlus {
packetEvents.load();
configManager = new ConfigManager(getDataFolder());
skinCache = new MojangSkinCache(configManager);
skinCache = new MojangSkinCache(configManager, new File(getDataFolder(), "skins"));
propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager);
NpcPropertyRegistryProvider.register(propertyRegistry);
NpcPropertyRegistryProvider.register(bootstrap, propertyRegistry);
shutdownTasks.add(NpcPropertyRegistryProvider::unregister);
}
@ -128,14 +129,15 @@ public class ZNpcsPlus {
PacketFactory packetFactory = setupPacketFactory(scheduler, propertyRegistry, configManager);
propertyRegistry.registerTypes(bootstrap, packetFactory, textSerializer, scheduler);
propertyRegistry.registerTypes(packetFactory, textSerializer, scheduler);
BungeeConnector bungeeConnector = new BungeeConnector(bootstrap);
ActionRegistryImpl actionRegistry = new ActionRegistryImpl();
ActionFactoryImpl actionFactory = new ActionFactoryImpl(scheduler, adventure, textSerializer, bungeeConnector);
NpcTypeRegistryImpl typeRegistry = new NpcTypeRegistryImpl();
NpcSerializerRegistryImpl serializerRegistry = new NpcSerializerRegistryImpl(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
NpcRegistryImpl npcRegistry = new NpcRegistryImpl(configManager, this, packetFactory, actionRegistry,
scheduler, typeRegistry, propertyRegistry, textSerializer);
scheduler, typeRegistry, propertyRegistry, serializerRegistry, textSerializer);
shutdownTasks.add(npcRegistry::unload);
UserManager userManager = new UserManager();
@ -157,7 +159,7 @@ public class ZNpcsPlus {
pluginManager.registerEvents(new UserListener(userManager), bootstrap);
registerCommands(npcRegistry, skinCache, adventure, actionRegistry,
typeRegistry, propertyRegistry, importerRegistry, configManager, packetFactory);
typeRegistry, propertyRegistry, importerRegistry, configManager, packetFactory, serializerRegistry);
log(ChatColor.WHITE + " * Starting tasks...");
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("");
@ -244,7 +246,7 @@ public class ZNpcsPlus {
private void registerCommands(NpcRegistryImpl npcRegistry, MojangSkinCache skinCache, BukkitAudiences adventure,
ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry,
EntityPropertyRegistryImpl propertyRegistry, DataImporterRegistry importerRegistry,
ConfigManager configManager, PacketFactory packetFactory) {
ConfigManager configManager, PacketFactory packetFactory, NpcSerializerRegistryImpl serializerRegistry) {
Message<CommandContext> incorrectUsageMessage = context -> context.send(Component.text("Incorrect usage: /" + context.getUsage(), NamedTextColor.RED));
CommandManager manager = new CommandManager(bootstrap, adventure, incorrectUsageMessage);
@ -294,6 +296,7 @@ public class ZNpcsPlus {
registerEnumParser(manager, ArmadilloState.class, incorrectUsageMessage);
registerEnumParser(manager, WoldVariant.class, incorrectUsageMessage);
registerEnumParser(manager, NpcStorageType.class, incorrectUsageMessage);
registerEnumParser(manager, SkeletonType.class, incorrectUsageMessage);
manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root"))
.addSubcommand("center", new CenterCommand(npcRegistry))
@ -319,7 +322,7 @@ public class ZNpcsPlus {
.addSubcommand("save", new SaveAllCommand(npcRegistry))
.addSubcommand("reload", new LoadAllCommand(npcRegistry))
.addSubcommand("import", new ImportCommand(npcRegistry, importerRegistry))
.addSubcommand("migrate", new MigrateCommand(configManager, this, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, npcRegistry.getStorage(), configManager.getConfig().storageType(), npcRegistry)))
.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("add", new HoloAddCommand(npcRegistry))
.addSubcommand("additem", new HoloAddItemCommand(npcRegistry))
@ -339,6 +342,7 @@ public class ZNpcsPlus {
.addSubcommand("delete", new ActionDeleteCommand(npcRegistry))
.addSubcommand("edit", new ActionEditCommand(npcRegistry, actionRegistry))
.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.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.skin.SkinDescriptorFactoryImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
@ -22,14 +23,16 @@ public class ZNpcsPlusApi implements NpcApi {
private final ActionRegistryImpl actionRegistry;
private final ActionFactoryImpl actionFactory;
private final SkinDescriptorFactoryImpl skinDescriptorFactory;
private final NpcSerializerRegistryImpl npcSerializerRegistry;
public ZNpcsPlusApi(NpcRegistryImpl npcRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, ActionRegistryImpl actionRegistry, ActionFactoryImpl actionFactory, MojangSkinCache skinCache) {
public ZNpcsPlusApi(NpcRegistryImpl npcRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, ActionRegistryImpl actionRegistry, ActionFactoryImpl actionFactory, MojangSkinCache skinCache, NpcSerializerRegistryImpl npcSerializerRegistry) {
this.npcRegistry = npcRegistry;
this.typeRegistry = typeRegistry;
this.propertyRegistry = propertyRegistry;
this.actionRegistry = actionRegistry;
this.actionFactory = actionFactory;
this.skinDescriptorFactory = new SkinDescriptorFactoryImpl(skinCache);
this.npcSerializerRegistry = npcSerializerRegistry;
}
@Override
@ -62,4 +65,9 @@ public class ZNpcsPlusApi implements NpcApi {
public SkinDescriptorFactory getSkinDescriptorFactory() {
return skinDescriptorFactory;
}
@Override
public NpcSerializerRegistryImpl getNpcSerializerRegistry() {
return npcSerializerRegistry;
}
}

View file

@ -26,12 +26,18 @@ public class CreateCommand implements CommandHandler {
@Override
public void run(CommandContext context) throws CommandExecutionException {
context.setUsage(context.getLabel() + " create <id> <type>");
context.setUsage(context.getLabel() + " create <id> [<type>]");
Player player = context.ensureSenderIsPlayer();
String id = context.popString();
if (npcRegistry.getById(id) != null) context.halt(Component.text("NPC with that ID already exists.", NamedTextColor.RED));
NpcTypeImpl type = 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()));
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.NpcTypeRegistryImpl;
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.PrefetchedDescriptor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class SkinCommand implements CommandHandler {
private final MojangSkinCache skinCache;
@ -63,7 +67,7 @@ public class SkinCommand implements CommandHandler {
} else if (type.equalsIgnoreCase("dynamic")) {
context.ensureArgsNotEmpty();
String name = context.dumpAllArgs();
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new FetchingDescriptor(skinCache, name));
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new NameFetchingDescriptor(skinCache, name));
npc.respawn();
context.halt(Component.text("The NPC's skin will now be resolved per-player from \"" + name + "\""));
} else if (type.equalsIgnoreCase("url")) {
@ -90,6 +94,30 @@ public class SkinCommand implements CommandHandler {
context.send(Component.text("Invalid url!", NamedTextColor.RED));
}
return;
} else if (type.equalsIgnoreCase("file")) {
context.ensureArgsNotEmpty();
String path = context.dumpAllArgs();
context.send(Component.text("Fetching skin from file \"" + path + "\"...", NamedTextColor.GREEN));
PrefetchedDescriptor.fromFile(skinCache, path).exceptionally(e -> {
if (e instanceof FileNotFoundException || e.getCause() instanceof FileNotFoundException) {
context.send(Component.text("A file at the specified path could not be found!", NamedTextColor.RED));
} else {
context.send(Component.text("An error occurred while fetching the skin from file! Check the console for more details.", NamedTextColor.RED));
//noinspection CallToPrintStackTrace
e.printStackTrace();
}
return null;
}).thenAccept(skin -> {
if (skin == null) return;
if (skin.getSkin() == null) {
context.send(Component.text("Failed to fetch skin, are you sure the file path is valid?", NamedTextColor.RED));
return;
}
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), skin);
npc.respawn();
context.send(Component.text("The NPC's skin has been set.", NamedTextColor.GREEN));
});
return;
}
context.send(Component.text("Unknown skin type! Please use one of the following: mirror, static, dynamic, url"));
}
@ -97,11 +125,19 @@ public class SkinCommand implements CommandHandler {
@Override
public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds());
if (context.argSize() == 2) return context.suggestLiteral("mirror", "static", "dynamic", "url");
if (context.argSize() == 2) return context.suggestLiteral("mirror", "static", "dynamic", "url", "file");
if (context.matchSuggestion("*", "static")) return context.suggestPlayers();
if (context.argSize() == 3 && context.matchSuggestion("*", "url")) {
return context.suggestLiteral("slim", "classic");
}
if (context.argSize() == 3 && context.matchSuggestion("*", "file")) {
if (skinCache.getSkinsFolder().exists()) {
File[] files = skinCache.getSkinsFolder().listFiles();
if (files != null) {
return Arrays.stream(files).map(File::getName).collect(Collectors.toList());
}
}
}
return Collections.emptyList();
}
}

View file

@ -21,9 +21,14 @@ public class ToggleCommand implements CommandHandler {
@Override
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();
boolean enabled = !npc.isEnabled();
boolean enabled;
if (context.argSize() == 1) {
enabled = context.popString().equalsIgnoreCase("enable");
} else {
enabled = !npc.isEnabled();
}
npc.setEnabled(enabled);
context.send(Component.text("NPC has been " + (enabled ? "enabled" : "disabled"), NamedTextColor.GREEN));
}
@ -31,6 +36,7 @@ public class ToggleCommand implements CommandHandler {
@Override
public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds());
if (context.argSize() == 2) return context.suggestLiteral("enable", "disable");
return Collections.emptyList();
}
}

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.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.properties.attributes.AttributeProperty;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
@ -44,6 +45,7 @@ public class PropertySetCommand implements CommandHandler {
// TODO: find a way to do this better & rewrite this mess
if (!npc.getType().getAllowedProperties().contains(property)) context.halt(Component.text("Property " + property.getName() + " not allowed for npc type " + npc.getType().getName(), NamedTextColor.RED));
if (!property.isPlayerModifiable()) context.halt(Component.text("This property is not modifiable by players", NamedTextColor.RED));
Class<?> type = property.getType();
Object value;
String valueName;
@ -124,6 +126,15 @@ public class PropertySetCommand implements CommandHandler {
value = context.parse(type);
valueName = value == null ? "NONE" : ((Vector3i) value).toPrettyString();
}
else if (property instanceof AttributeProperty) {
value = context.parse(type);
if ((double) value < ((AttributeProperty) property).getMinValue() || (double) value > ((AttributeProperty) property).getMaxValue()) {
double sanitizedValue = ((AttributeProperty) property).sanitizeValue((double) value);
context.send(Component.text("WARNING: Value " + value + " is out of range for property " + property.getName() + ", setting to " + sanitizedValue, NamedTextColor.YELLOW));
value = sanitizedValue;
}
valueName = String.valueOf(value);
}
else {
try {
value = context.parse(type);

View file

@ -7,13 +7,13 @@ import lol.pyr.znpcsplus.conversion.DataImporter;
import lol.pyr.znpcsplus.conversion.DataImporterRegistry;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.util.FutureUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class ImportCommand implements CommandHandler {
private final NpcRegistryImpl npcRegistry;
@ -33,7 +33,7 @@ public class ImportCommand implements CommandHandler {
if (importer == null) context.halt(Component.text("Importer not found! Possible importers: " +
String.join(", ", importerRegistry.getIds()), NamedTextColor.RED));
CompletableFuture.runAsync(() -> {
FutureUtil.exceptionPrintingRunAsync(() -> {
if (!importer.isValid()) {
context.send(Component.text("There is no data to import from this importer!", NamedTextColor.RED));
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.common.command.CommandExecutionException;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.util.FutureUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class LoadAllCommand implements CommandHandler {
private final NpcRegistryImpl npcRegistry;
@ -20,7 +20,7 @@ public class LoadAllCommand implements CommandHandler {
@Override
public void run(CommandContext context) throws CommandExecutionException {
CompletableFuture.runAsync(() -> {
FutureUtil.exceptionPrintingRunAsync(() -> {
npcRegistry.reload();
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.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.storage.NpcStorage;
import lol.pyr.znpcsplus.storage.NpcStorageType;
import net.kyori.adventure.text.Component;
@ -35,8 +36,9 @@ public class MigrateCommand implements CommandHandler {
private final NpcStorage currentStorage;
private final NpcStorageType currentStorageType;
private final NpcRegistryImpl npcRegistry;
private final NpcSerializerRegistryImpl serializerRegistry;
public MigrateCommand(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcStorage currentStorage, NpcStorageType currentStorageType, NpcRegistryImpl npcRegistry) {
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.plugin = plugin;
this.packetFactory = packetFactory;
@ -47,6 +49,7 @@ public class MigrateCommand implements CommandHandler {
this.currentStorage = currentStorage;
this.currentStorageType = currentStorageType;
this.npcRegistry = npcRegistry;
this.serializerRegistry = serializerRegistry;
}
@Override
@ -63,7 +66,7 @@ public class MigrateCommand implements CommandHandler {
if (currentStorageType == from) {
fromStorage = currentStorage;
} 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) {
context.halt(Component.text("Failed to initialize the source storage. Please check the console for more information.", NamedTextColor.RED));
return;
@ -84,7 +87,7 @@ public class MigrateCommand implements CommandHandler {
if (currentStorageType == to) {
toStorage = currentStorage;
} 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) {
context.halt(Component.text("Failed to initialize the destination storage. Please check the console for more information.", NamedTextColor.RED));
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.common.command.CommandExecutionException;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.util.FutureUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class SaveAllCommand implements CommandHandler {
private final NpcRegistryImpl npcRegistry;
@ -20,7 +20,7 @@ public class SaveAllCommand implements CommandHandler {
@Override
public void run(CommandContext context) throws CommandExecutionException {
CompletableFuture.runAsync(() -> {
FutureUtil.exceptionPrintingRunAsync(() -> {
npcRegistry.save();
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) {
List<String> keys = new ArrayList<>(linesSection.getKeys(false));
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) {
Component component = textSerializer.deserialize(line);
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.skin.SkinImpl;
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.PrefetchedDescriptor;
import lol.pyr.znpcsplus.util.BungeeConnector;
@ -175,7 +175,7 @@ public class ZNpcImporter implements DataImporter {
}
if (model.getSkinName() != null) {
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new FetchingDescriptor(skinCache, model.getSkinName()));
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new NameFetchingDescriptor(skinCache, model.getSkinName()));
}
else if (model.getSkin() != null && model.getSignature() != null) {
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new SkinImpl(model.getSkin(), model.getSignature())));
@ -189,12 +189,15 @@ public class ZNpcImporter implements DataImporter {
if (toggleValues.containsKey("mirror")) {
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new MirrorDescriptor(skinCache));
}
if (toggleValues.containsKey("glow")) {
try {
npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.valueOf((String) toggleValues.get("glow")));
} catch (IllegalArgumentException e) {
if (toggleValues.containsKey("glow") && (boolean) toggleValues.get("glow")) {
if (!model.getGlowName().isEmpty())
try {
npc.setProperty(propertyRegistry.getByName("glow", DyeColor.class), DyeColor.valueOf(model.getGlowName()));
} catch (IllegalArgumentException e) {
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() {
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);
}
protected static <V> EntityData newEntityData(int index, EntityDataType<V> type, V value) {
return new EntityData(index, type, value);
protected static <V> EntityData<V> newEntityData(int index, EntityDataType<V> type, V value) {
return new EntityData<>(index, type, value);
}
public List<EntityData> applyStandalone(Player player, PacketEntity packetEntity, boolean isSpawned) {
Map<Integer, EntityData> map = new HashMap<>();
public List<EntityData<?>> applyStandalone(Player player, PacketEntity packetEntity, boolean isSpawned) {
Map<Integer, EntityData<?>> map = new HashMap<>();
apply(player, packetEntity, isSpawned, map);
for (EntityPropertyImpl<?> property : dependencies) property.apply(player, packetEntity, isSpawned, map);
return new ArrayList<>(map.values());
}
abstract public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties);
abstract public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties);
}

View file

@ -2,6 +2,7 @@ package lol.pyr.znpcsplus.entity;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
import com.github.retrooper.packetevents.protocol.entity.pose.EntityPose;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
@ -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.player.EquipmentSlot;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import lol.pyr.znpcsplus.ZNpcsPlusBootstrap;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.properties.*;
import lol.pyr.znpcsplus.entity.properties.attributes.AttributeProperty;
import lol.pyr.znpcsplus.entity.properties.villager.VillagerLevelProperty;
import lol.pyr.znpcsplus.entity.properties.villager.VillagerProfessionProperty;
import lol.pyr.znpcsplus.entity.properties.villager.VillagerTypeProperty;
@ -32,18 +33,19 @@ import java.util.*;
import java.util.stream.Collectors;
/**
* 1.8 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=7415">...</a>
* 1.9 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=7968">...</a>
* 1.10 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=8241">...</a>
* 1.11 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=8534">...</a>
* 1.12 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=14048">...</a>
* 1.13 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=14800">...</a>
* 1.14 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=15240">...</a>
* 1.15 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=15991">...</a>
* 1.16 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=16539">...</a>
* 1.17 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=17521">...</a>
* 1.18-1.19 <a href="https://wiki.vg/index.php?title=Entity_metadata&oldid=18191">...</a>
* 1.20 <a href="https://wiki.vg/index.php?title=Entity_metadata">...</a>
* 1.8 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2767708">...</a>
* 1.9 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768074">...</a>
* 1.10 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768201">...</a>
* 1.11 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768444">...</a>
* 1.12 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768647">...</a>
* 1.13 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768701">...</a>
* 1.14 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768716">...</a>
* 1.15 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2768877">...</a>
* 1.16 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2769100">...</a>
* 1.17 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2769318">...</a>
* 1.18-1.19 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2769409">...</a>
* 1.20 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata?oldid=2769476">...</a>
* 1.21 <a href="https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata">...</a>
*/
@SuppressWarnings("unchecked")
public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
@ -90,6 +92,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
registerEnumSerializer(Sound.class);
registerEnumSerializer(ArmadilloState.class);
registerEnumSerializer(WoldVariant.class);
registerEnumSerializer(SkeletonType.class);
registerPrimitiveSerializers(Integer.class, Boolean.class, Double.class, Float.class, Long.class, Short.class, Byte.class, String.class);
@ -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();
boolean legacyBooleans = 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_distance", configManager.getConfig().lookPropertyDistance()));
register(new DummyProperty<>("look_return", false));
register(new DummyProperty<>("view_distance", configManager.getConfig().viewDistance()));
register(new DummyProperty<>("permission_required", false));
register(new ForceBodyRotationProperty(plugin, taskScheduler));
register(new ForceBodyRotationProperty(taskScheduler));
register(new DummyProperty<>("player_knockback", false));
register(new DummyProperty<>("player_knockback_exempt_permission", String.class));
@ -151,6 +155,16 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
linkProperties("glow", "fire", "invisible");
register(new BooleanProperty("silent", 4, false, legacyBooleans));
// Attribute Max Health
register(new AttributeProperty(packetFactory, "attribute_max_health", Attributes.MAX_HEALTH));
// Health - LivingEntity
int healthIndex = 6;
if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) healthIndex = 9;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_14)) healthIndex = 8;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_10)) healthIndex = 7;
register(new HealthProperty(healthIndex));
final int tameableIndex;
if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) tameableIndex = 17;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) tameableIndex = 16;
@ -177,11 +191,13 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_9)) babyIndex = 11;
else babyIndex = 12;
if (ver.isOlderThan(ServerVersion.V_1_9)) {
register(new EncodedByteProperty<>("baby", false, babyIndex, obj -> (byte) (obj ? -1 : 0)));
register(new LegacyBabyProperty(babyIndex));
} else {
register(new BooleanProperty("baby", babyIndex, false, legacyBooleans));
}
register(new EntitySittingProperty(packetFactory, this));
// Player
register(new DummyProperty<>("skin", SkinDescriptor.class, false));
final int skinLayersIndex;
@ -269,7 +285,9 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
else horseIndex = 16;
int horseEating = ver.isNewerThanOrEquals(ServerVersion.V_1_12) ? 0x10 : 0x20;
register(new BitsetProperty("is_tame", horseIndex, 0x02, false, legacyBooleans));
register(new BitsetProperty("is_saddled", horseIndex, 0x04, false, legacyBooleans));
if (ver.isOlderThan(ServerVersion.V_1_21)) {
register(new BitsetProperty("is_saddled", horseIndex, 0x04, false, legacyBooleans));
}
register(new BitsetProperty("is_eating", horseIndex, horseEating, false, legacyBooleans));
register(new BitsetProperty("is_rearing", horseIndex, horseEating << 1, false, legacyBooleans));
register(new BitsetProperty("has_mouth_open", horseIndex, horseEating << 2, false, legacyBooleans));
@ -337,10 +355,14 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
// Chested Horse
if (ver.isOlderThan(ServerVersion.V_1_11)) {
register(new BitsetProperty("has_chest", horseIndex, 0x08, false, legacyBooleans));
linkProperties("is_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 {
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
@ -436,6 +458,39 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
witherIndex += 3; // skip the first 3 indexes, will be used for the other properties later
register(new IntegerProperty("invulnerable_time", witherIndex, 0, false));
// Skeleton
if (ver.isOlderThan(ServerVersion.V_1_11)) {
if (legacyBooleans) register(new EncodedByteProperty<>("skeleton_type", SkeletonType.NORMAL, 13, SkeletonType::getLegacyId));
else register(new EncodedIntegerProperty<>("skeleton_type", SkeletonType.NORMAL, ver.isOlderThan(ServerVersion.V_1_10) ? 11 : 12, Enum::ordinal));
}
// Zombie
int zombieIndex;
if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) zombieIndex = 17;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_16)) zombieIndex = 16;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_10)) zombieIndex = 13;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_9)) zombieIndex = 12;
else zombieIndex = 13;
if (ver.isOlderThan(ServerVersion.V_1_9)) {
register(new EncodedByteProperty<>("zombie_is_villager", false, zombieIndex++, b -> (byte) (b ? 1 : 0)));
} else if (ver.isOlderThan(ServerVersion.V_1_11)) {
register(new EncodedIntegerProperty<>("zombie_type", ZombieType.ZOMBIE, zombieIndex++, Enum::ordinal));
} else {
zombieIndex++; // Not a mistake, this is field unused in 1.11+
}
if (ver.isOlderThan(ServerVersion.V_1_9)) {
register(new EncodedByteProperty<>("is_converting", false, zombieIndex++, b -> (byte) (b ? 1 : 0)));
} else if (ver.isOlderThan(ServerVersion.V_1_11)) {
register(new BooleanProperty("is_converting", zombieIndex++, false, legacyBooleans));
}
if (ver.isNewerThanOrEquals(ServerVersion.V_1_9) && ver.isOlderThan(ServerVersion.V_1_14)) {
register(new BooleanProperty("zombie_hands_held_up", zombieIndex++, false, legacyBooleans));
}
if (ver.isNewerThanOrEquals(ServerVersion.V_1_13)) {
register(new BooleanProperty("zombie_becoming_drowned", zombieIndex++, false, legacyBooleans));
}
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_9)) return;
// Shulker
int shulkerIndex;
@ -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_14)) llamaIndex = 19;
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));
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;
register(new EquipmentProperty(packetFactory, "body", EquipmentSlot.BODY));
// Bogged
register(new BooleanProperty("bogged_sheared", 16, false, legacyBooleans));
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21_2)) return;
// Creaking
register(new BooleanProperty("creaking_active", 17, false, legacyBooleans));
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21_4)) return;
// Creaking
register(new BooleanProperty("creaking_crumbling", 18, false, legacyBooleans));
}
private void registerSerializer(PropertySerializer<?> serializer) {
@ -721,8 +790,8 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
}
@Override
public void registerDummy(String name, Class<?> type) {
register(new DummyProperty<>(name, type));
public void registerDummy(String name, Class<?> type, boolean playerModifiable) {
register(new DummyProperty<>(name, type, playerModifiable));
}
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.packets.PacketFactory;
import lol.pyr.znpcsplus.reflection.Reflections;
import lol.pyr.znpcsplus.util.FutureUtil;
import lol.pyr.znpcsplus.util.NpcLocation;
import lol.pyr.znpcsplus.util.Viewable;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.CompletableFuture;
public class PacketEntity implements PropertyHolder {
private final PacketFactory packetFactory;
private final PropertyHolder properties;
private final Viewable viewable;
private final int entityId;
private final UUID uuid;
private final EntityType type;
private NpcLocation location;
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.properties = properties;
this.viewable = viewable;
this.entityId = reserveEntityID();
this.uuid = UUID.randomUUID();
this.type = type;
@ -51,22 +58,120 @@ public class PacketEntity implements PropertyHolder {
return type;
}
public void setLocation(NpcLocation location, Collection<Player> viewers) {
public void setLocation(NpcLocation 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) {
if (type == EntityTypes.PLAYER) packetFactory.spawnPlayer(player, this, properties);
else packetFactory.spawnEntity(player, this, properties);
public CompletableFuture<Void> spawn(Player player) {
return FutureUtil.exceptionPrintingRunAsync(() -> {
if (type == EntityTypes.PLAYER) packetFactory.spawnPlayer(player, this, properties).join();
else packetFactory.spawnEntity(player, this, properties);
if (vehicle != null) {
setVehicle(vehicle);
}
if (vehicleId != null) {
packetFactory.setPassengers(player, vehicleId, this.getEntityId());
}
if (passengers != null) {
packetFactory.setPassengers(player, this.getEntityId(), passengers.stream().mapToInt(Integer::intValue).toArray());
}
});
}
public void setHeadRotation(Player player, float yaw, float pitch) {
packetFactory.sendHeadRotation(player, this, yaw, pitch);
}
public PacketEntity getVehicle() {
return vehicle;
}
public Viewable getViewable() {
return viewable;
}
public void setVehicleId(Integer vehicleId) {
if (this.vehicle != null) {
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.vehicle.getEntityId());
this.vehicle.despawn(player);
packetFactory.teleportEntity(player, this);
}
} else if (this.vehicleId != null) {
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.vehicleId);
}
}
this.vehicleId = vehicleId;
if (vehicleId == null) return;
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.getEntityId(), vehicleId);
}
}
public void setVehicle(PacketEntity vehicle) {
// remove old vehicle
if (this.vehicle != null) {
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.vehicle.getEntityId());
this.vehicle.despawn(player);
packetFactory.teleportEntity(player, this);
}
} else if (this.vehicleId != null) {
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.vehicleId);
}
}
this.vehicle = vehicle;
if (this.vehicle == null) return;
vehicle.setLocation(location.withY(location.getY() - 0.9));
for (Player player : viewable.getViewers()) {
vehicle.spawn(player).thenRun(() -> {
packetFactory.setPassengers(player, vehicle.getEntityId(), this.getEntityId());
});
}
}
public Integer getVehicleId() {
return vehicleId;
}
public List<Integer> getPassengers() {
return passengers == null ? Collections.emptyList() : passengers;
}
public void addPassenger(int entityId) {
if (passengers == null) {
passengers = new ArrayList<>();
}
passengers.add(entityId);
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.getEntityId(), passengers.stream().mapToInt(Integer::intValue).toArray());
}
}
public void removePassenger(int entityId) {
if (passengers == null) return;
passengers.remove(entityId);
for (Player player : viewable.getViewers()) {
packetFactory.setPassengers(player, this.getEntityId(), passengers.stream().mapToInt(Integer::intValue).toArray());
}
if (passengers.isEmpty()) {
passengers = null;
}
}
public void despawn(Player player) {
packetFactory.destroyEntity(player, this, properties);
if (vehicle != null) vehicle.despawn(player);
}
public void refreshMeta(Player player) {

View file

@ -31,13 +31,22 @@ public class BitsetProperty extends EntityPropertyImpl<Boolean> {
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
EntityData oldData = properties.get(index);
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
EntityData<?> oldData = properties.get(index);
boolean enabled = entity.getProperty(this);
if (inverted) enabled = !enabled;
properties.put(index,
integer ? newEntityData(index, EntityDataTypes.INT, (oldData == null ? 0 : (int) oldData.getValue()) | (enabled ? bitmask : 0)) :
newEntityData(index, EntityDataTypes.BYTE, (byte) ((oldData == null ? 0 : (byte) oldData.getValue()) | (enabled ? bitmask : 0))));
if (integer) {
int oldValue = 0;
if (oldData != null && oldData.getValue() instanceof Number) {
oldValue = ((Number) oldData.getValue()).intValue();
}
properties.put(index, newEntityData(index, EntityDataTypes.INT, oldValue | (enabled ? bitmask : 0)));
} else {
byte oldValue = 0;
if (oldData != null && oldData.getValue() instanceof Number) {
oldValue = ((Number) oldData.getValue()).byteValue();
}
properties.put(index, newEntityData(index, EntityDataTypes.BYTE, (byte) (oldValue | (enabled ? bitmask : 0))));
}
}
}

View file

@ -25,7 +25,7 @@ public class BooleanProperty extends EntityPropertyImpl<Boolean> {
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
boolean enabled = entity.getProperty(this);
if (inverted) enabled = !enabled;
if (legacy) properties.put(index, newEntityData(index, EntityDataTypes.BYTE, (byte) (enabled ? 1 : 0)));

View file

@ -20,7 +20,7 @@ public class CamelSittingProperty extends EntityPropertyImpl<Boolean> {
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
boolean value = entity.getProperty(this);
if (value) {
properties.put(poseIndex, newEntityData(poseIndex, EntityDataTypes.ENTITY_POSE, EntityPose.SITTING));

View file

@ -22,7 +22,7 @@ public class CustomTypeProperty<T, U> extends EntityPropertyImpl<T> {
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
properties.put(index, newEntityData(index, type, decoder.decode(entity.getProperty(this))));
}

View file

@ -1,7 +1,6 @@
package lol.pyr.znpcsplus.entity.properties;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataType;
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
@ -16,20 +15,21 @@ import java.util.Optional;
public class DinnerboneProperty extends EntityPropertyImpl<Boolean> {
private final boolean optional;
private final Object serialized;
private final EntityDataType<?> type;
public DinnerboneProperty(boolean legacy, boolean optional) {
super("dinnerbone", false, Boolean.class);
this.optional = optional;
Component name = Component.text("Dinnerbone");
Object serialized = legacy ? AdventureSerializer.getLegacyGsonSerializer().serialize(name) :
this.serialized = legacy ? AdventureSerializer.serializer().legacy().serialize(name) :
optional ? name : LegacyComponentSerializer.legacySection().serialize(name);
this.serialized = optional ? Optional.of(serialized) : serialized;
this.type = optional ? EntityDataTypes.OPTIONAL_ADV_COMPONENT : EntityDataTypes.STRING;
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
properties.put(2, new EntityData(2, type, entity.getProperty(this) ? serialized : optional ? null : ""));
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
if (optional) {
properties.put(2, new EntityData<>(2, EntityDataTypes.OPTIONAL_ADV_COMPONENT, entity.getProperty(this) ? Optional.of((Component) serialized) : Optional.empty()));
} else {
properties.put(2, new EntityData<>(2, EntityDataTypes.STRING, entity.getProperty(this) ? (String) serialized : ""));
}
}
}

View file

@ -28,6 +28,6 @@ public class DummyProperty<T> extends EntityPropertyImpl<T> {
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
}
}

View file

@ -36,7 +36,7 @@ public class EncodedByteProperty<T> extends EntityPropertyImpl<T> {
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
T value = entity.getProperty(this);
if (value == null) return;
properties.put(index, newEntityData(index, type, decoder.decode(value)));

View file

@ -36,7 +36,7 @@ public class EncodedIntegerProperty<T> extends EntityPropertyImpl<T> {
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
T value = entity.getProperty(this);
if (value == null) return;
properties.put(index, newEntityData(index, type, decoder.decode(value)));

View file

@ -36,7 +36,7 @@ public class EncodedStringProperty<T> extends EntityPropertyImpl<T> {
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
T value = entity.getProperty(this);
if (value == null) return;
properties.put(index, newEntityData(index, type, decoder.decode(value)));

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
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)));
}
}

View file

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

View file

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

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
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
properties.put(8, newEntityData(8, EntityDataTypes.ITEMSTACK, entity.getProperty(this)));
properties.put(5, newEntityData(5, EntityDataTypes.BOOLEAN, true));
}

View file

@ -18,9 +18,13 @@ public class HorseColorProperty extends EntityPropertyImpl<HorseColor> {
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
EntityData oldData = properties.get(index);
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
EntityData<?> oldData = properties.get(index);
HorseColor value = entity.getProperty(this);
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
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
EntityData oldData = properties.get(index);
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
EntityData<?> oldData = properties.get(index);
HorseStyle value = entity.getProperty(this);
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
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
properties.put(index, legacy ?
newEntityData(index, EntityDataTypes.BYTE, (byte) entity.getProperty(this).intValue()) :
newEntityData(index, EntityDataTypes.INT, entity.getProperty(this)));

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
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
T value = entity.getProperty(this);
if (value == null && !allowNull) return;
properties.put(index, newEntityData(index, type, decoder.decode(value)));

View file

@ -27,14 +27,17 @@ public class NameProperty extends EntityPropertyImpl<Component> {
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
Component value = entity.getProperty(this);
if (value != null) {
value = PapiUtil.set(legacySerializer, player, value);
Object serialized = legacySerialization ? AdventureSerializer.getLegacyGsonSerializer().serialize(value) :
optional ? value : LegacyComponentSerializer.legacySection().serialize(value);
if (optional) properties.put(2, new EntityData(2, EntityDataTypes.OPTIONAL_ADV_COMPONENT, Optional.of(serialized)));
else properties.put(2, new EntityData(2, EntityDataTypes.STRING, serialized));
if (legacySerialization) {
properties.put(2, newEntityData(2, EntityDataTypes.STRING, AdventureSerializer.serializer().asJson(value)));
} else if (optional) {
properties.put(2, newEntityData(2, EntityDataTypes.OPTIONAL_ADV_COMPONENT, Optional.of(value)));
} else {
properties.put(2, newEntityData(2, EntityDataTypes.STRING, LegacyComponentSerializer.legacySection().serialize(value)));
}
}
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
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
Vector3i value = entity.getProperty(this);
if (value == null) properties.put(index, new EntityData(index, EntityDataTypes.OPTIONAL_BLOCK_POSITION, Optional.empty()));
else properties.put(index, new EntityData(index, EntityDataTypes.OPTIONAL_BLOCK_POSITION,
if (value == null) properties.put(index, new EntityData<>(index, EntityDataTypes.OPTIONAL_BLOCK_POSITION, Optional.empty()));
else properties.put(index, new EntityData<>(index, EntityDataTypes.OPTIONAL_BLOCK_POSITION,
Optional.of(new com.github.retrooper.packetevents.util.Vector3i(value.getX(), value.getY(), value.getZ()))));
}
}

View file

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

View file

@ -18,7 +18,7 @@ public class TargetNpcProperty extends EntityPropertyImpl<NpcEntryImpl> {
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
NpcEntryImpl value = entity.getProperty(this);
if (value == null) return;
if (value.getNpc().getEntity().getEntityId() == entity.getEntityId()) return;

View file

@ -25,15 +25,14 @@ public class TropicalFishVariantProperty<T> extends EntityPropertyImpl<T> {
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
T value = entity.getProperty(this);
if (value == null) {
return;
}
EntityData oldData = properties.get(index);
if (value == null) return;
EntityData<?> oldData = properties.get(index);
TropicalFishVariant.Builder builder;
if (oldData != null && oldData.getType() == EntityDataTypes.INT && oldData.getValue() != null) {
int oldVal = (int) oldData.getValue();
if (oldData != null && oldData.getType() == EntityDataTypes.INT && oldData.getValue() instanceof Integer) {
int oldVal = (Integer) oldData.getValue();
builder = TropicalFishVariant.Builder.fromInt(oldVal);
} else {
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
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
EntityData oldData = properties.get(index);
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData<?>> properties) {
EntityData<?> oldData = properties.get(index);
VillagerData old = oldData == null ? new VillagerData(VillagerTypes.PLAINS, VillagerProfessions.NONE, 1) : (VillagerData) oldData.getValue();
properties.put(index, newEntityData(index, EntityDataTypes.VILLAGER_DATA, apply(old, entity.getProperty(this))));
}

View file

@ -6,6 +6,7 @@ import lol.pyr.znpcsplus.api.hologram.Hologram;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.util.FutureUtil;
import lol.pyr.znpcsplus.util.NpcLocation;
import lol.pyr.znpcsplus.util.Viewable;
import net.kyori.adventure.text.Component;
@ -16,6 +17,8 @@ import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class HologramImpl extends Viewable implements Hologram {
private final ConfigManager configManager;
@ -38,14 +41,15 @@ public class HologramImpl extends Viewable implements Hologram {
}
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);
relocateLines(newLine);
relocateLines();
for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
}
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) {
@ -57,9 +61,9 @@ public class HologramImpl extends Viewable implements Hologram {
}
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);
relocateLines(newLine);
relocateLines();
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) {
HologramText newLine = new HologramText(propertyRegistry, packetFactory, null, line);
HologramText newLine = new HologramText(this, propertyRegistry, packetFactory, null, line);
lines.add(index, newLine);
relocateLines(newLine);
relocateLines();
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) {
HologramItem newLine = new HologramItem(propertyRegistry, packetFactory, null, item);
HologramItem newLine = new HologramItem(this, propertyRegistry, packetFactory, null, item);
lines.add(index, newLine);
relocateLines(newLine);
relocateLines();
for (Player viewer : getViewers()) newLine.show(viewer.getPlayer());
}
@ -144,8 +148,10 @@ public class HologramImpl extends Viewable implements Hologram {
}
@Override
protected void UNSAFE_show(Player player) {
for (HologramLine<?> line : lines) line.show(player);
protected CompletableFuture<Void> UNSAFE_show(Player player) {
return FutureUtil.allOf(lines.stream()
.map(line -> line.show(player))
.collect(Collectors.toList()));
}
@Override
@ -178,14 +184,10 @@ public class HologramImpl extends Viewable implements Hologram {
}
private void relocateLines() {
relocateLines(null);
}
private void relocateLines(HologramLine<?> newLine) {
final double lineSpacing = configManager.getConfig().lineSpacing();
double height = location.getY() + (lines.size() - 1) * lineSpacing + getOffset();
for (HologramLine<?> line : lines) {
line.setLocation(location.withY(height), line == newLine ? Collections.emptySet() : getViewers());
line.setLocation(location.withY(height));
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.packets.PacketFactory;
import lol.pyr.znpcsplus.util.NpcLocation;
import org.bukkit.entity.Player;
import java.util.Collection;
import lol.pyr.znpcsplus.util.Viewable;
public class HologramItem extends HologramLine<ItemStack> {
public HologramItem(EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, ItemStack item) {
super(item, packetFactory, EntityTypes.ITEM, location);
public HologramItem(Viewable viewable, EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, ItemStack item) {
super(viewable, item, packetFactory, EntityTypes.ITEM, location);
addProperty(propertyRegistry.getByName("holo_item"));
}
@ -33,8 +31,8 @@ public class HologramItem extends HologramLine<ItemStack> {
}
@Override
public void setLocation(NpcLocation location, Collection<Player> viewers) {
super.setLocation(location.withY(location.getY() + 2.05), viewers);
public void setLocation(NpcLocation location) {
super.setLocation(location.withY(location.getY() + 2.05));
}
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.packets.PacketFactory;
import lol.pyr.znpcsplus.util.NpcLocation;
import lol.pyr.znpcsplus.util.Viewable;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
public class HologramLine<M> implements PropertyHolder {
private M value;
private final PacketEntity entity;
private final Set<EntityProperty<?>> properties;
public HologramLine(M value, PacketFactory packetFactory, EntityType type, NpcLocation location) {
public HologramLine(Viewable viewable, M value, PacketFactory packetFactory, EntityType type, NpcLocation location) {
this.value = value;
this.entity = new PacketEntity(packetFactory, this, type, location);
this.entity = new PacketEntity(packetFactory, this, viewable, type, location);
this.properties = new HashSet<>();
}
@ -37,16 +38,16 @@ public class HologramLine<M> implements PropertyHolder {
entity.refreshMeta(player);
}
protected void show(Player player) {
entity.spawn(player);
protected CompletableFuture<Void> show(Player player) {
return entity.spawn(player);
}
protected void hide(Player player) {
entity.despawn(player);
}
public void setLocation(NpcLocation location, Collection<Player> viewers) {
entity.setLocation(location, viewers);
public void setLocation(NpcLocation location) {
entity.setLocation(location);
}
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.packets.PacketFactory;
import lol.pyr.znpcsplus.util.NpcLocation;
import lol.pyr.znpcsplus.util.Viewable;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
public class HologramText extends HologramLine<Component> {
private static final Component BLANK = Component.text("%blank%");
public HologramText(EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, Component text) {
super(text, packetFactory, EntityTypes.ARMOR_STAND, location);
public HologramText(Viewable viewable, EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, Component text) {
super(viewable, text, packetFactory, EntityTypes.ARMOR_STAND, location);
addProperty(propertyRegistry.getByName("name"));
addProperty(propertyRegistry.getByName("invisible"));
}
@Override
public void show(Player player) {
if (!getValue().equals(BLANK)) {
super.show(player);
}
public CompletableFuture<Void> show(Player player) {
if (getValue().equals(BLANK)) return CompletableFuture.completedFuture(null);
return super.show(player);
}
@SuppressWarnings("unchecked")

View file

@ -32,7 +32,7 @@ public class SwitchServerAction extends InteractionActionImpl {
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT,
Component.text("Click to edit this action", NamedTextColor.GRAY)))
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND,
"/" + context.getLabel() + " action edit " + id + " " + index + " 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("[DELETE]", NamedTextColor.RED)
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT,

View file

@ -20,9 +20,12 @@ import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class NpcImpl extends Viewable implements Npc {
@ -38,6 +41,8 @@ public class NpcImpl extends Viewable implements Npc {
private final Map<EntityPropertyImpl<?>, Object> propertyMap = new HashMap<>();
private final List<InteractionAction> actions = new ArrayList<>();
private final Map<UUID, float[]> playerLookMap = new ConcurrentHashMap<>();
protected NpcImpl(UUID uuid, EntityPropertyRegistryImpl propertyRegistry, ConfigManager configManager, LegacyComponentSerializer textSerializer, World world, NpcTypeImpl type, NpcLocation location, PacketFactory packetFactory) {
this(uuid, propertyRegistry, configManager, packetFactory, textSerializer, world.getName(), type, location);
}
@ -48,14 +53,14 @@ public class NpcImpl extends Viewable implements Npc {
this.type = type;
this.location = location;
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()));
}
public void setType(NpcTypeImpl type) {
UNSAFE_hideAll();
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()));
UNSAFE_showAll();
}
@ -85,20 +90,34 @@ public class NpcImpl extends Viewable implements Npc {
public void setLocation(NpcLocation 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()));
}
public void setHeadRotation(Player player, float yaw, float pitch) {
if (getHeadYaw(player) == yaw && getHeadPitch(player) == pitch) return;
playerLookMap.put(player.getUniqueId(), new float[]{yaw, pitch});
entity.setHeadRotation(player, yaw, pitch);
}
public void setHeadRotation(float yaw, float pitch) {
for (Player player : getViewers()) {
if (getHeadYaw(player) == yaw && getHeadPitch(player) == pitch) continue;
playerLookMap.put(player.getUniqueId(), new float[]{yaw, pitch});
entity.setHeadRotation(player, yaw, pitch);
}
}
public float getHeadYaw(Player player) {
return playerLookMap.getOrDefault(player.getUniqueId(), new float[]{location.getYaw(), location.getPitch()})[0];
}
public float getHeadPitch(Player player) {
return playerLookMap.getOrDefault(player.getUniqueId(), new float[]{location.getYaw(), location.getPitch()})[1];
}
public HologramImpl getHologram() {
return hologram;
}
@ -125,13 +144,14 @@ public class NpcImpl extends Viewable implements Npc {
}
@Override
protected void UNSAFE_show(Player player) {
entity.spawn(player);
hologram.show(player);
protected CompletableFuture<Void> UNSAFE_show(Player player) {
playerLookMap.put(player.getUniqueId(), new float[]{location.getYaw(), location.getPitch()});
return CompletableFuture.allOf(entity.spawn(player), hologram.show(player));
}
@Override
protected void UNSAFE_hide(Player player) {
playerLookMap.remove(player.getUniqueId());
entity.despawn(player);
hologram.hide(player);
}
@ -139,7 +159,7 @@ public class NpcImpl extends Viewable implements Npc {
private <T> void UNSAFE_refreshProperty(EntityPropertyImpl<T> property) {
if (!type.isAllowedProperty(property)) return;
for (Player viewer : getViewers()) {
List<EntityData> data = property.applyStandalone(viewer, entity, true);
List<EntityData<?>> data = property.applyStandalone(viewer, entity, true);
if (!data.isEmpty()) packetFactory.sendMetadata(viewer, entity, data);
}
}
@ -212,7 +232,8 @@ public class NpcImpl extends Viewable implements Npc {
}
@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);
}
@ -222,7 +243,8 @@ public class NpcImpl extends Viewable implements Npc {
}
@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);
}
@ -246,4 +268,29 @@ public class NpcImpl extends Viewable implements Npc {
public void swingHand(boolean offHand) {
for (Player viewer : getViewers()) entity.swingHand(viewer, offHand);
}
@Override
public @NotNull List<Integer> getPassengers() {
return entity.getPassengers();
}
@Override
public void addPassenger(int entityId) {
entity.addPassenger(entityId);
}
@Override
public void removePassenger(int entityId) {
entity.removePassenger(entityId);
}
@Override
public @Nullable Integer getVehicleId() {
return entity.getVehicleId();
}
@Override
public void setVehicleId(Integer vehicleId) {
entity.setVehicleId(vehicleId);
}
}

View file

@ -14,6 +14,7 @@ import lol.pyr.znpcsplus.hologram.HologramText;
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.storage.NpcStorage;
import lol.pyr.znpcsplus.storage.NpcStorageType;
import lol.pyr.znpcsplus.util.NpcLocation;
@ -35,13 +36,13 @@ public class NpcRegistryImpl implements NpcRegistry {
private final Map<String, NpcEntryImpl> npcIdLookupMap = new HashMap<>();
private final Map<UUID, NpcEntryImpl> npcUuidLookupMap = new HashMap<>();
public NpcRegistryImpl(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, TaskScheduler scheduler, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, 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.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) {
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.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) {
if (entry == null) throw new NullPointerException();
unregister(npcIdLookupMap.put(entry.getId(), entry));
unregister(npcUuidLookupMap.put(entry.getNpc().getUuid(), entry));
npcList.add(entry);
@ -195,6 +202,14 @@ public class NpcRegistryImpl implements NpcRegistry {
storage.deleteNpc(entry);
}
@Override
public void delete(UUID uuid) {
NpcEntryImpl entry = npcUuidLookupMap.get(uuid);
if (entry == null) return;
unregister(entry);
storage.deleteNpc(entry);
}
public void switchIds(String oldId, String newId) {
NpcEntryImpl entry = getById(oldId);
delete(oldId);

View file

@ -114,12 +114,15 @@ public class NpcTypeImpl implements NpcType {
public NpcTypeImpl build() {
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",
"player_knockback", "player_knockback_exempt_permission", "player_knockback_distance", "player_knockback_vertical",
"player_knockback_horizontal", "player_knockback_cooldown", "player_knockback_sound", "player_knockback_sound_name",
"player_knockback_sound_volume", "player_knockback_sound_pitch");
if (!type.equals(EntityTypes.PLAYER)) addProperties("dinnerbone");
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.LIVINGENTITY)) {
addProperties("health", "attribute_max_health");
}
// TODO: make this look nicer after completing the rest of the properties
if (version.isNewerThanOrEquals(ServerVersion.V_1_9)) addProperties("glow");
if (version.isNewerThanOrEquals(ServerVersion.V_1_14)) {
@ -143,6 +146,9 @@ public class NpcTypeImpl implements NpcType {
} else if (version.isOlderThan(ServerVersion.V_1_11) && type.equals(EntityTypes.HORSE)) {
addProperties("has_chest");
}
if (version.isOlderThan(ServerVersion.V_1_11) && EntityTypes.isTypeInstanceOf(type, EntityTypes.SKELETON)) {
addProperties("skeleton_type");
}
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.ABSTRACT_EVO_ILLU_ILLAGER)) {
addProperties("spell");
}
@ -174,6 +180,35 @@ public class NpcTypeImpl implements NpcType {
if (version.isNewerThanOrEquals(ServerVersion.V_1_20_5)) {
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.WOLF)) {
addProperties("wolf_variant");
if (version.isNewerThanOrEquals(ServerVersion.V_1_21)) {
addProperties("body");
} else {
addProperties("chestplate");
}
}
}
if (version.isNewerThanOrEquals(ServerVersion.V_1_21_4)) {
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.CREAKING)) {
addProperties("creaking_crumbling");
}
}
if (EntityTypes.isTypeInstanceOf(type, EntityTypes.ZOMBIE)) {
if (version.isOlderThan(ServerVersion.V_1_9)) {
addProperties("zombie_is_villager");
} else if (version.isOlderThan(ServerVersion.V_1_11)) {
addProperties("zombie_type");
}
if (version.isOlderThan(ServerVersion.V_1_11)) {
addProperties("is_converting");
}
if (version.isNewerThanOrEquals(ServerVersion.V_1_9) && version.isOlderThan(ServerVersion.V_1_14)) {
addProperties("zombie_hands_held_up");
}
if (version.isNewerThanOrEquals(ServerVersion.V_1_13)) {
addProperties("zombie_becoming_drowned");
}
}
return new NpcTypeImpl(name, type, hologramOffset, new HashSet<>(allowedProperties), defaultProperties);

View file

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

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.player.Equipment;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateAttributes;
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.util.NamedColor;
@ -11,7 +12,7 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
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 destroyEntity(Player player, PacketEntity entity, PropertyHolder properties);
void teleportEntity(Player player, PacketEntity entity);
@ -21,7 +22,10 @@ public interface PacketFactory {
void removeTeam(Player player, PacketEntity entity);
void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties);
void sendEquipment(Player player, PacketEntity entity, Equipment equipment);
void sendMetadata(Player player, PacketEntity entity, List<EntityData> data);
void sendMetadata(Player player, PacketEntity entity, List<EntityData<?>> data);
void sendHeadRotation(Player player, PacketEntity entity, float yaw, float pitch);
void sendHandSwing(Player player, PacketEntity entity, boolean offHand);
void setPassengers(Player player, int vehicle, int... passengers);
void sendAllAttributes(Player player, PacketEntity entity, PropertyHolder properties);
void sendAttribute(Player player, PacketEntity entity, WrapperPlayServerUpdateAttributes.Property property);
}

View file

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

View file

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

View file

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

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 lol.pyr.znpcsplus.api.skin.SkinDescriptor;
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.PrefetchedDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.UUIDFetchingDescriptor;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public interface BaseSkinDescriptor extends SkinDescriptor {
@ -22,7 +24,14 @@ public interface BaseSkinDescriptor extends SkinDescriptor {
static BaseSkinDescriptor deserialize(MojangSkinCache skinCache, String str) {
String[] arr = str.split(";");
if (arr[0].equalsIgnoreCase("mirror")) return new MirrorDescriptor(skinCache);
else if (arr[0].equalsIgnoreCase("fetching")) 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")) {
List<TextureProperty> properties = new ArrayList<>();
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.SkinDescriptorFactory;
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.PrefetchedDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.UUIDFetchingDescriptor;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID;
public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory {
private final MojangSkinCache skinCache;
@ -26,7 +28,12 @@ public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory {
@Override
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
@ -52,4 +59,9 @@ public class SkinDescriptorFactoryImpl implements SkinDescriptorFactory {
public SkinDescriptor createUrlDescriptor(URL url, String variant) {
return PrefetchedDescriptor.fromUrl(skinCache, url, variant).join();
}
@Override
public SkinDescriptor createFileDescriptor(String path) {
return PrefetchedDescriptor.fromFile(skinCache, path).join();
}
}

View file

@ -5,6 +5,7 @@ import com.google.gson.JsonParser;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.reflection.Reflections;
import lol.pyr.znpcsplus.skin.SkinImpl;
import lol.pyr.znpcsplus.util.FutureUtil;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@ -14,6 +15,7 @@ import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
@ -26,9 +28,12 @@ public class MojangSkinCache {
private final Map<String, SkinImpl> cache = new ConcurrentHashMap<>();
private final Map<String, CachedId> idCache = new ConcurrentHashMap<>();
private final File skinsFolder;
public MojangSkinCache(ConfigManager configManager) {
public MojangSkinCache(ConfigManager configManager, File skinsFolder) {
this.configManager = configManager;
this.skinsFolder = skinsFolder;
if (!skinsFolder.exists()) skinsFolder.mkdirs();
}
public void cleanCache() {
@ -42,8 +47,8 @@ public class MojangSkinCache {
if (idCache.containsKey(name.toLowerCase())) return fetchByUUID(idCache.get(name.toLowerCase()).getId());
return CompletableFuture.supplyAsync(() -> {
URL url = parseUrl("https://api.mojang.com/users/profiles/minecraft/" + name);
return FutureUtil.exceptionPrintingSupplyAsync(() -> {
URL url = parseUrl("https://api.minecraftservices.com/minecraft/profile/lookup/name/" + name);
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) url.openConnection();
@ -75,7 +80,7 @@ public class MojangSkinCache {
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);
HttpURLConnection connection = null;
try {
@ -106,7 +111,7 @@ public class MojangSkinCache {
}
public CompletableFuture<SkinImpl> fetchByUrl(URL url, String variant) {
return CompletableFuture.supplyAsync(() -> {
return FutureUtil.exceptionPrintingSupplyAsync(() -> {
URL apiUrl = parseUrl("https://api.mineskin.org/generate/url");
HttpURLConnection connection = null;
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) {
String name = s.toLowerCase();
if (!idCache.containsKey(name)) return false;
@ -170,7 +227,7 @@ public class MojangSkinCache {
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");
HttpURLConnection connection = null;
try {
@ -213,4 +270,8 @@ public class MojangSkinCache {
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;
public class FetchingDescriptor implements BaseSkinDescriptor, SkinDescriptor {
public class NameFetchingDescriptor implements BaseSkinDescriptor, SkinDescriptor {
private final MojangSkinCache skinCache;
private final String name;
public FetchingDescriptor(MojangSkinCache skinCache, String name) {
public NameFetchingDescriptor(MojangSkinCache skinCache, String name) {
this.skinCache = skinCache;
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.SkinImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.util.FutureUtil;
import org.bukkit.entity.Player;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
@ -18,11 +20,21 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
}
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) {
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

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.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.storage.mysql.MySQLStorage;
import lol.pyr.znpcsplus.storage.sqlite.SQLiteStorage;
import lol.pyr.znpcsplus.storage.yaml.YamlStorage;
@ -16,13 +17,13 @@ import java.io.File;
public enum NpcStorageType {
YAML {
@Override
public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer) {
return new YamlStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer, new File(plugin.getDataFolder(), "data"));
public NpcStorage create(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcSerializerRegistryImpl serializerRegistry) {
return new YamlStorage(serializerRegistry, new File(plugin.getDataFolder(), "data"));
}
},
SQLITE {
@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 {
return new SQLiteStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer, new File(plugin.getDataFolder(), "znpcsplus.sqlite"));
} catch (Exception e) {
@ -33,7 +34,7 @@ public enum NpcStorageType {
},
MYSQL {
@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 {
return new MySQLStorage(packetFactory, configManager, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
} 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;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.entity.PropertySerializer;
import lol.pyr.znpcsplus.hologram.HologramImpl;
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
import lol.pyr.znpcsplus.api.serialization.NpcSerializer;
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.serialization.NpcSerializerRegistryImpl;
import lol.pyr.znpcsplus.storage.NpcStorage;
import lol.pyr.znpcsplus.util.NpcLocation;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.util.*;
import java.util.logging.Level;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class YamlStorage implements NpcStorage {
private final static Logger logger = Logger.getLogger("YamlStorage");
private final PacketFactory packetFactory;
private final ConfigManager configManager;
private final ActionRegistryImpl actionRegistry;
private final NpcTypeRegistryImpl typeRegistry;
private final EntityPropertyRegistryImpl propertyRegistry;
private final LegacyComponentSerializer textSerializer;
private final File folder;
private final NpcSerializer<YamlConfiguration> yamlSerializer;
public YamlStorage(PacketFactory packetFactory, ConfigManager configManager, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, File folder) {
this.packetFactory = packetFactory;
this.configManager = configManager;
this.actionRegistry = actionRegistry;
this.typeRegistry = typeRegistry;
this.propertyRegistry = propertyRegistry;
this.textSerializer = textSerializer;
public YamlStorage(NpcSerializerRegistryImpl serializerRegistry, File folder) {
this.yamlSerializer = serializerRegistry.getSerializer(YamlConfiguration.class);
this.folder = folder;
if (!this.folder.exists()) this.folder.mkdirs();
}
@ -53,45 +34,7 @@ public class YamlStorage implements NpcStorage {
List<NpcEntryImpl> npcs = new ArrayList<>(files.length);
for (File file : files) if (file.isFile() && file.getName().toLowerCase().endsWith(".yml")) try {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
UUID uuid = config.contains("uuid") ? UUID.fromString(config.getString("uuid")) : UUID.randomUUID();
NpcImpl npc = new NpcImpl(uuid, propertyRegistry, configManager, packetFactory, textSerializer, config.getString("world"),
typeRegistry.getByName(config.getString("type")), deserializeLocation(config.getConfigurationSection("location")));
if (config.isBoolean("enabled")) npc.setEnabled(config.getBoolean("enabled"));
ConfigurationSection properties = config.getConfigurationSection("properties");
if (properties != null) {
for (String key : properties.getKeys(false)) {
EntityPropertyImpl<?> property = propertyRegistry.getByName(key);
if (property == null) {
Bukkit.getLogger().log(Level.WARNING, "Unknown property '" + key + "' for npc '" + config.getString("id") + "'. skipping ...");
continue;
}
PropertySerializer<?> serializer = propertyRegistry.getSerializer(property.getType());
if (serializer == null) {
Bukkit.getLogger().log(Level.WARNING, "Unknown serializer for property '" + key + "' for npc '" + config.getString("id") + "'. skipping ...");
continue;
}
Object value = serializer.deserialize(properties.getString(key));
if (value == null) {
Bukkit.getLogger().log(Level.WARNING, "Failed to deserialize property '" + key + "' for npc '" + config.getString("id") + "'. Resetting to default ...");
value = property.getDefaultValue();
}
npc.UNSAFE_setProperty(property, value);
}
}
HologramImpl hologram = npc.getHologram();
hologram.setOffset(config.getDouble("hologram.offset", 0.0));
hologram.setRefreshDelay(config.getLong("hologram.refresh-delay", -1));
for (String line : config.getStringList("hologram.lines")) hologram.addLine(line);
for (String s : config.getStringList("actions")) npc.addAction(actionRegistry.deserialize(s));
NpcEntryImpl entry = new NpcEntryImpl(config.getString("id"), npc);
entry.setProcessed(config.getBoolean("is-processed"));
entry.setAllowCommandModification(config.getBoolean("allow-commands"));
entry.setSave(true);
npcs.add(entry);
npcs.add((NpcEntryImpl) yamlSerializer.deserialize(config));
} catch (Throwable t) {
logger.severe("Failed to load npc file: " + file.getName());
t.printStackTrace();
@ -102,43 +45,7 @@ public class YamlStorage implements NpcStorage {
@Override
public void saveNpcs(Collection<NpcEntryImpl> npcs) {
for (NpcEntryImpl entry : npcs) try {
YamlConfiguration config = new YamlConfiguration();
config.set("id", entry.getId());
config.set("is-processed", entry.isProcessed());
config.set("allow-commands", entry.isAllowCommandModification());
NpcImpl npc = entry.getNpc();
config.set("enabled", npc.isEnabled());
config.set("uuid", npc.getUuid().toString());
config.set("world", npc.getWorldName());
config.set("location", serializeLocation(npc.getLocation()));
config.set("type", npc.getType().getName());
for (EntityProperty<?> property : npc.getAllProperties()) try {
PropertySerializer<?> serializer = propertyRegistry.getSerializer(((EntityPropertyImpl<?>) property).getType());
if (serializer == null) {
Bukkit.getLogger().log(Level.WARNING, "Unknown serializer for property '" + property.getName() + "' for npc '" + entry.getId() + "'. skipping ...");
continue;
}
config.set("properties." + property.getName(), serializer.UNSAFE_serialize(npc.getProperty(property)));
} catch (Exception exception) {
logger.severe("Failed to serialize property " + property.getName() + " for npc with id " + entry.getId());
exception.printStackTrace();
}
HologramImpl hologram = npc.getHologram();
if (hologram.getOffset() != 0.0) config.set("hologram.offset", hologram.getOffset());
if (hologram.getRefreshDelay() != -1) config.set("hologram.refresh-delay", hologram.getRefreshDelay());
List<String> lines = new ArrayList<>(npc.getHologram().getLines().size());
for (int i = 0; i < hologram.getLines().size(); i++) {
lines.add(hologram.getLine(i));
}
config.set("hologram.lines", lines);
config.set("actions", npc.getActions().stream()
.map(actionRegistry::serialize)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
YamlConfiguration config = yamlSerializer.serialize(entry);
config.save(fileFor(entry));
} catch (Exception exception) {
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<LookType> lookProperty = propertyRegistry.getByName("look", LookType.class);
EntityPropertyImpl<Double> lookDistanceProperty = propertyRegistry.getByName("look_distance", Double.class);
EntityPropertyImpl<Boolean> lookReturnProperty = propertyRegistry.getByName("look_return", Boolean.class);
EntityPropertyImpl<Boolean> permissionRequiredProperty = propertyRegistry.getByName("permission_required", Boolean.class);
EntityPropertyImpl<Boolean> playerKnockbackProperty = propertyRegistry.getByName("player_knockback", Boolean.class);
EntityPropertyImpl<String> playerKnockbackExemptPermissionProperty = propertyRegistry.getByName("player_knockback_exempt_permission", String.class);
@ -45,6 +46,7 @@ public class NpcProcessorTask extends BukkitRunnable {
EntityPropertyImpl<Float> playerKnockbackSoundVolumeProperty = propertyRegistry.getByName("player_knockback_sound_volume", Float.class);
EntityPropertyImpl<Float> playerKnockbackSoundPitchProperty = propertyRegistry.getByName("player_knockback_sound_pitch", Float.class);
double lookDistance;
boolean lookReturn;
boolean permissionRequired;
boolean playerKnockback;
String playerKnockbackExemptPermission = null;
@ -64,6 +66,7 @@ public class NpcProcessorTask extends BukkitRunnable {
Player closest = null;
LookType lookType = npc.getProperty(lookProperty);
lookDistance = NumberConversions.square(npc.getProperty(lookDistanceProperty));
lookReturn = npc.getProperty(lookReturnProperty);
permissionRequired = npc.getProperty(permissionRequiredProperty);
playerKnockback = npc.getProperty(playerKnockbackProperty);
if (playerKnockback) {
@ -106,9 +109,13 @@ public class NpcProcessorTask extends BukkitRunnable {
closestDist = distance;
closest = player;
}
if (lookType.equals(LookType.PER_PLAYER) && lookDistance >= distance) {
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());
if (lookType.equals(LookType.PER_PLAYER)) {
if (lookDistance >= distance) {
NpcLocation expected = npc.getLocation().lookingAt(player.getLocation().add(0, -npc.getType().getHologramOffset(), 0));
npc.setHeadRotation(player, expected.getYaw(), expected.getPitch());
} else if (lookReturn) {
npc.setHeadRotation(player, npc.getLocation().getYaw(), npc.getLocation().getPitch());
}
}
// player knockback
@ -132,7 +139,11 @@ public class NpcProcessorTask extends BukkitRunnable {
if (closest != null && lookDistance >= closestDist) {
NpcLocation expected = npc.getLocation().lookingAt(closest.getLocation().add(0, -npc.getType().getHologramOffset(), 0));
if (!expected.equals(npc.getLocation())) npc.setHeadRotation(expected.getYaw(), expected.getPitch());
} else if (lookReturn) {
npc.setHeadRotation(npc.getLocation().getYaw(), npc.getLocation().getPitch());
}
} else if (lookType.equals(LookType.FIXED)) {
npc.setHeadRotation(npc.getLocation().getYaw(), npc.getLocation().getPitch());
}
}
}

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