Compare commits

..

29 commits

Author SHA1 Message Date
bf0b58b4b6 Merge remote-tracking branch 'upstream/2.X' into 2.X
# Conflicts:
#	plugin/build.gradle
2024-11-26 17:39:35 +01:00
Pyr
8b1fde3652
Merge pull request #155 from ArasakaID/2.X 2024-11-12 18:01:25 +00:00
ArasakaID
7bffd2f79d 1.21.3 Update (Untested)
Update packet for 1.21.3
2024-11-11 22:04:44 +07:00
Pyrbu
9304c96723 switch user map to ConcurrentHashMap to prevent errors on Folia 2024-09-05 14:42:34 +02:00
Pyrbu
a3b9bda53a shuffle around relocations to prevent classes from server jar being relocated 2024-09-05 14:20:58 +02:00
Pyr
79761cba92
Merge pull request #146 from SirSalad/2.X
fix: update checker timeout
2024-09-02 09:41:24 +02:00
Pyrbu
7495493c27 make sure worlds arent null 2024-08-19 17:53:27 +02:00
Pyrbu
d02d0cb5e9 expose Npc#getWorldName 2024-08-19 17:52:41 +02:00
Pyrbu
71f52a987b Merge remote-tracking branch 'origin/2.X' into 2.X 2024-08-19 17:45:49 +02:00
Pyrbu
5c99681561 expose Npc#setWorld in the api 2024-08-19 17:45:42 +02:00
D3v1s0m
79ede2b586
add storage migrate command to migrate from one storage type to another 2024-08-18 22:52:09 +05:30
D3v1s0m
304278edd8
use task scheduler for force_body_rotation property instead 2024-08-18 14:53:48 +05:30
D3v1s0m
0272a9ef68
fix database connection url and closing. add ssl option 2024-08-18 14:51:32 +05:30
Pyrbu
d7c0870546 add missing holo commands to messages 2024-08-14 14:18:25 +02:00
Pyrbu
0f7ad57b64 add action hovers 2024-08-14 14:14:58 +02:00
Pyrbu
aeeba809be fix typos 2024-08-14 13:52:27 +02:00
Pyrbu
74463178fe update adventure platform (fixes hovers and probably more things) 2024-08-14 13:29:42 +02:00
SirSalad
1b3a197085
fix: 'final static' semantics 2024-07-27 13:24:01 -04:00
SirSalad
cf8ce5097f
fix: update checker timeout 2024-07-27 13:20:59 -04:00
Pyrbu
13a47fa743 fix fetching descriptor deserialization when name contains ";" 2024-07-20 23:34:13 +02:00
Pyrbu
36a939e497 [ci skip] fix link in readme 2024-07-07 08:22:05 +02:00
Pyrbu
979b95eed5 [ci skip] update readme 2024-07-07 08:17:49 +02:00
Pyrbu
18c5cc0e24 fix typo 2024-07-04 00:26:53 +02:00
Pyrbu
e7a82d55e7 Merge remote-tracking branch 'origin/2.X' into 2.X 2024-07-04 00:25:34 +02:00
Pyrbu
06889a221e make npc property registry accessible during onLoad 2024-07-04 00:25:27 +02:00
D3v1s0m
aafdf0b3f9
update to mc 1.21 2024-07-01 10:12:52 +05:30
D3v1s0m
71430afcc3
fix force_body_rotation property to only apply it true 2024-07-01 10:09:11 +05:30
D3v1s0m
3248418464
fix base hologram offset for multiple npc types 2024-07-01 10:08:01 +05:30
D3v1s0m
18cdef4527
update hologram location on npc type change 2024-07-01 10:06:35 +05:30
38 changed files with 494 additions and 61 deletions

BIN
.github/znpc.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View file

