diff --git a/api/src/main/java/lol/pyr/znpcsplus/api/entity/PropertyHolder.java b/api/src/main/java/lol/pyr/znpcsplus/api/entity/PropertyHolder.java index 65c34a1..1180375 100644 --- a/api/src/main/java/lol/pyr/znpcsplus/api/entity/PropertyHolder.java +++ b/api/src/main/java/lol/pyr/znpcsplus/api/entity/PropertyHolder.java @@ -43,6 +43,14 @@ public interface PropertyHolder { */ void setItemProperty(EntityProperty key, ItemStack value); + /** + * Weird fix which is sadly required in order to not decrease performance + * when using item properties, read https://github.com/Pyrbu/ZNPCsPlus/pull/129#issuecomment-1948777764 + * + * @param key Unique key representing a property + */ + ItemStack getItemProperty(EntityProperty key); + /** * Method used to get a set of all of the property keys that this holder has a value for * diff --git a/api/src/main/java/lol/pyr/znpcsplus/api/hologram/Hologram.java b/api/src/main/java/lol/pyr/znpcsplus/api/hologram/Hologram.java index 6050c73..b19e905 100644 --- a/api/src/main/java/lol/pyr/znpcsplus/api/hologram/Hologram.java +++ b/api/src/main/java/lol/pyr/znpcsplus/api/hologram/Hologram.java @@ -41,4 +41,16 @@ public interface Hologram { * @return The number of lines in the hologram */ int lineCount(); + + /** + * Gets the refresh delay of the hologram + * @return The refresh delay of the hologram + */ + long getRefreshDelay(); + + /** + * Sets the refresh delay of the hologram + * @param delay The delay to set + */ + void setRefreshDelay(long delay); } diff --git a/api/src/main/java/lol/pyr/znpcsplus/util/ArmadilloState.java b/api/src/main/java/lol/pyr/znpcsplus/util/ArmadilloState.java new file mode 100644 index 0000000..bdea88f --- /dev/null +++ b/api/src/main/java/lol/pyr/znpcsplus/util/ArmadilloState.java @@ -0,0 +1,8 @@ +package lol.pyr.znpcsplus.util; + +public enum ArmadilloState { + IDLE, + ROLLING, + SCARED, + UNROLLING +} diff --git a/api/src/main/java/lol/pyr/znpcsplus/util/WoldVariant.java b/api/src/main/java/lol/pyr/znpcsplus/util/WoldVariant.java new file mode 100644 index 0000000..fed5d66 --- /dev/null +++ b/api/src/main/java/lol/pyr/znpcsplus/util/WoldVariant.java @@ -0,0 +1,23 @@ +package lol.pyr.znpcsplus.util; + +public enum WoldVariant { + PALE(3), + SPOTTED(6), + SNOWY(5), + BLACK(1), + ASHEN(0), + RUSTY(4), + WOODS(8), + CHESTNUT(2), + STRIPED(7); + + private final int id; + + WoldVariant(int id) { + this.id = id; + } + + public int getId() { + return id; + } +} diff --git a/plugin/build.gradle b/plugin/build.gradle index 1a59906..023a8f0 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -6,9 +6,9 @@ plugins { runServer { javaLauncher = javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(21) } - minecraftVersion "1.20.4" + minecraftVersion "1.20.6" } processResources { @@ -32,18 +32,17 @@ publishing { dependencies { compileOnly "me.clip:placeholderapi:2.11.5" // Placeholder support - compileOnly "com.google.code.gson:gson:2.10.1" // JSON parsing - compileOnly "org.bstats:bstats-bukkit:3.0.2" // Plugin stats - compileOnly "me.robertlit:SpigotResourcesAPI:2.0" // Spigot API wrapper for update checker - compileOnly "com.github.retrooper.packetevents:spigot:2.3.0" // Packets - compileOnly "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs - compileOnly "lol.pyr:director-adventure:2.1.1" // Commands + implementation "com.google.code.gson:gson:2.10.1" // JSON parsing + implementation "org.bstats:bstats-bukkit:3.0.2" // Plugin stats + implementation "me.robertlit:SpigotResourcesAPI:2.0" // Spigot API wrapper for update checker + implementation "com.github.retrooper.packetevents:spigot:2.3.0" // Packets + implementation "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs + implementation "lol.pyr:director-adventure:2.1.1" // Commands // Fancy text library - compileOnly "net.kyori:adventure-platform-bukkit:4.3.2" - compileOnly "net.kyori:adventure-text-minimessage:4.15.0" + implementation "net.kyori:adventure-platform-bukkit:4.3.2" + implementation "net.kyori:adventure-text-minimessage:4.15.0" - implementation "me.lucko:jar-relocator:1.7" implementation project(":api") } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java index 5829d99..4729f6f 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java @@ -115,7 +115,7 @@ public class ZNpcsPlus { MojangSkinCache skinCache = new MojangSkinCache(configManager); EntityPropertyRegistryImpl propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager); PacketFactory packetFactory = setupPacketFactory(scheduler, propertyRegistry, configManager); - propertyRegistry.registerTypes(packetFactory, textSerializer); + propertyRegistry.registerTypes(bootstrap, packetFactory, textSerializer); ActionRegistry actionRegistry = new ActionRegistry(); NpcTypeRegistryImpl typeRegistry = new NpcTypeRegistryImpl(); @@ -131,7 +131,7 @@ public class ZNpcsPlus { scheduler, packetFactory, textSerializer, typeRegistry, getDataFolder().getParentFile(), propertyRegistry, skinCache, npcRegistry, bungeeConnector); - log(ChatColor.WHITE + " * Registerring components..."); + log(ChatColor.WHITE + " * Registering components..."); bungeeConnector.registerChannel(); shutdownTasks.add(bungeeConnector::unregisterChannel); @@ -276,10 +276,13 @@ public class ZNpcsPlus { registerEnumParser(manager, RabbitType.class, incorrectUsageMessage); registerEnumParser(manager, AttachDirection.class, incorrectUsageMessage); registerEnumParser(manager, Sound.class, incorrectUsageMessage); + registerEnumParser(manager, ArmadilloState.class, incorrectUsageMessage); + registerEnumParser(manager, WoldVariant.class, incorrectUsageMessage); manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root")) .addSubcommand("center", new CenterCommand(npcRegistry)) .addSubcommand("create", new CreateCommand(npcRegistry, typeRegistry)) + .addSubcommand("clone", new CloneCommand(npcRegistry)) .addSubcommand("reloadconfig", new ReloadConfigCommand(configManager)) .addSubcommand("toggle", new ToggleCommand(npcRegistry)) .addSubcommand("skin", new SkinCommand(skinCache, npcRegistry, typeRegistry, propertyRegistry)) diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlusBootstrap.java b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlusBootstrap.java index c18c741..9eb1ab9 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlusBootstrap.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlusBootstrap.java @@ -2,7 +2,6 @@ package lol.pyr.znpcsplus; import lol.pyr.director.adventure.command.CommandContext; import lol.pyr.director.common.message.Message; -import lol.pyr.znpcsplus.libraries.LibraryLoader; import lol.pyr.znpcsplus.util.FileUtil; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -32,55 +31,6 @@ public class ZNpcsPlusBootstrap extends JavaPlugin { Bukkit.getPluginManager().disablePlugin(this); return; } - getLogger().info("Downloading and loading libraries, this might take a while if this is the first time you're launching the plugin"); - LibraryLoader loader = new LibraryLoader(this, new File(getDataFolder(), "libraries")); - - loader.addRelocation(decrypt("org..bstats"), "lol.pyr.znpcsplus.libraries.bstats"); - loader.addRelocation(decrypt("me..robertlit..spigotresources"), "lol.pyr.znpcsplus.libraries.spigotresources"); - loader.addRelocation(decrypt("net..kyori"), "lol.pyr.znpcsplus.libraries.kyori"); - loader.addRelocation(decrypt("org..checkerframework"), "lol.pyr.znpcsplus.libraries.checkerframework"); - loader.addRelocation(decrypt("com..google"), "lol.pyr.znpcsplus.libraries.google"); - loader.addRelocation(decrypt("com..github..retrooper..packetevents"), "lol.pyr.znpcsplus.libraries.packetevents.api"); - loader.addRelocation(decrypt("io..github..retrooper..packetevents"), "lol.pyr.znpcsplus.libraries.packetevents.impl"); - loader.addRelocation(decrypt("org..yaml..snakeyaml"), "lol.pyr.znpcsplus.libraries.snakeyaml"); - loader.addRelocation(decrypt("space..arim..dazzleconf"), "lol.pyr.znpcsplus.libraries.dazzleconf"); - loader.addRelocation(decrypt("lol..pyr..director"), "lol.pyr.znpcsplus.libraries.command"); - - loader.loadLibrary(decrypt("com..google..guava"), "guava", "18.0"); - loader.loadLibrary(decrypt("com..google..code..gson"), "gson", "2.10.1"); - - loader.loadLibrary(decrypt("org..bstats"), "bstats-base", "3.0.2"); - loader.loadLibrary(decrypt("org..bstats"), "bstats-bukkit", "3.0.2"); - - loader.loadLibrary("me.robertlit", "SpigotResourcesAPI", "2.0", "https://repo.pyr.lol/releases"); - - loader.loadLibrary(decrypt("com..github..retrooper..packetevents"), "api", "2.3.0", "https://repo.codemc.io/repository/maven-releases/"); - loader.loadLibrary(decrypt("com..github..retrooper..packetevents"), "spigot", "2.3.0", "https://repo.codemc.io/repository/maven-releases/"); - - loader.loadLibrary(decrypt("space..arim..dazzleconf"), "dazzleconf-core", "1.2.1"); - loader.loadLibrary(decrypt("space..arim..dazzleconf"), "dazzleconf-ext-snakeyaml", "1.2.1"); - loader.loadLibrary("org.yaml", "snakeyaml", "1.33"); - - loader.loadLibrary("lol.pyr", "director-adventure", "2.1.1", "https://repo.pyr.lol/releases"); - - loader.loadLibrary(decrypt("net..kyori"), "adventure-api", "4.15.0"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-key", "4.15.0"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-nbt", "4.15.0"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-facet", "4.3.2"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-api", "4.3.2"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-platform-bukkit", "4.3.2"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-text-minimessage", "4.15.0"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-bungeecord", "4.3.2"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson", "4.15.0"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-gson-legacy-impl", "4.15.0"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-json", "4.15.0"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-json-legacy-impl", "4.15.0"); - loader.loadLibrary(decrypt("net..kyori"), "adventure-text-serializer-legacy", "4.15.0"); - loader.loadLibrary(decrypt("net..kyori"), "examination-api", "1.3.0"); - loader.loadLibrary(decrypt("net..kyori"), "examination-string", "1.3.0"); - loader.deleteUnloadedLibraries(); - - getLogger().info("Loaded " + loader.loadedLibraryCount() + " libraries!"); zNpcsPlus = new ZNpcsPlus(this); } @@ -120,9 +70,4 @@ public class ZNpcsPlusBootstrap extends JavaPlugin { public boolean movedLegacy() { return legacy; } - - // Ugly hack because of https://github.com/johnrengelman/shadow/issues/232 - private static String decrypt(String packageName) { - return packageName.replace("..", "."); - } } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/CloneCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/CloneCommand.java new file mode 100644 index 0000000..eb9d001 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/CloneCommand.java @@ -0,0 +1,44 @@ +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.npc.NpcEntryImpl; +import lol.pyr.znpcsplus.npc.NpcRegistryImpl; +import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl; +import lol.pyr.znpcsplus.util.NpcLocation; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; + +public class CloneCommand implements CommandHandler { + private final NpcRegistryImpl npcRegistry; + + public CloneCommand(NpcRegistryImpl npcRegistry) { + this.npcRegistry = npcRegistry; + } + + @Override + public void run(CommandContext context) throws CommandExecutionException { + context.setUsage(context.getLabel() + " clone "); + Player player = context.ensureSenderIsPlayer(); + + String id = context.popString(); + if (npcRegistry.getById(id) == null) context.halt(Component.text("NPC with ID " + id + " does not exist.", NamedTextColor.RED)); + String newId = context.popString(); + if (npcRegistry.getById(newId) != null) context.halt(Component.text("NPC with ID " + newId + " already exists.", NamedTextColor.RED)); + + npcRegistry.clone(id, newId, player.getWorld(), new NpcLocation(player.getLocation())); + + context.send(Component.text("Cloned NPC with ID " + id + " to ID " + newId + ".", NamedTextColor.GREEN)); + } + + @Override + public List suggest(CommandContext context) throws CommandExecutionException { + if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds()); + return Collections.emptyList(); + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/MoveCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/MoveCommand.java index dc87abb..452cf44 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/commands/MoveCommand.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/MoveCommand.java @@ -13,6 +13,7 @@ import org.bukkit.entity.Player; import java.util.Collections; import java.util.List; +import java.util.Objects; public class MoveCommand implements CommandHandler { private final NpcRegistryImpl npcRegistry; @@ -27,7 +28,7 @@ public class MoveCommand implements CommandHandler { Player player = context.ensureSenderIsPlayer(); NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc(); npc.setLocation(new NpcLocation(player.getLocation())); - if (!npc.getWorld().equals(player.getWorld())) npc.setWorld(player.getWorld()); + if (!Objects.equals(npc.getWorld(), player.getWorld())) npc.setWorld(player.getWorld()); context.send(Component.text("NPC moved to your current location.", NamedTextColor.GREEN)); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/NearCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/NearCommand.java index f8fad20..6f965f7 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/commands/NearCommand.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/NearCommand.java @@ -10,9 +10,11 @@ import lol.pyr.znpcsplus.util.NpcLocation; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Location; import org.bukkit.entity.Player; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; public class NearCommand implements CommandHandler { @@ -30,8 +32,11 @@ public class NearCommand implements CommandHandler { double radius = Math.pow(raw, 2); List entries = npcRegistry.getAllModifiable().stream() - .filter(entry -> entry.getNpc().getWorld().equals(player.getWorld())) - .filter(entry -> entry.getNpc().getBukkitLocation().distanceSquared(player.getLocation()) < radius) + .filter(entry -> Objects.equals(entry.getNpc().getWorld(), player.getWorld())) + .filter(entry -> { + Location loc = entry.getNpc().getBukkitLocation(); + return loc != null && loc.distanceSquared(player.getLocation()) < radius; + }) .collect(Collectors.toList()); if (entries.isEmpty()) context.halt(Component.text("There are no npcs within " + raw + " blocks around you.", NamedTextColor.RED)); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/TeleportCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/TeleportCommand.java index 012ba85..b9705cd 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/commands/TeleportCommand.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/TeleportCommand.java @@ -9,6 +9,7 @@ import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import lol.pyr.znpcsplus.util.FoliaUtil; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Location; import org.bukkit.entity.Player; import java.util.Collections; @@ -26,7 +27,9 @@ public class TeleportCommand implements CommandHandler { context.setUsage(context.getLabel() + " teleport "); Player player = context.ensureSenderIsPlayer(); NpcImpl npc = context.parse(NpcEntryImpl.class).getNpc(); - FoliaUtil.teleport(player, npc.getBukkitLocation()); + Location location = npc.getBukkitLocation(); + if (location == null) context.halt("Unable to teleport to NPC, the world is not loaded!"); + FoliaUtil.teleport(player, location); context.send(Component.text("Teleported to NPC!", NamedTextColor.GREEN)); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/CitizensImporter.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/CitizensImporter.java index 8a05461..aecfb93 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/CitizensImporter.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/CitizensImporter.java @@ -4,6 +4,7 @@ import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.conversion.DataImporter; import lol.pyr.znpcsplus.conversion.citizens.model.CitizensTrait; import lol.pyr.znpcsplus.conversion.citizens.model.CitizensTraitsRegistry; +import lol.pyr.znpcsplus.conversion.citizens.model.traits.TypeTrait; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.npc.NpcEntryImpl; import lol.pyr.znpcsplus.npc.NpcImpl; @@ -52,7 +53,7 @@ public class CitizensImporter implements DataImporter { this.propertyRegistry = propertyRegistry; this.skinCache = skinCache; this.dataFile = dataFile; - this.traitsRegistry = new CitizensTraitsRegistry(typeRegistry, propertyRegistry, skinCache); + this.traitsRegistry = new CitizensTraitsRegistry(propertyRegistry, skinCache, taskScheduler, textSerializer); this.npcRegistry = npcRegistry; } @@ -81,11 +82,12 @@ public class CitizensImporter implements DataImporter { world = Bukkit.getWorlds().get(0).getName(); } NpcImpl npc = new NpcImpl(uuid, propertyRegistry, configManager, packetFactory, textSerializer, world, typeRegistry.getByName("armor_stand"), new NpcLocation(0, 0, 0, 0, 0)); - npc.getType().applyDefaultProperties(npc); - npc.getHologram().addTextLineComponent(textSerializer.deserialize(name)); ConfigurationSection traits = npcSection.getConfigurationSection("traits"); if (traits != null) { + TypeTrait typeTrait = new TypeTrait(typeRegistry); + npc = typeTrait.apply(npc, traits.getString("type")); + npc.getType().applyDefaultProperties(npc); for (String traitName : traits.getKeys(false)) { Object trait = traits.get(traitName); CitizensTrait citizensTrait = traitsRegistry.getByName(traitName); @@ -94,6 +96,10 @@ public class CitizensImporter implements DataImporter { } } } + boolean nameVisible = Boolean.parseBoolean(npcSection.getString("metadata.name-visible", "true")); + if (nameVisible) { + npc.getHologram().addTextLineComponent(textSerializer.deserialize(name)); + } String id = key.toLowerCase(); while (npcRegistry.getById(id) != null) { id += "_"; // TODO: make a backup of the old npc instead diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/CitizensTraitsRegistry.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/CitizensTraitsRegistry.java index ba3ba89..6b004e1 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/CitizensTraitsRegistry.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/CitizensTraitsRegistry.java @@ -1,24 +1,28 @@ package lol.pyr.znpcsplus.conversion.citizens.model; import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry; -import lol.pyr.znpcsplus.api.npc.NpcTypeRegistry; import lol.pyr.znpcsplus.conversion.citizens.model.traits.*; +import lol.pyr.znpcsplus.scheduling.TaskScheduler; import lol.pyr.znpcsplus.skin.cache.MojangSkinCache; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import java.util.HashMap; public class CitizensTraitsRegistry { private final HashMap traitMap = new HashMap<>(); - public CitizensTraitsRegistry(NpcTypeRegistry typeRegistry, EntityPropertyRegistry propertyRegistry, MojangSkinCache skinCache) { + public CitizensTraitsRegistry(EntityPropertyRegistry propertyRegistry, MojangSkinCache skinCache, TaskScheduler taskScheduler, LegacyComponentSerializer textSerializer) { register(new LocationTrait()); - register(new TypeTrait(typeRegistry)); register(new ProfessionTrait(propertyRegistry)); register(new VillagerTrait(propertyRegistry)); register(new SkinTrait(propertyRegistry)); register(new MirrorTrait(propertyRegistry, skinCache)); register(new SkinLayersTrait(propertyRegistry)); register(new LookTrait(propertyRegistry)); + register(new CommandTrait(taskScheduler)); + register(new HologramTrait(textSerializer)); + register(new EquipmentTrait(propertyRegistry)); + register(new SpawnedTrait()); } public CitizensTrait getByName(String name) { diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/CommandTrait.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/CommandTrait.java new file mode 100644 index 0000000..08c2ee0 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/CommandTrait.java @@ -0,0 +1,69 @@ +package lol.pyr.znpcsplus.conversion.citizens.model.traits; + +import lol.pyr.znpcsplus.api.interaction.InteractionType; +import lol.pyr.znpcsplus.conversion.citizens.model.SectionCitizensTrait; +import lol.pyr.znpcsplus.interaction.InteractionActionImpl; +import lol.pyr.znpcsplus.interaction.consolecommand.ConsoleCommandAction; +import lol.pyr.znpcsplus.interaction.playercommand.PlayerCommandAction; +import lol.pyr.znpcsplus.npc.NpcImpl; +import lol.pyr.znpcsplus.scheduling.TaskScheduler; +import org.bukkit.configuration.ConfigurationSection; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +public class CommandTrait extends SectionCitizensTrait { + private final TaskScheduler scheduler; + + public CommandTrait(TaskScheduler scheduler) { + super("commandtrait"); + this.scheduler = scheduler; + } + + @Override + public @NotNull NpcImpl apply(NpcImpl npc, ConfigurationSection section) { + ConfigurationSection commands = section.getConfigurationSection("commands"); + if (commands != null) { + Set keys = commands.getKeys(false); + if (keys != null) { + for (String key : keys) { + ConfigurationSection commandSection = commands.getConfigurationSection(key); + String command = commandSection.getString("command"); + String hand = commandSection.getString("hand", "BOTH"); + InteractionType clickType = wrapClickType(hand); + boolean isPlayerCommand = commandSection.getBoolean("player", true); + int cooldown = commandSection.getInt("cooldown", 0); + int delay = commandSection.getInt("delay", 0); + if (command != null) { + InteractionActionImpl action; + if (isPlayerCommand) { + action = new PlayerCommandAction(scheduler, command, clickType, cooldown, delay); + } else { + action = new ConsoleCommandAction(scheduler, command, clickType, cooldown, delay); + } + npc.addAction(action); + } + } + } + } + return npc; + } + + private InteractionType wrapClickType(String hand) { + if (hand == null) { + return InteractionType.ANY_CLICK; + } + switch (hand) { + case "RIGHT": + case "SHIFT_RIGHT": + return InteractionType.RIGHT_CLICK; + case "LEFT": + case "SHIFT_LEFT": + return InteractionType.LEFT_CLICK; + case "BOTH": + return InteractionType.ANY_CLICK; + } + throw new IllegalStateException(); + } + +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/EquipmentTrait.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/EquipmentTrait.java new file mode 100644 index 0000000..1f3393a --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/EquipmentTrait.java @@ -0,0 +1,115 @@ +package lol.pyr.znpcsplus.conversion.citizens.model.traits; + +import com.github.retrooper.packetevents.protocol.item.ItemStack; +import com.github.retrooper.packetevents.protocol.player.EquipmentSlot; +import com.google.common.io.BaseEncoding; +import io.github.retrooper.packetevents.util.SpigotConversionUtil; +import lol.pyr.znpcsplus.api.entity.EntityProperty; +import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry; +import lol.pyr.znpcsplus.conversion.citizens.model.SectionCitizensTrait; +import lol.pyr.znpcsplus.npc.NpcImpl; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.util.io.BukkitObjectInputStream; +import org.jetbrains.annotations.NotNull; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class EquipmentTrait extends SectionCitizensTrait { + private final EntityPropertyRegistry propertyRegistry; + private final HashMap EQUIPMENT_SLOT_MAP = new HashMap<>(); + + public EquipmentTrait(EntityPropertyRegistry propertyRegistry) { + super("equipment"); + this.propertyRegistry = propertyRegistry; + EQUIPMENT_SLOT_MAP.put("hand", EquipmentSlot.MAIN_HAND); + EQUIPMENT_SLOT_MAP.put("offhand", EquipmentSlot.OFF_HAND); + EQUIPMENT_SLOT_MAP.put("helmet", EquipmentSlot.HELMET); + EQUIPMENT_SLOT_MAP.put("chestplate", EquipmentSlot.CHEST_PLATE); + EQUIPMENT_SLOT_MAP.put("leggings", EquipmentSlot.LEGGINGS); + EQUIPMENT_SLOT_MAP.put("boots", EquipmentSlot.BOOTS); + } + + @Override + public @NotNull NpcImpl apply(NpcImpl npc, ConfigurationSection section) { + for (String key : section.getKeys(false)) { + EquipmentSlot slot = EQUIPMENT_SLOT_MAP.get(key); + if (slot == null) { + continue; + } + + ItemStack itemStack = parseItemStack(section.getConfigurationSection(key)); + if (itemStack == null) { + continue; + } + + EntityProperty property = propertyRegistry.getByName(key, ItemStack.class); + npc.setProperty(property, itemStack); + } + + return npc; + } + + private ItemStack parseItemStack(ConfigurationSection section) { + Material material = null; + if (section.isString("type_key")) { + material = Material.getMaterial(section.getString("type_key").toUpperCase()); + } else if (section.isString("type")) { + material = Material.matchMaterial(section.getString("type").toUpperCase()); + } else if (section.isString("id")) { + material = Material.matchMaterial(section.getString("id").toUpperCase()); + } + if (material == null || material == Material.AIR) { + return null; + } + org.bukkit.inventory.ItemStack itemStack = new org.bukkit.inventory.ItemStack(material, section.getInt("amount", 1), + (short) section.getInt("durability", section.getInt("data", 0))); + if (section.isInt("mdata")) { + //noinspection deprecation + itemStack.getData().setData((byte) section.getInt("mdata")); + } + if (section.isConfigurationSection("enchantments")) { + ConfigurationSection enchantments = section.getConfigurationSection("enchantments"); + itemStack.addUnsafeEnchantments(deserializeEnchantments(enchantments)); + } + if (section.isConfigurationSection("meta")) { + ItemMeta itemMeta = deserializeMeta(section.getConfigurationSection("meta")); + if (itemMeta != null) { + itemStack.setItemMeta(itemMeta); + } + } + return SpigotConversionUtil.fromBukkitItemStack(itemStack); + } + + private Map deserializeEnchantments(ConfigurationSection section) { + Map enchantments = new HashMap<>(); + for (String key : section.getKeys(false)) { + Enchantment enchantment = Enchantment.getByName(key); + if (enchantment == null) { + continue; + } + enchantments.put(enchantment, section.getInt(key)); + } + return enchantments; + } + + private ItemMeta deserializeMeta(ConfigurationSection section) { + if (section.isString("encoded-meta")) { + byte[] raw = BaseEncoding.base64().decode(section.getString("encoded-meta")); + try { + BukkitObjectInputStream inp = new BukkitObjectInputStream(new ByteArrayInputStream(raw)); + ItemMeta meta = (ItemMeta) inp.readObject(); + inp.close(); + return meta; + } catch (IOException | ClassNotFoundException e1) { + e1.printStackTrace(); + } + } + return null; + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/HologramTrait.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/HologramTrait.java new file mode 100644 index 0000000..670e48a --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/HologramTrait.java @@ -0,0 +1,36 @@ +package lol.pyr.znpcsplus.conversion.citizens.model.traits; + +import lol.pyr.znpcsplus.conversion.citizens.model.SectionCitizensTrait; +import lol.pyr.znpcsplus.npc.NpcImpl; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.configuration.ConfigurationSection; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class HologramTrait extends SectionCitizensTrait { + private final LegacyComponentSerializer textSerializer; + + public HologramTrait(LegacyComponentSerializer textSerializer) { + super("hologramtrait"); + this.textSerializer = textSerializer; + } + + @Override + public @NotNull NpcImpl apply(NpcImpl npc, ConfigurationSection section) { + ConfigurationSection linesSection = section.getConfigurationSection("lines"); + if (linesSection != null) { + List keys = new ArrayList<>(linesSection.getKeys(false)); + for (int i = keys.size() - 1; i >= 0; i--) { + String line = linesSection.getConfigurationSection(keys.get(i)).getString("text"); + if (line != null) { + Component component = textSerializer.deserialize(line); + npc.getHologram().addTextLineComponent(component); + } + } + } + return npc; + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/SpawnedTrait.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/SpawnedTrait.java new file mode 100644 index 0000000..9f080c5 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/model/traits/SpawnedTrait.java @@ -0,0 +1,20 @@ +package lol.pyr.znpcsplus.conversion.citizens.model.traits; + +import lol.pyr.znpcsplus.conversion.citizens.model.CitizensTrait; +import lol.pyr.znpcsplus.npc.NpcImpl; +import org.jetbrains.annotations.NotNull; + +public class SpawnedTrait extends CitizensTrait { + + public SpawnedTrait() { + super("spawned"); + } + + @Override + public @NotNull NpcImpl apply(NpcImpl npc, Object value) { + if (value != null) { + npc.setEnabled((boolean) value); + } + return npc; + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/ZNpcImporter.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/ZNpcImporter.java index 3df75d0..fa763c4 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/ZNpcImporter.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/ZNpcImporter.java @@ -157,6 +157,7 @@ public class ZNpcImporter implements DataImporter { HologramImpl hologram = npc.getHologram(); hologram.setOffset(model.getHologramHeight()); + Collections.reverse(model.getHologramLines()); for (String raw : model.getHologramLines()) { Component line = textSerializer.deserialize(raw); hologram.addTextLineComponent(line); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java index 64b43a5..ba70aa3 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java @@ -9,6 +9,7 @@ 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; @@ -86,6 +87,8 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { registerEnumSerializer(RabbitType.class); registerEnumSerializer(AttachDirection.class); registerEnumSerializer(Sound.class); + registerEnumSerializer(ArmadilloState.class); + registerEnumSerializer(WoldVariant.class); registerPrimitiveSerializers(Integer.class, Boolean.class, Double.class, Float.class, Long.class, Short.class, Byte.class, String.class); @@ -101,7 +104,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { */ } - public void registerTypes(PacketFactory packetFactory, LegacyComponentSerializer textSerializer) { + public void registerTypes(ZNpcsPlusBootstrap plugin, PacketFactory packetFactory, LegacyComponentSerializer textSerializer) { ServerVersion ver = PacketEvents.getAPI().getServerManager().getVersion(); boolean legacyBooleans = ver.isOlderThan(ServerVersion.V_1_9); boolean legacyNames = ver.isOlderThan(ServerVersion.V_1_9); @@ -124,6 +127,8 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { register(new DummyProperty<>("permission_required", false)); + register(new ForceBodyRotationProperty(plugin)); + register(new DummyProperty<>("player_knockback", false)); register(new DummyProperty<>("player_knockback_exempt_permission", String.class)); register(new DummyProperty<>("player_knockback_distance", 0.4)); @@ -411,7 +416,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { register(new EncodedByteProperty<>("wolf_collar", DyeColor.BLUE, wolfIndex++, DyeColor::getDyeData)); } else register(new EncodedIntegerProperty<>("wolf_collar", DyeColor.RED, wolfIndex++, Enum::ordinal)); if (ver.isNewerThanOrEquals(ServerVersion.V_1_16)) { - register(new EncodedIntegerProperty<>("wolf_angry", false, wolfIndex, b -> b ? 1 : 0)); + register(new EncodedIntegerProperty<>("wolf_angry", false, wolfIndex++, b -> b ? 1 : 0)); linkProperties("tamed", "sitting"); } else { @@ -634,6 +639,9 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { // Frog register(new EncodedIntegerProperty<>("frog_variant", FrogVariant.TEMPERATE, 17, Enum::ordinal, EntityDataTypes.FROG_VARIANT)); + // Warden + register(new EncodedIntegerProperty<>("warden_anger", 0, 16, b -> Math.min(150, Math.max(0, b)))); + if (!ver.isNewerThanOrEquals(ServerVersion.V_1_20)) return; // Camel @@ -643,6 +651,14 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { // Sniffer register(new CustomTypeProperty<>("sniffer_state", 17, SnifferState.IDLING, EntityDataTypes.SNIFFER_STATE, state -> com.github.retrooper.packetevents.protocol.entity.sniffer.SnifferState.valueOf(state.name()))); + + if (!ver.isNewerThanOrEquals(ServerVersion.V_1_20_5)) return; + // Armadillo + register(new CustomTypeProperty<>("armadillo_state", 17, ArmadilloState.IDLE, EntityDataTypes.ARMADILLO_STATE, state -> + com.github.retrooper.packetevents.protocol.entity.armadillo.ArmadilloState.valueOf(state.name()))); + + // Wolf + register(new EncodedIntegerProperty<>("wolf_variant", WoldVariant.PALE, wolfIndex, WoldVariant::getId, EntityDataTypes.WOLF_VARIANT)); } private void registerSerializer(PropertySerializer serializer) { diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java index 97ef4de..4418152 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java @@ -107,6 +107,11 @@ public class PacketEntity implements PropertyHolder { properties.setItemProperty(key, value); } + @Override + public ItemStack getItemProperty(EntityProperty key) { + return properties.getItemProperty(key); + } + @Override public Set> getAppliedProperties() { return properties.getAppliedProperties(); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/ForceBodyRotationProperty.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/ForceBodyRotationProperty.java new file mode 100644 index 0000000..4d568a7 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/ForceBodyRotationProperty.java @@ -0,0 +1,24 @@ +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 org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.Map; + +public class ForceBodyRotationProperty extends DummyProperty { + private final ZNpcsPlusBootstrap plugin; + + public ForceBodyRotationProperty(ZNpcsPlusBootstrap plugin) { + super("force_body_rotation", false); + this.plugin = plugin; + } + + @Override + public void apply(Player player, PacketEntity entity, boolean isSpawned, Map properties) { + Bukkit.getScheduler().runTaskLater(plugin, () -> entity.swingHand(player, false), 2L); + Bukkit.getScheduler().runTaskLater(plugin, () -> entity.swingHand(player, false), 6L); + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramImpl.java index d26efa1..4092e92 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramImpl.java @@ -147,10 +147,12 @@ public class HologramImpl extends Viewable implements Hologram { for (HologramLine line : lines) line.hide(player); } + @Override public long getRefreshDelay() { return refreshDelay; } + @Override public void setRefreshDelay(long refreshDelay) { this.refreshDelay = refreshDelay; } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramLine.java b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramLine.java index f2652f0..3b572e5 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramLine.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramLine.java @@ -1,6 +1,7 @@ package lol.pyr.znpcsplus.hologram; import com.github.retrooper.packetevents.protocol.entity.type.EntityType; +import io.github.retrooper.packetevents.util.SpigotConversionUtil; import lol.pyr.znpcsplus.api.entity.EntityProperty; import lol.pyr.znpcsplus.api.entity.PropertyHolder; import lol.pyr.znpcsplus.entity.PacketEntity; @@ -76,6 +77,12 @@ public class HologramLine implements PropertyHolder { throw new UnsupportedOperationException("Can't set properties on a hologram line"); } + @SuppressWarnings("unchecked") + @Override + public ItemStack getItemProperty(EntityProperty key) { + return SpigotConversionUtil.toBukkitItemStack(((EntityProperty) key).getDefaultValue()); + } + @Override public Set> getAppliedProperties() { return properties; diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/libraries/LibraryLoader.java b/plugin/src/main/java/lol/pyr/znpcsplus/libraries/LibraryLoader.java deleted file mode 100644 index 7d94674..0000000 --- a/plugin/src/main/java/lol/pyr/znpcsplus/libraries/LibraryLoader.java +++ /dev/null @@ -1,119 +0,0 @@ -package lol.pyr.znpcsplus.libraries; - -import me.lucko.jarrelocator.JarRelocator; -import me.lucko.jarrelocator.Relocation; -import org.bukkit.plugin.Plugin; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.logging.Logger; - -public class LibraryLoader { - private final static Logger logger = Logger.getLogger("ZNPCsPlus Library Loader"); - - private final UrlClassLoaderAccess loaderAccess; - private final File librariesFolder; - private final Set loadedLibraries = new HashSet<>(); - private final List relocationRules = new ArrayList<>(); - - public LibraryLoader(Plugin plugin, File librariesFolder) { - loaderAccess = UrlClassLoaderAccess.create((URLClassLoader) plugin.getClass().getClassLoader()); - this.librariesFolder = librariesFolder; - if (!librariesFolder.exists()) librariesFolder.mkdirs(); - } - - public void deleteUnloadedLibraries() { - File[] files = librariesFolder.listFiles(); - if (files == null) return; - for (File file : files) if (!loadedLibraries.contains(file)) file.delete(); - } - - public void addRelocation(String pre, String post) { - relocationRules.add(new Relocation(pre, post)); - } - - public void loadSnapshotLibrary(String groupId, String artifactId, String version, String snapshotVersion, String repoUrl) { - try { - loadLibrary(groupId + ":" + artifactId + ":" + version, - getDependencyFile(groupId, artifactId, version), - getSnapshotDependencyUrl(groupId, artifactId, version, snapshotVersion, repoUrl)); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - - public int loadedLibraryCount() { - return loadedLibraries.size(); - } - - public void loadLibrary(String groupId, String artifactId, String version) { - loadLibrary(groupId, artifactId, version, "https://repo1.maven.org/maven2"); - } - - public void loadLibrary(String groupId, String artifactId, String version, String repoUrl) { - try { - loadLibrary(groupId + ":" + artifactId + ":" + version, - getDependencyFile(groupId, artifactId, version), - getDependencyUrl(groupId, artifactId, version, repoUrl)); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - - private void loadLibrary(String name, File file, URL url) { - if (!file.exists()) { - try (InputStream in = url.openStream()) { - File temp = new File(file.getParentFile(), file.getName() + ".temp"); - Files.copy(in, temp.toPath()); - new JarRelocator(temp, file, relocationRules).run(); - temp.delete(); - // logger.info("Downloaded library " + name); - } catch (IOException e) { - logger.severe("Failed to download library " + name); - e.printStackTrace(); - } - } - - try { - loaderAccess.addURL(file.toURI().toURL()); - loadedLibraries.add(file); - // logger.info("Loaded library " + name); - } catch (Exception e) { - logger.severe("Failed to load library, plugin may not work correctly (" + name + ")"); - e.printStackTrace(); - } - } - - private File getDependencyFile(String groupId, String artifactId, String version) { - return new File(librariesFolder, groupId.replace(".", "-") + "-" - + artifactId.replace(".", "-") + "-" - + version.replace(".", "-") + ".jar"); - } - - private static URL getDependencyUrl(String groupId, String artifactId, String version, String repoUrl) throws MalformedURLException { - String url = repoUrl.endsWith("/") ? repoUrl : repoUrl + "/"; - url += groupId.replace(".", "/") + "/"; - url += artifactId + "/"; - url += version + "/"; - url += artifactId + "-" + version + ".jar"; - return new URL(url); - } - - private static URL getSnapshotDependencyUrl(String groupId, String artifactId, String version, String snapshotVersion, String repoUrl) throws MalformedURLException { - String url = repoUrl.endsWith("/") ? repoUrl : repoUrl + "/"; - url += groupId.replace(".", "/") + "/"; - url += artifactId + "/"; - url += version + "/"; - url += artifactId + "-" + snapshotVersion + ".jar"; - return new URL(url); - } -} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/libraries/UrlClassLoaderAccess.java b/plugin/src/main/java/lol/pyr/znpcsplus/libraries/UrlClassLoaderAccess.java deleted file mode 100644 index 0e2b11d..0000000 --- a/plugin/src/main/java/lol/pyr/znpcsplus/libraries/UrlClassLoaderAccess.java +++ /dev/null @@ -1,152 +0,0 @@ -package lol.pyr.znpcsplus.libraries; - -import javax.annotation.Nonnull; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Collection; - -/** - * Provides access to {@link URLClassLoader}#addURL. - * From https://github.com/lucko/helper/blob/master/helper/src/main/java/me/lucko/helper/maven/URLClassLoaderAccess.java - */ -public abstract class UrlClassLoaderAccess { - - /** - * Creates a {@link UrlClassLoaderAccess} for the given class loader. - * - * @param classLoader the class loader - * @return the access object - */ - static UrlClassLoaderAccess create(URLClassLoader classLoader) { - if (Reflection.isSupported()) { - return new Reflection(classLoader); - } else if (Unsafe.isSupported()) { - return new Unsafe(classLoader); - } else { - return Noop.INSTANCE; - } - } - - private final URLClassLoader classLoader; - - protected UrlClassLoaderAccess(URLClassLoader classLoader) { - this.classLoader = classLoader; - } - - - /** - * Adds the given URL to the class loader. - * - * @param url the URL to add - */ - public abstract void addURL(@Nonnull URL url); - - /** - * Accesses using reflection, not supported on Java 9+. - */ - private static class Reflection extends UrlClassLoaderAccess { - private static final Method ADD_URL_METHOD; - - static { - Method addUrlMethod; - try { - addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); - addUrlMethod.setAccessible(true); - } catch (Exception e) { - addUrlMethod = null; - } - ADD_URL_METHOD = addUrlMethod; - } - - private static boolean isSupported() { - return ADD_URL_METHOD != null; - } - - Reflection(URLClassLoader classLoader) { - super(classLoader); - } - - @Override - public void addURL(@Nonnull URL url) { - try { - ADD_URL_METHOD.invoke(super.classLoader, url); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - } - - /** - * Accesses using sun.misc.Unsafe, supported on Java 9+. - * - * @author Vaishnav Anil (https://github.com/slimjar/slimjar) - */ - private static class Unsafe extends UrlClassLoaderAccess { - private static final sun.misc.Unsafe UNSAFE; - - static { - sun.misc.Unsafe unsafe; - try { - Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - unsafe = (sun.misc.Unsafe) unsafeField.get(null); - } catch (Throwable t) { - unsafe = null; - } - UNSAFE = unsafe; - } - - private static boolean isSupported() { - return UNSAFE != null; - } - - private final Collection unopenedURLs; - private final Collection pathURLs; - - @SuppressWarnings("unchecked") - Unsafe(URLClassLoader classLoader) { - super(classLoader); - - Collection unopenedURLs; - Collection pathURLs; - try { - Object ucp = fetchField(URLClassLoader.class, classLoader, "ucp"); - unopenedURLs = (Collection) fetchField(ucp.getClass(), ucp, "unopenedUrls"); - pathURLs = (Collection) fetchField(ucp.getClass(), ucp, "path"); - } catch (Throwable e) { - unopenedURLs = null; - pathURLs = null; - } - this.unopenedURLs = unopenedURLs; - this.pathURLs = pathURLs; - } - - private static Object fetchField(final Class clazz, final Object object, final String name) throws NoSuchFieldException { - Field field = clazz.getDeclaredField(name); - long offset = UNSAFE.objectFieldOffset(field); - return UNSAFE.getObject(object, offset); - } - - @Override - public void addURL(@Nonnull URL url) { - this.unopenedURLs.add(url); - this.pathURLs.add(url); - } - } - - private static class Noop extends UrlClassLoaderAccess { - private static final Noop INSTANCE = new Noop(); - - private Noop() { - super(null); - } - - @Override - public void addURL(@Nonnull URL url) { - throw new UnsupportedOperationException(); - } - } - -} \ No newline at end of file diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java index fcc563d..1b102aa 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java @@ -20,6 +20,7 @@ import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.stream.Collectors; @@ -75,8 +76,10 @@ public class NpcImpl extends Viewable implements Npc { return location; } - public Location getBukkitLocation() { - return location.toBukkitLocation(getWorld()); + public @Nullable Location getBukkitLocation() { + World world = getWorld(); + if (world == null) return null; + return location.toBukkitLocation(world); } public void setLocation(NpcLocation location) { @@ -112,7 +115,7 @@ public class NpcImpl extends Viewable implements Npc { return uuid; } - public World getWorld() { + public @Nullable World getWorld() { return Bukkit.getWorld(worldName); } @@ -133,6 +136,7 @@ public class NpcImpl extends Viewable implements Npc { } private void UNSAFE_refreshProperty(EntityPropertyImpl property) { + if (!type.isAllowedProperty(property)) return; for (Player viewer : getViewers()) { List data = property.applyStandalone(viewer, entity, true); if (!data.isEmpty()) packetFactory.sendMetadata(viewer, entity, data); @@ -164,6 +168,12 @@ public class NpcImpl extends Viewable implements Npc { setProperty((EntityPropertyImpl) key, SpigotConversionUtil.fromBukkitItemStack(value)); } + @SuppressWarnings("unchecked") + @Override + public ItemStack getItemProperty(EntityProperty key) { + return SpigotConversionUtil.toBukkitItemStack(getProperty((EntityProperty) key)); + } + public void setProperty(EntityPropertyImpl key, T value) { if (key == null) return; if (value == null || value.equals(key.getDefaultValue())) propertyMap.remove(key); @@ -176,6 +186,11 @@ public class NpcImpl extends Viewable implements Npc { setProperty((EntityPropertyImpl) property, (T) value); } + @SuppressWarnings("unchecked") + public void UNSAFE_setProperty(EntityProperty property, Object value) { + setProperty((EntityPropertyImpl) property, (T) value); + } + public Set> getAllProperties() { return Collections.unmodifiableSet(propertyMap.keySet()); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcRegistryImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcRegistryImpl.java index 3b760e6..bcdea13 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcRegistryImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcRegistryImpl.java @@ -1,12 +1,17 @@ package lol.pyr.znpcsplus.npc; import lol.pyr.znpcsplus.ZNpcsPlus; +import lol.pyr.znpcsplus.api.entity.EntityProperty; import lol.pyr.znpcsplus.api.npc.NpcEntry; import lol.pyr.znpcsplus.api.npc.NpcRegistry; import lol.pyr.znpcsplus.api.npc.NpcType; import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; +import lol.pyr.znpcsplus.hologram.HologramItem; +import lol.pyr.znpcsplus.hologram.HologramLine; +import lol.pyr.znpcsplus.hologram.HologramText; import lol.pyr.znpcsplus.interaction.ActionRegistry; +import lol.pyr.znpcsplus.interaction.InteractionActionImpl; import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.scheduling.TaskScheduler; import lol.pyr.znpcsplus.storage.NpcStorage; @@ -153,6 +158,35 @@ public class NpcRegistryImpl implements NpcRegistry { return entry; } + public NpcEntryImpl clone(String id, String newId, World newWorld, NpcLocation newLocation) { + NpcEntryImpl oldNpc = getById(id); + if (oldNpc == null) return null; + NpcEntryImpl newNpc = create(newId, newWorld, oldNpc.getNpc().getType(), newLocation); + newNpc.enableEverything(); + + for (EntityProperty property : oldNpc.getNpc().getAllProperties()) { + newNpc.getNpc().UNSAFE_setProperty(property, oldNpc.getNpc().getProperty(property)); + } + + for (InteractionActionImpl action : oldNpc.getNpc().getActions()) { + newNpc.getNpc().addAction(action); + } + + for (HologramLine line : oldNpc.getNpc().getHologram().getLines()) { + if (line instanceof HologramText) { + HologramText text = (HologramText) line; + newNpc.getNpc().getHologram().addTextLineComponent(text.getValue()); + } + else if (line instanceof HologramItem) { + HologramItem item = (HologramItem) line; + newNpc.getNpc().getHologram().addItemLinePEStack(item.getValue()); + } + else throw new IllegalArgumentException("Unknown hologram line type during clone"); + } + + return newNpc; + } + @Override public void delete(String id) { NpcEntryImpl entry = npcIdLookupMap.get(id.toLowerCase()); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java index 565493f..d072740 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java @@ -164,12 +164,18 @@ public class NpcTypeImpl implements NpcType { addProperties("panda_eating"); } } - if (EntityTypes.isTypeInstanceOf(type, EntityTypes.ABSTRACT_TAMEABLE_ANIMAL)) { + if (EntityTypes.isTypeInstanceOf(type, EntityTypes.ABSTRACT_TAMEABLE_ANIMAL) && + !(version.isNewerThanOrEquals(ServerVersion.V_1_14) && type.equals(EntityTypes.OCELOT))) { addProperties("tamed", "sitting"); } if (EntityTypes.isTypeInstanceOf(type, EntityTypes.GUARDIAN)) { addProperties("is_retracting_spikes"); } + if (version.isNewerThanOrEquals(ServerVersion.V_1_20_5)) { + if (EntityTypes.isTypeInstanceOf(type, EntityTypes.WOLF)) { + addProperties("wolf_variant"); + } + } return new NpcTypeImpl(name, type, hologramOffset, new HashSet<>(allowedProperties), defaultProperties); } } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeRegistryImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeRegistryImpl.java index 1d197f3..138bbd9 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeRegistryImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeRegistryImpl.java @@ -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") + .addProperties("skin_cape", "skin_jacket", "skin_left_sleeve", "skin_right_sleeve", "skin_left_leg", "skin_right_leg", "skin_hat", "shoulder_entity_left", "shoulder_entity_right", "force_body_rotation") .addDefaultProperty("skin_cape", true) .addDefaultProperty("skin_jacket", true) .addDefaultProperty("skin_left_sleeve", true) @@ -357,7 +357,8 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry { .setHologramOffset(-1.675)); register(builder(p, "warden", EntityTypes.WARDEN) - .setHologramOffset(0.925)); + .setHologramOffset(0.925) + .addProperties("warden_anger")); if (!version.isNewerThanOrEquals(ServerVersion.V_1_20)) return; @@ -368,6 +369,12 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry { register(builder(p, "camel", EntityTypes.CAMEL) .setHologramOffset(0.25) .addProperties("bashing", "camel_sitting")); + + if (!version.isNewerThanOrEquals(ServerVersion.V_1_20_5)) return; + + register(builder(p, "armadillo", EntityTypes.ARMADILLO) + .setHologramOffset(-1.475) + .addProperties("armadillo_state")); } public Collection getAll() { diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/reflection/ReflectionPackage.java b/plugin/src/main/java/lol/pyr/znpcsplus/reflection/ReflectionPackage.java index c58b4c3..4fb169f 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/reflection/ReflectionPackage.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/reflection/ReflectionPackage.java @@ -14,21 +14,27 @@ import java.util.stream.Collectors; * pre-1.17 had all of their classes "flattened" into one package. */ public class ReflectionPackage { - private static final String VERSION = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; - public static final String BUKKIT = "org.bukkit.craftbukkit." + VERSION; + private static final String VERSION = generateVersion(); + public static final String BUKKIT = "org.bukkit.craftbukkit" + VERSION; private static final boolean flattened = !PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_17); /** * Check if the classes are flattened, if so we need to add the version string into the * package string which is another quirk of the old server jars. */ - public static final String MINECRAFT = joinWithDot("net.minecraft", flattened ? "server." + VERSION : ""); + public static final String MINECRAFT = joinWithDot("net.minecraft", flattened ? "server" + VERSION : ""); public static final String ENTITY = flattened ? MINECRAFT : joinWithDot(MINECRAFT, "world.entity"); public static String joinWithDot(String... parts) { return Arrays.stream(parts) .filter(Objects::nonNull) - .filter(p -> p.length() != 0) + .filter(p -> !p.isEmpty()) .collect(Collectors.joining(".")); } + + private static String generateVersion() { + String[] parts = Bukkit.getServer().getClass().getPackage().getName().split("\\."); + if (parts.length > 3) return "." + parts[3]; + return ""; + } }