@ -5,7 +5,7 @@
that players can interact with to perform actions like switching servers on a network or executing commands.
This plugin is a remake of a plugin called ZNPCs, we originally started because the maintainer of ZNPCs decided to announce that he was
[dropping support for the plugin](https://media.discordapp.net/attachments/1093914615873806477/1098409384855474237/znpc.png).
[dropping support for the plugin](https://github.com/Pyrbu/ZNPCsPlus/blob/2.X/.github/znpc.png?raw=true).
Looking for up-to-date builds of the plugin? Check out our [Jenkins](https://ci.pyr.lol/job/ZNPCsPlus/)
@ -19,7 +19,7 @@ Looking for up-to-date builds of the plugin? Check out our [Jenkins](https://ci.
### Requirements, Extensions & Supported Software
Requirements:
- Java 8+
- Minecraft 1.8 - 1.20.4
- Minecraft 1.8 - 1.21
Supported Softwares:
- Spigot ([Website](https://www.spigotmc.org/))
@ -43,7 +43,6 @@ Open an issue in the GitHub [issue tracker](https://github.com/Pyrbu/ZNPCsPlus/i
- [wiki.vg](https://wiki.vg/Main_Page) - Minecraft protocol documentation
- [gson](https://github.com/google/gson) - JSON parsing library made by Google
- [Mineskin.org](https://mineskin.org/) - Website for raw skin file uploads
- [SpigotResourcesAPI](https://github.com/robertlit/SpigotResourcesAPI/) - Spigot API wrapper used for updater
- [adventure](https://docs.advntr.dev/) - Minecraft text api
- [DazzleConf](https://github.com/A248/DazzleConf) - Configuration library
- [Director](https://github.com/Pyrbu/Director) - Command library

View file

@ -0,0 +1,46 @@
package lol.pyr.znpcsplus.api;
import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
import org.bukkit.Bukkit;
/**
* Provider for the registered entity property registry instance
*/
public class NpcPropertyRegistryProvider {
private static EntityPropertyRegistry registry = null;
private NpcPropertyRegistryProvider() {
throw new UnsupportedOperationException();
}
/**
* Static method that returns the entity property registry instance of the plugin
*
* @return The instance of the entity property registry for the ZNPCsPlus plugin
*/
public static EntityPropertyRegistry get() {
if (registry == null) throw new IllegalStateException(
"ZNPCsPlus plugin isn't loaded yet!\n" +
"Please add it to your plugin.yml as a depend or softdepend."
);
return registry;
}
/**
* 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 api Instance of the ZNPCsPlus entity property registry
*/
public static void register(EntityPropertyRegistry api) {
NpcPropertyRegistryProvider.registry = api;
}
/**
* Internal method used to unregister the plugin from the provider when the plugin shuts down
* You probably shouldn't call this method under any circumstances
*/
public static void unregister() {
Bukkit.getServicesManager().unregister(registry);
}
}

View file

@ -38,6 +38,18 @@ public interface Npc extends PropertyHolder {
*/
void setLocation(NpcLocation location);
/**
* Sets the world of this NPC
* @param world The bukkit world to set
*/
void setWorld(World world);
/**
* Sets the world of this NPC
* @param name The name world to set
*/
void setWorld(String name);
/**
* Gets the hologram of this NPC
* @return The {@link Hologram} of this NPC
@ -69,6 +81,13 @@ public interface Npc extends PropertyHolder {
*/
World getWorld();
/**
* Gets the name of the world this NPC is in
* Unlike {@link Npc#getWorld()} this will never be null
* @return The name of the world this NPC is in
*/
String getWorldName();
/**
* Gets the list of actions for this NPC
* @return The {@link List} of {@link InteractionAction}s for this NPC

View file

@ -20,5 +20,8 @@ public enum NpcPose {
ROARING,
SNIFFING,
EMERGING,
DIGGING
DIGGING,
SLIDING,
SHOOTING,
INHALING,
}

View file

@ -4,36 +4,27 @@ plugins {
id "xyz.jpenilla.run-paper" version "2.2.0"
}
runServer {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(21)
}
minecraftVersion "1.20.6"
}
processResources {
expand("version": version)
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
artifactId = "znpcsplus-plugin"
pom {
name.set("znpcsplus-plugin")
description.set("The ZNPCsPlus plugin")
url.set("https://github.com/Pyrbu/ZNPCsPlus")
}
}
}
}
dependencies {
compileOnly "me.clip:placeholderapi:2.11.6" // Placeholder support
compileOnly "com.github.retrooper:packetevents-spigot:2.6.0" // Packets
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 "space.arim.dazzleconf:dazzleconf-ext-snakeyaml:1.2.1" // Configs
implementation "lol.pyr:director-adventure:2.1.2" // Commands
// Fancy text library
implementation "net.kyori:adventure-platform-bukkit:4.3.3"
implementation "net.kyori:adventure-platform-bukkit:4.3.4"
implementation "net.kyori:adventure-text-minimessage:4.17.0"
implementation project(":api")
@ -52,6 +43,7 @@ shadowJar {
relocate "net.kyori", "lol.pyr.znpcsplus.libraries.kyori"
relocate "org.checkerframework", "lol.pyr.znpcsplus.libraries.checkerframework"
relocate "com.google", "lol.pyr.znpcsplus.libraries.google"
relocate "com.google.gson", "lol.pyr.znpcsplus.libraries.gson"
relocate "org.yaml.snakeyaml", "lol.pyr.znpcsplus.libraries.snakeyaml"
relocate "space.arim.dazzleconf", "lol.pyr.znpcsplus.libraries.dazzleconf"
relocate "lol.pyr.director", "lol.pyr.znpcsplus.libraries.command"

View file

@ -14,6 +14,7 @@ import lol.pyr.director.adventure.parse.primitive.FloatParser;
import lol.pyr.director.adventure.parse.primitive.IntegerParser;
import lol.pyr.director.common.message.Message;
import lol.pyr.znpcsplus.api.NpcApiProvider;
import lol.pyr.znpcsplus.api.NpcPropertyRegistryProvider;
import lol.pyr.znpcsplus.api.interaction.InteractionType;
import lol.pyr.znpcsplus.commands.*;
import lol.pyr.znpcsplus.commands.action.*;
@ -22,6 +23,7 @@ import lol.pyr.znpcsplus.commands.property.PropertyRemoveCommand;
import lol.pyr.znpcsplus.commands.property.PropertySetCommand;
import lol.pyr.znpcsplus.commands.storage.ImportCommand;
import lol.pyr.znpcsplus.commands.storage.LoadAllCommand;
import lol.pyr.znpcsplus.commands.storage.MigrateCommand;
import lol.pyr.znpcsplus.commands.storage.SaveAllCommand;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.conversion.DataImporterRegistry;
@ -38,6 +40,7 @@ import lol.pyr.znpcsplus.scheduling.SpigotScheduler;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.skin.cache.SkinCacheCleanTask;
import lol.pyr.znpcsplus.storage.NpcStorageType;
import lol.pyr.znpcsplus.tasks.HologramRefreshTask;
import lol.pyr.znpcsplus.tasks.NpcProcessorTask;
import lol.pyr.znpcsplus.tasks.ViewableHideOnLeaveListener;
@ -78,12 +81,23 @@ public class ZNpcsPlus {
private final PacketEventsAPI<Plugin> packetEvents;
private final ZNpcsPlusBootstrap bootstrap;
private final ConfigManager configManager;
private final MojangSkinCache skinCache;
private final EntityPropertyRegistryImpl propertyRegistry;
public ZNpcsPlus(ZNpcsPlusBootstrap bootstrap) {
this.bootstrap = bootstrap;
packetEvents = SpigotPacketEventsBuilder.build(bootstrap);
PacketEvents.setAPI(packetEvents);
packetEvents.getSettings().checkForUpdates(false);
packetEvents.load();
configManager = new ConfigManager(getDataFolder());
skinCache = new MojangSkinCache(configManager);
propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager);
NpcPropertyRegistryProvider.register(propertyRegistry);
shutdownTasks.add(NpcPropertyRegistryProvider::unregister);
}
private void log(String str) {
@ -113,11 +127,9 @@ public class ZNpcsPlus {
TaskScheduler scheduler = FoliaUtil.isFolia() ? new FoliaScheduler(bootstrap) : new SpigotScheduler(bootstrap);
shutdownTasks.add(scheduler::cancelAll);
ConfigManager configManager = new ConfigManager(getDataFolder());
MojangSkinCache skinCache = new MojangSkinCache(configManager);
EntityPropertyRegistryImpl propertyRegistry = new EntityPropertyRegistryImpl(skinCache, configManager);
PacketFactory packetFactory = setupPacketFactory(scheduler, propertyRegistry, configManager);
propertyRegistry.registerTypes(bootstrap, packetFactory, textSerializer);
propertyRegistry.registerTypes(bootstrap, packetFactory, textSerializer, scheduler);
BungeeConnector bungeeConnector = new BungeeConnector(bootstrap);
ActionRegistryImpl actionRegistry = new ActionRegistryImpl();
@ -147,7 +159,7 @@ public class ZNpcsPlus {
pluginManager.registerEvents(new UserListener(userManager), bootstrap);
registerCommands(npcRegistry, skinCache, adventure, actionRegistry,
typeRegistry, propertyRegistry, importerRegistry, configManager);
typeRegistry, propertyRegistry, importerRegistry, configManager, packetFactory);
log(ChatColor.WHITE + " * Starting tasks...");
if (configManager.getConfig().checkForUpdates()) {
@ -219,6 +231,7 @@ public class ZNpcsPlus {
versions.put(ServerVersion.V_1_17, LazyLoader.of(() -> new V1_17PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
versions.put(ServerVersion.V_1_19_3, LazyLoader.of(() -> new V1_19_3PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
versions.put(ServerVersion.V_1_20_2, LazyLoader.of(() -> new V1_20_2PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
versions.put(ServerVersion.V_1_21_3, LazyLoader.of(() -> new V1_21_3PacketFactory(scheduler, packetEvents, propertyRegistry, textSerializer, configManager)));
ServerVersion version = packetEvents.getServerManager().getVersion();
if (versions.containsKey(version)) return versions.get(version).get();
@ -233,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) {
ConfigManager configManager, PacketFactory packetFactory) {
Message<CommandContext> incorrectUsageMessage = context -> context.send(Component.text("Incorrect usage: /" + context.getUsage(), NamedTextColor.RED));
CommandManager manager = new CommandManager(bootstrap, adventure, incorrectUsageMessage);
@ -282,6 +295,7 @@ public class ZNpcsPlus {
registerEnumParser(manager, Sound.class, incorrectUsageMessage);
registerEnumParser(manager, ArmadilloState.class, incorrectUsageMessage);
registerEnumParser(manager, WoldVariant.class, incorrectUsageMessage);
registerEnumParser(manager, NpcStorageType.class, incorrectUsageMessage);
manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root"))
.addSubcommand("center", new CenterCommand(npcRegistry))
@ -306,7 +320,8 @@ public class ZNpcsPlus {
.addSubcommand("storage", new MultiCommand(bootstrap.loadHelpMessage("storage"))
.addSubcommand("save", new SaveAllCommand(npcRegistry))
.addSubcommand("reload", new LoadAllCommand(npcRegistry))
.addSubcommand("import", new ImportCommand(npcRegistry, importerRegistry)))
.addSubcommand("import", new ImportCommand(npcRegistry, importerRegistry))
.addSubcommand("migrate", new MigrateCommand(configManager, this, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, npcRegistry.getStorage(), configManager.getConfig().storageType(), npcRegistry)))
.addSubcommand("holo", new MultiCommand(bootstrap.loadHelpMessage("holo"))
.addSubcommand("add", new HoloAddCommand(npcRegistry))
.addSubcommand("additem", new HoloAddItemCommand(npcRegistry))

View file

@ -0,0 +1,176 @@
package lol.pyr.znpcsplus.commands.storage;
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 lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
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.storage.NpcStorage;
import lol.pyr.znpcsplus.storage.NpcStorageType;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
public class MigrateCommand implements CommandHandler {
private final ConfigManager configManager;
private final ZNpcsPlus plugin;
private final PacketFactory packetFactory;
private final ActionRegistryImpl actionRegistry;
private final NpcTypeRegistryImpl typeRegistry;
private final EntityPropertyRegistryImpl propertyRegistry;
private final LegacyComponentSerializer textSerializer;
private final NpcStorage currentStorage;
private final NpcStorageType currentStorageType;
private final NpcRegistryImpl npcRegistry;
public MigrateCommand(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcStorage currentStorage, NpcStorageType currentStorageType, NpcRegistryImpl npcRegistry) {
this.configManager = configManager;
this.plugin = plugin;
this.packetFactory = packetFactory;
this.actionRegistry = actionRegistry;
this.typeRegistry = typeRegistry;
this.propertyRegistry = propertyRegistry;
this.textSerializer = textSerializer;
this.currentStorage = currentStorage;
this.currentStorageType = currentStorageType;
this.npcRegistry = npcRegistry;
}
@Override
public void run(CommandContext context) throws CommandExecutionException {
context.setUsage(context.getLabel() + " storage migrate <from> <to> [force]");
NpcStorageType from = context.parse(NpcStorageType.class);
NpcStorageType to = context.parse(NpcStorageType.class);
boolean force = context.argSize() > 2 && context.parse(Boolean.class);
if (from.equals(to)) {
context.halt(Component.text("The storage types must be different.", NamedTextColor.RED));
return;
}
NpcStorage fromStorage;
if (currentStorageType == from) {
fromStorage = currentStorage;
} else {
fromStorage = from.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
if (fromStorage == null) {
context.halt(Component.text("Failed to initialize the source storage. Please check the console for more information.", NamedTextColor.RED));
return;
}
}
Collection<NpcEntryImpl> entries;
try {
entries = fromStorage.loadNpcs();
} catch (Exception e) {
context.halt(Component.text("Failed to load NPCs from the source storage.", NamedTextColor.RED));
return;
}
if (entries.isEmpty()) {
context.send(Component.text("No NPCs to migrate.", NamedTextColor.YELLOW));
return;
}
NpcStorage toStorage;
if (currentStorageType == to) {
toStorage = currentStorage;
} else {
toStorage = to.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
if (toStorage == null) {
context.halt(Component.text("Failed to initialize the destination storage. Please check the console for more information.", NamedTextColor.RED));
return;
}
}
Collection<NpcEntryImpl> existingEntries;
try {
existingEntries = toStorage.loadNpcs();
} catch (Exception e) {
context.halt(Component.text("Failed to load NPCs from the destination storage.", NamedTextColor.RED));
return;
}
if (existingEntries.isEmpty()) {
toStorage.saveNpcs(entries);
context.send(Component.text("Migrated " + entries.size() + " NPCs from the source storage (", NamedTextColor.GREEN)
.append(Component.text(from.name(), NamedTextColor.GOLD))
.append(Component.text(") to the destination storage (", NamedTextColor.GREEN))
.append(Component.text(to.name(), NamedTextColor.GOLD))
.append(Component.text(").", NamedTextColor.GREEN)));
if (currentStorageType == to) {
npcRegistry.reload();
} else {
toStorage.close();
}
return;
}
if (!force) {
Collection<NpcEntryImpl> toSave = entries.stream().filter(e -> existingEntries.stream().noneMatch(e2 -> e2.getId().equals(e.getId()))).collect(Collectors.toList());
Collection<NpcEntryImpl> idExists = entries.stream().filter(e -> existingEntries.stream().anyMatch(e2 -> e2.getId().equals(e.getId()))).collect(Collectors.toList());
if (toSave.isEmpty()) {
context.send(Component.text("No NPCs to migrate.", NamedTextColor.YELLOW));
if (currentStorageType != to) {
toStorage.close();
}
} else {
toStorage.saveNpcs(toSave);
context.send(Component.text("Migrated " + toSave.size() + " NPCs from the source storage (", NamedTextColor.GREEN)
.append(Component.text(from.name(), NamedTextColor.GOLD))
.append(Component.text(") to the destination storage (", NamedTextColor.GREEN))
.append(Component.text(to.name(), NamedTextColor.GOLD))
.append(Component.text(").", NamedTextColor.GREEN)));
if (currentStorageType == to) {
npcRegistry.reload();
} else {
toStorage.close();
}
}
if (!idExists.isEmpty()) {
AtomicReference<Component> component = new AtomicReference<>(Component.text("The following NPCs were not migrated because their IDs already exist in the destination storage:").color(NamedTextColor.YELLOW));
idExists.forEach(e -> {
component.set(component.get().append(Component.newline()).append(Component.text(e.getId(), NamedTextColor.RED)));
});
component.set(component.get().append(Component.newline())
.append(Component.text("Use the ", NamedTextColor.YELLOW))
.append(Component.text("force", NamedTextColor.GOLD))
.append(Component.text(" argument to overwrite them.", NamedTextColor.YELLOW)));
context.send(component.get());
}
} else {
toStorage.saveNpcs(entries);
context.send(Component.text("Force migrated " + entries.size() + " NPCs from the source storage (", NamedTextColor.GREEN)
.append(Component.text(from.name(), NamedTextColor.GOLD))
.append(Component.text(") to the destination storage (", NamedTextColor.GREEN))
.append(Component.text(to.name(), NamedTextColor.GOLD))
.append(Component.text(").", NamedTextColor.GREEN)));
if (currentStorageType == to) {
npcRegistry.reload();
} else {
toStorage.close();
}
}
}
@Override
public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) {
return context.suggestEnum(NpcStorageType.values());
} else if (context.argSize() == 2) {
NpcStorageType from = context.suggestionParse(0, NpcStorageType.class);
if (from == null) return Collections.emptyList();
return context.suggestCollection(Arrays.stream(NpcStorageType.values())
.filter(t -> t != from).map(Enum::name).collect(Collectors.toList()));
} else if (context.argSize() == 3) {
return context.suggestLiteral("true");
}
return Collections.emptyList();
}
}

View file

@ -30,9 +30,14 @@ public interface DatabaseConfig {
@DefaultString("znpcsplus")
String databaseName();
@ConfKey("use-ssl")
@ConfComments("Should SSL be used when connecting to the database?")
@DefaultBoolean(false)
boolean useSSL();
default String createConnectionURL(String dbType) {
if (dbType.equalsIgnoreCase("mysql")) {
return "jdbc:mysql://" + host() + ":" + port() + "/" + databaseName() + "?useSSL=false&user=" + username() + "&password=" + password();
return "jdbc:mysql://" + host() + ":" + port() + "/" + databaseName() + "?useSSL=" + useSSL();
} else {
throw new IllegalArgumentException("Unsupported database type: " + dbType);
}

View file

@ -20,6 +20,7 @@ import lol.pyr.znpcsplus.entity.properties.villager.VillagerProfessionProperty;
import lol.pyr.znpcsplus.entity.properties.villager.VillagerTypeProperty;
import lol.pyr.znpcsplus.entity.serializers.*;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.util.*;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
@ -104,7 +105,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
*/
}
public void registerTypes(ZNpcsPlusBootstrap plugin, PacketFactory packetFactory, LegacyComponentSerializer textSerializer) {
public void registerTypes(ZNpcsPlusBootstrap plugin, PacketFactory packetFactory, LegacyComponentSerializer textSerializer, TaskScheduler taskScheduler) {
ServerVersion ver = PacketEvents.getAPI().getServerManager().getVersion();
boolean legacyBooleans = ver.isOlderThan(ServerVersion.V_1_9);
boolean legacyNames = ver.isOlderThan(ServerVersion.V_1_9);
@ -127,7 +128,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
register(new DummyProperty<>("permission_required", false));
register(new ForceBodyRotationProperty(plugin));
register(new ForceBodyRotationProperty(plugin, taskScheduler));
register(new DummyProperty<>("player_knockback", false));
register(new DummyProperty<>("player_knockback_exempt_permission", String.class));
@ -659,6 +660,11 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
// Wolf
register(new EncodedIntegerProperty<>("wolf_variant", WoldVariant.PALE, wolfIndex, WoldVariant::getId, EntityDataTypes.WOLF_VARIANT));
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_21)) return;
// Bogged
register(new BooleanProperty("bogged_sheared", 16, false, legacyBooleans));
}
private void registerSerializer(PropertySerializer<?> serializer) {

View file

@ -3,22 +3,26 @@ 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 lol.pyr.znpcsplus.scheduling.TaskScheduler;
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) {
public ForceBodyRotationProperty(ZNpcsPlusBootstrap plugin, TaskScheduler scheduler) {
super("force_body_rotation", false);
this.plugin = plugin;
this.scheduler = scheduler;
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
Bukkit.getScheduler().runTaskLater(plugin, () -> entity.swingHand(player, false), 2L);
Bukkit.getScheduler().runTaskLater(plugin, () -> entity.swingHand(player, false), 6L);
if (entity.getProperty(this)) {
scheduler.runLaterAsync(() -> entity.swingHand(player, false), 2L);
scheduler.runLaterAsync(() -> entity.swingHand(player, false), 6L);
}
}
}

View file

@ -44,7 +44,7 @@ public class ConsoleCommandActionType implements InteractionActionType<ConsoleCo
@Override
public void appendUsage(CommandContext context) {
context.setUsage(context.getUsage() + " " + getSubcommandName() + " <id> <click type> <cooldown seconds> <delay ticks> <server>");
context.setUsage(context.getUsage() + " " + getSubcommandName() + " <id> <click type> <cooldown seconds> <delay ticks> <command>");
}
@Override

View file

@ -43,7 +43,7 @@ public class PlayerChatActionType implements InteractionActionType<PlayerChatAct
@Override
public void appendUsage(CommandContext context) {
context.setUsage(context.getUsage() + " " + getSubcommandName() + " <id> <click type> <cooldown seconds> <delay ticks> <server>");
context.setUsage(context.getUsage() + " " + getSubcommandName() + " <id> <click type> <cooldown seconds> <delay ticks> <message>");
}
@Override

View file

@ -56,6 +56,7 @@ public class NpcImpl extends Viewable implements Npc {
UNSAFE_hideAll();
this.type = type;
entity = new PacketEntity(packetFactory, this, type.getType(), entity.getLocation());
hologram.setLocation(location.withY(location.getY() + type.getHologramOffset()));
UNSAFE_showAll();
}
@ -231,10 +232,17 @@ public class NpcImpl extends Viewable implements Npc {
}
public void setWorld(World world) {
if (world == null) throw new IllegalArgumentException("world can not be null");
delete();
this.worldName = world.getName();
}
public void setWorld(String name) {
if (name == null) throw new IllegalArgumentException("world name can not be null");
delete();
this.worldName = name;
}
public void swingHand(boolean offHand) {
for (Player viewer : getViewers()) entity.swingHand(viewer, offHand);
}

View file

@ -207,5 +207,10 @@ public class NpcRegistryImpl implements NpcRegistry {
public void unload() {
npcList.forEach(npcEntry -> npcEntry.getNpc().delete());
storage.close();
}
public NpcStorage getStorage() {
return storage;
}
}

View file

@ -48,7 +48,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
// Most hologram offsets generated using Entity#getHeight() in 1.19.4
register(builder(p, "armor_stand", EntityTypes.ARMOR_STAND)
.setHologramOffset(-0.15)
.setHologramOffset(0)
.addEquipmentProperties()
.addProperties("small", "arms", "base_plate", "head_rotation", "body_rotation", "left_arm_rotation", "right_arm_rotation", "left_leg_rotation", "right_leg_rotation"));
@ -106,7 +106,8 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "iron_golem", EntityTypes.IRON_GOLEM)
.setHologramOffset(0.725));
register(builder(p, "magma_cube", EntityTypes.MAGMA_CUBE)); // TODO: Hologram offset scaling with size property
register(builder(p, "magma_cube", EntityTypes.MAGMA_CUBE)
.setHologramOffset(-1.455)); // TODO: Hologram offset scaling with size property
register(builder(p, "mooshroom", EntityTypes.MOOSHROOM)
.setHologramOffset(-0.575)
@ -137,7 +138,8 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "skeleton_horse", EntityTypes.SKELETON_HORSE)
.setHologramOffset(-0.375));
register(builder(p, "slime", EntityTypes.SLIME)); // TODO: Hologram offset scaling with size property
register(builder(p, "slime", EntityTypes.SLIME)
.setHologramOffset(-1.455)); // TODO: Hologram offset scaling with size property
register(builder(p, "snow_golem", EntityTypes.SNOW_GOLEM)
.setHologramOffset(-0.075)
@ -230,7 +232,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
.addEquipmentProperties());
register(builder(p, "zombie_villager", EntityTypes.ZOMBIE_VILLAGER)
.setHologramOffset(-1.0)
.setHologramOffset(-0.025)
.addEquipmentProperties());
if (!version.isNewerThanOrEquals(ServerVersion.V_1_12)) return;
@ -315,7 +317,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
.addProperties("hoglin_immune_to_zombification"));
register(builder(p, "piglin", EntityTypes.PIGLIN)
.setHologramOffset(-1.0)
.setHologramOffset(-0.025)
.addEquipmentProperties()
.addProperties("piglin_baby", "piglin_charging_crossbow", "piglin_dancing"));
@ -363,19 +365,28 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
if (!version.isNewerThanOrEquals(ServerVersion.V_1_20)) return;
register(builder(p, "sniffer", EntityTypes.SNIFFER)
.setHologramOffset(0.125)
.setHologramOffset(0.075)
.addProperties("sniffer_state"));
register(builder(p, "camel", EntityTypes.CAMEL)
.setHologramOffset(0.25)
.setHologramOffset(0.4)
.addProperties("bashing", "camel_sitting"));
if (!version.isNewerThanOrEquals(ServerVersion.V_1_20_5)) return;
register(builder(p, "armadillo", EntityTypes.ARMADILLO)
.setHologramOffset(-1.475)
.setHologramOffset(-1.325)
.addProperties("armadillo_state"));
if (!version.isNewerThanOrEquals(ServerVersion.V_1_21)) return;
register(builder(p, "bogged", EntityTypes.BOGGED)
.setHologramOffset(0.015)
.addProperties("bogged_sheared"));
register(builder(p, "breeze", EntityTypes.BREEZE)
.setHologramOffset(-0.205));
}
public Collection<NpcType> getAll() {

View file

@ -0,0 +1,29 @@
package lol.pyr.znpcsplus.packets;
import com.github.retrooper.packetevents.PacketEventsAPI;
import com.github.retrooper.packetevents.protocol.entity.EntityPositionData;
import com.github.retrooper.packetevents.protocol.teleport.RelativeFlag;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityHeadLook;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityTeleport;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.util.NpcLocation;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
public class V1_21_3PacketFactory extends V1_20_2PacketFactory {
public V1_21_3PacketFactory(TaskScheduler scheduler, PacketEventsAPI<Plugin> packetEvents, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, ConfigManager configManager) {
super(scheduler, packetEvents, propertyRegistry, textSerializer, configManager);
}
@Override
public void teleportEntity(Player player, PacketEntity entity) {
NpcLocation location = entity.getLocation();
sendPacket(player, new WrapperPlayServerEntityTeleport(entity.getEntityId(), new EntityPositionData(npcLocationToVector(location), new Vector3d(0, 0, 0), location.getYaw(), location.getPitch()), RelativeFlag.NONE, false));
sendPacket(player, new WrapperPlayServerEntityHeadLook(entity.getEntityId(), location.getYaw()));
}
}

View file

@ -9,6 +9,7 @@ import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@ -21,7 +22,7 @@ public interface BaseSkinDescriptor extends SkinDescriptor {
static BaseSkinDescriptor deserialize(MojangSkinCache skinCache, String str) {
String[] arr = str.split(";");
if (arr[0].equalsIgnoreCase("mirror")) return new MirrorDescriptor(skinCache);
else if (arr[0].equalsIgnoreCase("fetching")) return new FetchingDescriptor(skinCache, arr[1]);
else if (arr[0].equalsIgnoreCase("fetching")) return new FetchingDescriptor(skinCache, String.join(";", Arrays.copyOfRange(arr, 1, arr.length)));
else if (arr[0].equalsIgnoreCase("prefetched")) {
List<TextureProperty> properties = new ArrayList<>();
for (int i = 0; i < (arr.length - 1) / 3; i++) {

View file

@ -8,4 +8,7 @@ public interface NpcStorage {
Collection<NpcEntryImpl> loadNpcs();
void saveNpcs(Collection<NpcEntryImpl> npcs);
void deleteNpc(NpcEntryImpl npc);
default void close() {
}
}

View file

@ -13,4 +13,6 @@ public abstract class Database {
public abstract Connection getSQLConnection();
public abstract void load();
public abstract void close();
}

View file

@ -10,10 +10,14 @@ import java.util.logging.Logger;
public class MySQL extends Database {
private final String connectionURL;
private final String username;
private final String password;
public MySQL(String connectionURL, Logger logger) {
public MySQL(String connectionURL, String username, String password, Logger logger) {
super(logger);
this.connectionURL = connectionURL;
this.username = username;
this.password = password;
}
@Override
@ -25,7 +29,7 @@ public class MySQL extends Database {
return connection;
}
Class.forName("com.mysql.jdbc.Driver");
connection = java.sql.DriverManager.getConnection(connectionURL);
connection = java.sql.DriverManager.getConnection(connectionURL, username, password);
return connection;
} catch (ClassNotFoundException ex) {
logger.severe("MySQL JDBC library not found" + ex);
@ -56,6 +60,18 @@ public class MySQL extends Database {
connection = getSQLConnection();
}
@Override
public void close() {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
logger.severe("An error occurred while closing the connection");
e.printStackTrace();
}
}
public boolean tableExists(String tableName) {
try {
Statement s = connection.createStatement();

View file

@ -46,7 +46,8 @@ public class MySQLStorage implements NpcStorage {
this.typeRegistry = typeRegistry;
this.propertyRegistry = propertyRegistry;
this.textSerializer = textSerializer;
this.database = new MySQL(configManager.getConfig().databaseConfig().createConnectionURL("mysql"), logger);
this.database = new MySQL(configManager.getConfig().databaseConfig().createConnectionURL("mysql"),
configManager.getConfig().databaseConfig().username(), configManager.getConfig().databaseConfig().password(), logger);
database.load();
if (database.getSQLConnection() == null) {
throw new RuntimeException("Failed to initialize MySQL Storage");
@ -313,4 +314,9 @@ public class MySQLStorage implements NpcStorage {
exception.printStackTrace();
}
}
@Override
public void close() {
database.close();
}
}

View file

@ -41,6 +41,18 @@ public class SQLite extends Database{
connection = getSQLConnection();
}
@Override
public void close() {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
logger.severe("An error occurred while closing the connection");
e.printStackTrace();
}
}
public boolean tableExists(String tableName) {
try {
Statement s = connection.createStatement();

View file

@ -312,4 +312,9 @@ public class SQLiteStorage implements NpcStorage {
exception.printStackTrace();
}
}
@Override
public void close() {
database.close();
}
}

View file

@ -1,20 +1,21 @@
package lol.pyr.znpcsplus.updater;
import me.robertlit.spigotresources.api.Resource;
import me.robertlit.spigotresources.api.SpigotResourcesAPI;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.concurrent.TimeUnit;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.logging.Logger;
public class UpdateChecker extends BukkitRunnable {
private final static Logger logger = Logger.getLogger("ZNPCsPlus Update Checker");
private final static int RESOURCE_ID = 109380;
private final static String GET_RESOURCE = "https://api.spigotmc.org/simple/0.2/index.php?action=getResource&id=109380";
public final static String DOWNLOAD_LINK = "https://www.spigotmc.org/resources/znpcsplus.109380/";
private final SpigotResourcesAPI api = new SpigotResourcesAPI(1, TimeUnit.MINUTES);
private final PluginDescriptionFile info;
private Status status = Status.UNKNOWN;
private String newestVersion = "N/A";
@ -24,9 +25,29 @@ public class UpdateChecker extends BukkitRunnable {
}
public void run() {
Resource resource = api.getResource(RESOURCE_ID).join();
if (resource == null) return;
newestVersion = resource.getVersion();
String foundVersion = null;
try {
URL getResource = new URL(GET_RESOURCE);
HttpURLConnection httpRequest = ((HttpURLConnection) getResource.openConnection());
httpRequest.setRequestMethod("GET");
httpRequest.setConnectTimeout(5_000);
httpRequest.setReadTimeout(5_000);
if (httpRequest.getResponseCode() == HttpURLConnection.HTTP_OK) {
try (InputStreamReader reader = new InputStreamReader(httpRequest.getInputStream())) {
JsonObject jsonObject = JsonParser.parseReader(reader).getAsJsonObject();
foundVersion = jsonObject.get("current_version").getAsString();
}
} else {
logger.warning("Failed to check for updates: HTTP response code " + httpRequest.getResponseCode());
}
} catch (IOException e) {
logger.warning("Failed to check for updates: " + e.getMessage());
return;
}
if (foundVersion == null) return;
newestVersion = foundVersion;
status = compareVersions(info.getVersion(), newestVersion);
if (status == Status.UPDATE_NEEDED) notifyConsole();

View file

@ -3,12 +3,12 @@ package lol.pyr.znpcsplus.user;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class UserManager {
private final Map<UUID, User> userMap = new HashMap<>();
private final Map<UUID, User> userMap = new ConcurrentHashMap<>();
public UserManager() {
Bukkit.getOnlinePlayers().forEach(this::get);

View file

@ -0,0 +1,13 @@
<gray>Examples:
<gold>* <yellow>/npc action add <gold>consolecommand cool_npc1 ANY_CLICK 0 0 say {player} just clicked a cool npc!
<gold>* <yellow>/npc action add <gold>playerchat dog LEFT_CLICK 0 100 It has been 5 seconds since i clicked the npc
<gold>* <yellow>/npc action add <gold>message npc123 RIGHT_CLICK 1 0 You can only click this npc once per second
<gray>Action Types:
<gold>* <yellow>Console Command <gray>- Send a console command when a player interacts with the npc
<gold>* <yellow>Message <gray>- Send a message to any player that interacts with the npc
<gold>* <yellow>Player Chat <gray>- Make any player that interacts send something in the chat
<gold>* <yellow>Player Command <gray>- Make any player that interacts send a command
<gold>* <yellow>Switch Server <gray>- Send the player to a different server on the proxy using bungee messaging channel
<gray>Command used to add actions to an npc

View file

@ -0,0 +1,3 @@
<gray>Usage <gold>» <yellow>/npc action clear <gold><id>
<gray>Command used to clear all npc actions

View file

@ -0,0 +1,3 @@
<gray>Usage <gold>» <yellow>/npc action delete <gold><id> <action id>
<gray>Command used to delete a specific action from an npc

View file

@ -0,0 +1,3 @@
<gray>Usage <gold>» <yellow>/npc action edit <gold><id> <action id> <type> <args>
<gray>Command used to change a specific action on an npc

View file

@ -0,0 +1,3 @@
<gray>Usage <gold>» <yellow>/npc action list <gold><id>
<gray>Command used to list all actions of an npc

View file

@ -3,9 +3,16 @@
<gray>Hover over any command more info
<hover:show_text:'{@holo-hover/add}'><gold>* <yellow>/npc holo add <id> <text></hover>
<hover:show_text:'{@holo-hover/delete}'><gold>* <yellow>/npc holo delete <id> <line></hover>
<hover:show_text:'{@holo-hover/set}'><gold>* <yellow>/npc holo set <id> <line> <text></hover>
<hover:show_text:'{@holo-hover/insert}'><gold>* <yellow>/npc holo insert <id> <line> <text></hover>
<hover:show_text:'{@holo-hover/additem}'><gold>* <yellow>/npc holo additem <id></hover>
<hover:show_text:'{@holo-hover/setitem}'><gold>* <yellow>/npc holo setitem <id> <line></hover>
<hover:show_text:'{@holo-hover/insertitem}'><gold>* <yellow>/npc holo insertitem <id> <line></hover>
<hover:show_text:'{@holo-hover/delete}'><gold>* <yellow>/npc holo delete <id> <line></hover>
<hover:show_text:'{@holo-hover/offset}'><gold>* <yellow>/npc holo offset <id> <amount></hover>
<hover:show_text:'{@holo-hover/refreshdelay}'><gold>* <yellow>/npc holo refreshdelay <id> <delay></hover>
<hover:show_text:'{@holo-hover/info}'><gold>* <yellow>/npc holo info <id></hover>

View file

@ -0,0 +1,16 @@
<gray>Usage <gold>» <yellow>/npc storage migrate <gold><from> <to> [force]
<gray>Storage Types:
<gold>* <yellow>YAML <gray>- Npcs are stored in yaml files
<gold>* <yellow>SQLite <gray>- Npcs are stored in a SQLite database
<gold>* <yellow>MySQL <gray>- Npcs are stored in a MySQL database
<gray>Command used to migrate npcs from one storage type to another.
This command will NOT delete the original storage files or database,
but will copy the npcs to the new storage type.
<gray>This will also not overwrite any existing npcs in the new storage
type, unless the <gold>force <gray>argument is set to <gold>true<gray>.
<red>Warning: <bold>force</bold> will overwrite any existing npcs with the same id
in the new storage type and CANNOT be undone.

View file

@ -5,4 +5,5 @@
<hover:show_text:'{@storage-hover/save}'><gold>* <yellow>/npc storage save</hover>
<hover:show_text:'{@storage-hover/reload}'><gold>* <yellow>/npc storage reload</hover>
<hover:show_text:'{@storage-hover/import}'><gold>* <yellow>/npc storage import <importer></hover>
<hover:show_text:'{@storage-hover/migrate}'><gold>* <yellow>/npc storage migrate <from> <to> [force]</hover>