start work on persistence

This commit is contained in:
Pyrbu 2023-05-08 12:17:25 +01:00
parent 464b5716fa
commit 53ad7a73d6
29 changed files with 548 additions and 210 deletions

@ -6,6 +6,7 @@ plugins {
dependencies {
compileOnly "org.spigotmc:spigot-api:1.19.4-R0.1-SNAPSHOT"
compileOnly "net.kyori:adventure-platform-bukkit:4.3.0"
compileOnly "net.kyori:adventure-text-minimessage:4.13.1"
compileOnly "com.github.retrooper.packetevents:spigot:2.0.0-SNAPSHOT"
}

@ -1,45 +1,5 @@
package lol.pyr.znpcsplus.api.entity;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.HashMap;
import java.util.Map;
public class EntityProperty<T> {
private final String name;
private final T defaultValue;
public EntityProperty(String name) {
this(name, null);
}
public EntityProperty(String name, T defaultValue) {
this.name = name.toUpperCase();
this.defaultValue = defaultValue;
BY_NAME.put(this.name, this);
}
public String name() {
return name;
}
public T getDefaultValue() {
return defaultValue;
}
private final static Map<String, EntityProperty<?>> BY_NAME = new HashMap<>();
public static EntityProperty<?> getByName(String name) {
return BY_NAME.get(name.toUpperCase());
}
public static EntityProperty<Boolean> SKIN_LAYERS = new EntityProperty<>("skin_layers", true);
public static EntityProperty<SkinDescriptor> SKIN = new EntityProperty<>("skin");
public static EntityProperty<NamedTextColor> GLOW = new EntityProperty<>("glow");
public static EntityProperty<Boolean> FIRE = new EntityProperty<>("fire", false);
public static EntityProperty<Boolean> INVISIBLE = new EntityProperty<>("invisible", false);
public static EntityProperty<Boolean> SILENT = new EntityProperty<>("silent", false);
public static EntityProperty<Component> NAME = new EntityProperty<>("name");
public interface EntityProperty<T> {
T getDefaultValue();
}

@ -1,114 +1,4 @@
package lol.pyr.znpcsplus.api.npc;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import java.util.*;
public class NpcType {
private final static Map<String, NpcType> BY_NAME = new HashMap<>();
public static Collection<NpcType> values() {
return BY_NAME.values();
}
public static NpcType byName(String name) {
return BY_NAME.get(name.toUpperCase());
}
private final EntityType type;
private final Set<EntityProperty<?>> allowedProperties;
private final String name;
private final double hologramOffset;
private NpcType(String name, EntityType type, double hologramOffset, Set<EntityProperty<?>> allowedProperties) {
this.name = name.toUpperCase();
this.type = type;
this.hologramOffset = hologramOffset;
this.allowedProperties = allowedProperties;
}
public String getName() {
return name;
}
public EntityType getType() {
return type;
}
public double getHologramOffset() {
return hologramOffset;
}
public Set<EntityProperty<?>> getAllowedProperties() {
return allowedProperties;
}
private static NpcType define(Builder builder) {
return define(builder.build());
}
private static NpcType define(NpcType type) {
BY_NAME.put(type.getName(), type);
return type;
}
public static final NpcType PLAYER = define(
new Builder("player", EntityTypes.PLAYER)
.addProperties(EntityProperty.SKIN, EntityProperty.SKIN_LAYERS)
.setHologramOffset(-0.45D));
public static final NpcType CREEPER = define(
new Builder("creeper", EntityTypes.CREEPER)
.setHologramOffset(-0.6D));
public static final NpcType ZOMBIE = define(
new Builder("zombie", EntityTypes.ZOMBIE)
.setHologramOffset(-0.3D));
public static final NpcType SKELETON = define(
new Builder("skeleton", EntityTypes.SKELETON)
.setHologramOffset(-0.3D));
private static final class Builder {
private final String name;
private final EntityType type;
private final List<EntityProperty<?>> allowedProperties = new ArrayList<>();
private boolean globalProperties = true;
private double hologramOffset = 0;
private Builder(String name, EntityType type) {
this.name = name;
this.type = type;
}
public Builder addProperties(EntityProperty<?>... properties) {
allowedProperties.addAll(Arrays.asList(properties));
return this;
}
public Builder setEnableGlobalProperties(boolean enabled) {
globalProperties = enabled;
return this;
}
public Builder setHologramOffset(double hologramOffset) {
this.hologramOffset = hologramOffset;
return this;
}
public NpcType build() {
if (globalProperties) {
allowedProperties.add(EntityProperty.FIRE);
allowedProperties.add(EntityProperty.INVISIBLE);
allowedProperties.add(EntityProperty.SILENT);
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9))
allowedProperties.add(EntityProperty.GLOW);
}
return new NpcType(name, type, hologramOffset, new HashSet<>(allowedProperties));
}
}
public interface NpcType {
}

@ -7,8 +7,8 @@ import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder
import lol.pyr.director.adventure.command.CommandManager;
import lol.pyr.director.adventure.command.MultiCommand;
import lol.pyr.znpcsplus.api.ZApiProvider;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.npc.NpcType;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.npc.NpcTypeImpl;
import lol.pyr.znpcsplus.config.Configs;
import lol.pyr.znpcsplus.interaction.InteractionPacketListener;
import lol.pyr.znpcsplus.interaction.types.ConsoleCommandAction;
@ -143,15 +143,15 @@ public class ZNpcsPlus extends JavaPlugin {
int z = 0;
World world = Bukkit.getWorld("world");
if (world == null) world = Bukkit.getWorlds().get(0);
for (NpcType type : NpcType.values()) {
for (NpcTypeImpl type : NpcTypeImpl.values()) {
NpcEntryImpl entry = NpcRegistryImpl.get().create("debug_npc" + (z * wrap + x), world, type, new ZLocation(x * 3, 200, z * 3, 0, 0));
entry.setProcessed(true);
NpcImpl npc = entry.getNpc();
if (type.getType() == EntityTypes.PLAYER) {
SkinCache.fetchByName("Notch").thenAccept(skin -> npc.setProperty(EntityProperty.SKIN, new PrefetchedDescriptor(skin)));
npc.setProperty(EntityProperty.INVISIBLE, true);
SkinCache.fetchByName("Notch").thenAccept(skin -> npc.setProperty(EntityPropertyImpl.SKIN, new PrefetchedDescriptor(skin)));
npc.setProperty(EntityPropertyImpl.INVISIBLE, true);
}
npc.setProperty(EntityProperty.GLOW, NamedTextColor.RED);
npc.setProperty(EntityPropertyImpl.GLOW, NamedTextColor.RED);
// npc.setProperty(EntityProperty.FIRE, true);
npc.getHologram().addLine(Component.text("Hello, World!"));
if (x++ > wrap) {
@ -159,16 +159,16 @@ public class ZNpcsPlus extends JavaPlugin {
z++;
}
}
NpcEntryImpl entry = NpcRegistryImpl.get().create("debug_npc" + (z * wrap + x), world, NpcType.byName("player"), new ZLocation(x * 3, 200, z * 3, 0, 0));
NpcEntryImpl entry = NpcRegistryImpl.get().create("debug_npc" + (z * wrap + x), world, NpcTypeImpl.byName("player"), new ZLocation(x * 3, 200, z * 3, 0, 0));
entry.setProcessed(true);
NpcImpl npc = entry.getNpc();
npc.setProperty(EntityProperty.SKIN, new FetchingDescriptor("jeb_"));
npc.setProperty(EntityPropertyImpl.SKIN, new FetchingDescriptor("jeb_"));
npc.addAction(new MessageAction(1000L, "<red>Hi, I'm jeb!"));
x++;
entry = NpcRegistryImpl.get().create("debug_npc" + (z * wrap + x), world, NpcType.byName("player"), new ZLocation(x * 3, 200, z * 3, 0, 0));
entry = NpcRegistryImpl.get().create("debug_npc" + (z * wrap + x), world, NpcTypeImpl.byName("player"), new ZLocation(x * 3, 200, z * 3, 0, 0));
entry.setProcessed(true);
npc = entry.getNpc();
npc.setProperty(EntityProperty.SKIN, new MirrorDescriptor());
npc.setProperty(EntityPropertyImpl.SKIN, new MirrorDescriptor());
npc.addAction(new ConsoleCommandAction(1000L, "kick {player}"));
}
}

@ -12,24 +12,24 @@ package lol.pyr.znpcsplus.config;
* TOO_FEW_ARGUMENTS("messages", "&cThis command does not contain enough arguments. Type &f/znpcs&c or view our documentation for a list/examples of existing arguments.", String.class),
* PATH_START("messages", "&aSuccess! Move to create a path for your NPC. When finished, type &f/znpcs path exit&c to exit path creation.", String.class),
* EXIT_PATH("messages", "&cYou have exited path creation.", String.class),
* PATH_FOUND("messages", "&cThere is already a path with this name.", String.class),
* PATH_FOUND("messages", "&cThere is already a path with this getName.", String.class),
* NPC_FOUND("messages", "&cThere is already an NPC with this ID.", String.class),
* NO_PATH_FOUND("messages", "&cThe path you have specified does not exist.", String.class),
* NO_SKIN_FOUND("messages", "&cThe skin username/URL you have specified does not exist or is invalid.", String.class),
* NO_NPC_FOUND("messages", "&cThe NPC you have specified does not exist.", String.class),
* NO_ACTION_FOUND("messages", "&cThis action does not exist! Type &f/znpcs&c or view our documentation for a list/examples of existing action types.", String.class),
* METHOD_NOT_FOUND("messages", "&cThis method does not exist! Type &f/znpcs&c or view our documentation for a list/examples of existing methods.", String.class),
* INVALID_NAME_LENGTH("messages", "&cThe name you specified either too short or long. Please enter a positive integer of (3 to 16) characters.", String.class),
* INVALID_NAME_LENGTH("messages", "&cThe getName you specified either too short or long. Please enter a positive integer of (3 to 16) characters.", String.class),
* UNSUPPORTED_ENTITY("messages", "&cThis entity type not available in your current server version.", String.class),
* PATH_SET_INCORRECT_USAGE("messages", "&eUsage: &aset <npc_id> <path_name>", String.class),
* ACTION_ADD_INCORRECT_USAGE("messages", "&eUsage: &a<SERVER/CMD/MESSAGE/CONSOLE> <actionValue>", String.class),
* ACTION_DELAY_INCORRECT_USAGE("messages", "&eUsage: &a<action_id> <delay>", String.class),
* CONVERSATION_SET_INCORRECT_USAGE("messages", "&cUsage: <npc_id> <conversation_name> <RADIUS/CLICK>", String.class),
* NO_CONVERSATION_FOUND("messages", "&cThe conversation you have specified does not exist!", String.class),
* CONVERSATION_FOUND("messages", "&cThere is already a conversation with this name.", String.class),
* CONVERSATION_FOUND("messages", "&cThere is already a conversation with this getName.", String.class),
* INVALID_SIZE("messages", "&cThe position you have specified cannot exceed the limit.", String.class),
* FETCHING_SKIN("messages", "&aFetching skin for name: &f%s&a. Please wait...", String.class),
* CANT_GET_SKIN("messages", "&cCould not fetch skin for name: %s.", String.class),
* FETCHING_SKIN("messages", "&aFetching skin for getName: &f%s&a. Please wait...", String.class),
* CANT_GET_SKIN("messages", "&cCould not fetch skin for getName: %s.", String.class),
* GET_SKIN("messages", "&aSkin successfully fetched!", String.class),
* NOT_SUPPORTED_NPC_TYPE("messages", "&cThis NPC type doesn't exists or is not supported in your current server version.", String.class);
*/

@ -0,0 +1,90 @@
package lol.pyr.znpcsplus.entity;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import java.util.HashMap;
import java.util.Map;
public class EntityPropertyImpl<T> implements EntityProperty<T> {
private final String name;
private final T defaultValue;
private final PropertySerializer<T> serializer;
private final PropertyDeserializer<T> deserializer;
public EntityPropertyImpl(String name, PropertySerializer<T> serializer, PropertyDeserializer<T> deserializer) {
this(name, null, serializer, deserializer);
}
public EntityPropertyImpl(String name, T defaultValue, PropertySerializer<T> serializer, PropertyDeserializer<T> deserializer) {
this.name = name.toUpperCase();
this.defaultValue = defaultValue;
this.serializer = serializer;
this.deserializer = deserializer;
BY_NAME.put(this.name, this);
}
public String getName() {
return name;
}
public String serialize(PropertyHolder holder) {
return serialize(holder.getProperty(this));
}
public String serialize(T value) {
return serializer.serialize(value);
}
public T deserialize(String str) {
return deserializer.deserialize(str);
}
@Override
public T getDefaultValue() {
return defaultValue;
}
private final static Map<String, EntityPropertyImpl<?>> BY_NAME = new HashMap<>();
public static EntityPropertyImpl<?> getByName(String name) {
return BY_NAME.get(name.toUpperCase());
}
@FunctionalInterface
private interface PropertySerializer<T> {
String serialize(T property);
}
@FunctionalInterface
private interface PropertyDeserializer<T> {
T deserialize(String property);
}
private final static PropertySerializer<Boolean> BOOLEAN_SERIALIZER = Object::toString;
private final static PropertyDeserializer<Boolean> BOOLEAN_DESERIALIZER = Boolean::valueOf;
private final static PropertySerializer<NamedTextColor> COLOR_SERIALIZER = color -> String.valueOf(color.value());
private final static PropertyDeserializer<NamedTextColor> COLOR_DESERIALIZER = str -> NamedTextColor.namedColor(Integer.parseInt(str));
private final static PropertySerializer<Component> COMPONENT_SERIALIZER = component -> MiniMessage.miniMessage().serialize(component);
private final static PropertyDeserializer<Component> COMPONENT_DESERIALIZER = str -> MiniMessage.miniMessage().deserialize(str);
private final static PropertySerializer<SkinDescriptor> DESCRIPTOR_SERIALIZER = descriptor -> ((BaseSkinDescriptor) descriptor).serialize();
private final static PropertyDeserializer<SkinDescriptor> DESCRIPTOR_DESERIALIZER = BaseSkinDescriptor::deserialize;
public static EntityPropertyImpl<Boolean> SKIN_LAYERS = new EntityPropertyImpl<>("skin_layers", true, BOOLEAN_SERIALIZER, BOOLEAN_DESERIALIZER);
public static EntityPropertyImpl<SkinDescriptor> SKIN = new EntityPropertyImpl<>("skin", DESCRIPTOR_SERIALIZER, DESCRIPTOR_DESERIALIZER);
public static EntityPropertyImpl<NamedTextColor> GLOW = new EntityPropertyImpl<>("glow", COLOR_SERIALIZER, COLOR_DESERIALIZER);
public static EntityPropertyImpl<Boolean> FIRE = new EntityPropertyImpl<>("fire", false, BOOLEAN_SERIALIZER, BOOLEAN_DESERIALIZER);
public static EntityPropertyImpl<Boolean> INVISIBLE = new EntityPropertyImpl<>("invisible", false, BOOLEAN_SERIALIZER, BOOLEAN_DESERIALIZER);
public static EntityPropertyImpl<Boolean> SILENT = new EntityPropertyImpl<>("silent", false, BOOLEAN_SERIALIZER, BOOLEAN_DESERIALIZER);
public static EntityPropertyImpl<Component> NAME = new EntityPropertyImpl<>("name", COMPONENT_SERIALIZER, COMPONENT_DESERIALIZER);
}

@ -36,6 +36,10 @@ public class HologramImpl extends Viewable implements Hologram {
relocateLines();
}
public List<HologramLine> getLines() {
return Collections.unmodifiableList(lines);
}
public void clearLines() {
UNSAFE_hideAll();
lines.clear();

@ -1,8 +1,9 @@
package lol.pyr.znpcsplus.hologram;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.util.ZLocation;
import net.kyori.adventure.text.Component;
@ -42,13 +43,13 @@ public class HologramLine implements PropertyHolder {
@SuppressWarnings("unchecked")
@Override
public <T> T getProperty(EntityProperty<T> key) {
if (key == EntityProperty.INVISIBLE) return (T) Boolean.TRUE;
if (key == EntityProperty.NAME) return (T) text;
if (key == EntityPropertyImpl.INVISIBLE) return (T) Boolean.TRUE;
if (key == EntityPropertyImpl.NAME) return (T) text;
return key.getDefaultValue();
}
@Override
public boolean hasProperty(EntityProperty<?> key) {
return key == EntityProperty.NAME || key == EntityProperty.INVISIBLE;
return key == EntityPropertyImpl.NAME || key == EntityPropertyImpl.INVISIBLE;
}
}

@ -23,5 +23,11 @@ public abstract class NpcAction {
return delay;
}
public abstract void run(Player player);
public String getArgument() {
return argument;
}
public abstract void run(Player player);
public abstract NpcActionType getType();
}

@ -2,6 +2,7 @@ package lol.pyr.znpcsplus.interaction.types;
import lol.pyr.znpcsplus.ZNpcsPlus;
import lol.pyr.znpcsplus.interaction.NpcAction;
import lol.pyr.znpcsplus.interaction.NpcActionType;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@ -16,4 +17,9 @@ public class ConsoleCommandAction extends NpcAction {
String cmd = argument.replace("{player}", player.getName()).replace("{uuid}", player.getUniqueId().toString());
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), ZNpcsPlus.PLACEHOLDERS_SUPPORTED ? PlaceholderAPI.setPlaceholders(player, cmd) : cmd);
}
@Override
public NpcActionType getType() {
return NpcActionType.CONSOLE_CMD;
}
}

@ -2,6 +2,7 @@ package lol.pyr.znpcsplus.interaction.types;
import lol.pyr.znpcsplus.ZNpcsPlus;
import lol.pyr.znpcsplus.interaction.NpcAction;
import lol.pyr.znpcsplus.interaction.NpcActionType;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.entity.Player;
@ -18,4 +19,9 @@ public class MessageAction extends NpcAction {
public void run(Player player) {
ZNpcsPlus.ADVENTURE.player(player).sendMessage(message);
}
@Override
public NpcActionType getType() {
return NpcActionType.MESSAGE;
}
}

@ -2,6 +2,7 @@ package lol.pyr.znpcsplus.interaction.types;
import lol.pyr.znpcsplus.ZNpcsPlus;
import lol.pyr.znpcsplus.interaction.NpcAction;
import lol.pyr.znpcsplus.interaction.NpcActionType;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@ -16,4 +17,9 @@ public class PlayerCommandAction extends NpcAction {
String cmd = argument.replace("{player}", player.getName()).replace("{uuid}", player.getUniqueId().toString());
Bukkit.dispatchCommand(player, ZNpcsPlus.PLACEHOLDERS_SUPPORTED ? PlaceholderAPI.setPlaceholders(player, cmd) : cmd);
}
@Override
public NpcActionType getType() {
return NpcActionType.PLAYER_CMD;
}
}

@ -2,6 +2,7 @@ package lol.pyr.znpcsplus.interaction.types;
import lol.pyr.znpcsplus.ZNpcsPlus;
import lol.pyr.znpcsplus.interaction.NpcAction;
import lol.pyr.znpcsplus.interaction.NpcActionType;
import org.bukkit.entity.Player;
public class SwitchServerAction extends NpcAction {
@ -13,4 +14,9 @@ public class SwitchServerAction extends NpcAction {
public void run(Player player) {
ZNpcsPlus.BUNGEE_UTILS.sendPlayerToServer(player, argument);
}
@Override
public NpcActionType getType() {
return NpcActionType.SERVER;
}
}

@ -3,13 +3,15 @@ package lol.pyr.znpcsplus.npc;
import lol.pyr.znpcsplus.api.npc.NpcEntry;
public class NpcEntryImpl implements NpcEntry {
private final String id;
private final NpcImpl npc;
private boolean process = false;
private boolean save = false;
private boolean modify = false;
public NpcEntryImpl(NpcImpl npc) {
public NpcEntryImpl(String id, NpcImpl npc) {
this.id = id.toLowerCase();
this.npc = npc;
}
@ -54,4 +56,8 @@ public class NpcEntryImpl implements NpcEntry {
setProcessed(true);
setAllowCommandModification(true);
}
public String getId() {
return id;
}
}

@ -1,8 +1,8 @@
package lol.pyr.znpcsplus.npc;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.api.npc.NpcType;
import lol.pyr.znpcsplus.api.npc.Npc;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.hologram.HologramImpl;
import lol.pyr.znpcsplus.interaction.NpcAction;
@ -19,28 +19,33 @@ public class NpcImpl extends Viewable implements Npc {
private final String worldName;
private PacketEntity entity;
private ZLocation location;
private NpcType type;
private NpcTypeImpl type;
private final HologramImpl hologram;
private final Map<EntityProperty<?>, Object> propertyMap = new HashMap<>();
private final Map<EntityPropertyImpl<?>, Object> propertyMap = new HashMap<>();
private final Set<NpcAction> actions = new HashSet<>();
protected NpcImpl(World world, NpcType type, ZLocation location) {
this.worldName = world.getName();
protected NpcImpl(World world, NpcTypeImpl type, ZLocation location) {
this(world.getName(), type, location);
}
public NpcImpl(String world, NpcTypeImpl type, ZLocation location) {
this.worldName = world;
this.type = type;
this.location = location;
entity = new PacketEntity(this, type.getType(), location);
hologram = new HologramImpl(location.withY(location.getY() + type.getHologramOffset()));
}
public void setType(NpcType type) {
public void setType(NpcTypeImpl type) {
UNSAFE_hideAll();
this.type = type;
entity = new PacketEntity(this, type.getType(), entity.getLocation());
UNSAFE_showAll();
}
public NpcType getType() {
public NpcTypeImpl getType() {
return type;
}
@ -66,6 +71,10 @@ public class NpcImpl extends Viewable implements Npc {
return Bukkit.getWorld(worldName);
}
public String getWorldName() {
return worldName;
}
@Override
protected void _show(Player player) {
entity.spawn(player);
@ -84,14 +93,14 @@ public class NpcImpl extends Viewable implements Npc {
@SuppressWarnings("unchecked")
public <T> T getProperty(EntityProperty<T> key) {
return hasProperty(key) ? (T) propertyMap.get(key) : key.getDefaultValue();
return hasProperty(key) ? (T) propertyMap.get((EntityPropertyImpl<?>) key) : key.getDefaultValue();
}
public boolean hasProperty(EntityProperty<?> key) {
return propertyMap.containsKey(key);
return propertyMap.containsKey((EntityPropertyImpl<?>) key);
}
public <T> void setProperty(EntityProperty<T> key, T value) {
public <T> void setProperty(EntityPropertyImpl<T> key, T value) {
if (value.equals(key.getDefaultValue())) removeProperty(key);
else {
propertyMap.put(key, value);
@ -99,11 +108,15 @@ public class NpcImpl extends Viewable implements Npc {
}
}
public void removeProperty(EntityProperty<?> key) {
public void removeProperty(EntityPropertyImpl<?> key) {
propertyMap.remove(key);
_refreshMeta();
}
public Set<EntityPropertyImpl<?>> getAppliedProperties() {
return Collections.unmodifiableSet(propertyMap.keySet());
}
public Collection<NpcAction> getActions() {
return Collections.unmodifiableSet(actions);
}

@ -24,7 +24,7 @@ public class NpcRegistryImpl implements NpcRegistry {
private final Map<String, NpcEntryImpl> npcMap = new HashMap<>();
public NpcEntryImpl get(String id) {
return npcMap.get(id.toUpperCase());
return npcMap.get(id.toLowerCase());
}
public Collection<NpcEntryImpl> all() {
@ -39,19 +39,22 @@ public class NpcRegistryImpl implements NpcRegistry {
return Collections.unmodifiableSet(npcMap.keySet());
}
@Override
public NpcEntryImpl create(String id, World world, NpcType type, ZLocation location) {
id = id.toUpperCase();
return create(id, world, (NpcTypeImpl) type, location);
}
public NpcEntryImpl create(String id, World world, NpcTypeImpl type, ZLocation location) {
id = id.toLowerCase();
if (npcMap.containsKey(id)) throw new IllegalArgumentException("An npc with the id " + id + " already exists!");
NpcImpl npc = new NpcImpl(world, type, location);
NpcEntryImpl entry = new NpcEntryImpl(npc);
NpcEntryImpl entry = new NpcEntryImpl(id, npc);
npcMap.put(id, entry);
return entry;
}
@Override
public void delete(String id) {
id = id.toUpperCase();
id = id.toLowerCase();
if (!npcMap.containsKey(id)) return;
npcMap.remove(id).getNpc().delete();
}

@ -0,0 +1,114 @@
package lol.pyr.znpcsplus.npc;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import java.util.*;
public class NpcTypeImpl {
private final static Map<String, NpcTypeImpl> BY_NAME = new HashMap<>();
public static Collection<NpcTypeImpl> values() {
return BY_NAME.values();
}
public static NpcTypeImpl byName(String name) {
return BY_NAME.get(name.toUpperCase());
}
private final EntityType type;
private final Set<EntityPropertyImpl<?>> allowedProperties;
private final String name;
private final double hologramOffset;
private NpcTypeImpl(String name, EntityType type, double hologramOffset, Set<EntityPropertyImpl<?>> allowedProperties) {
this.name = name.toUpperCase();
this.type = type;
this.hologramOffset = hologramOffset;
this.allowedProperties = allowedProperties;
}
public String getName() {
return name;
}
public EntityType getType() {
return type;
}
public double getHologramOffset() {
return hologramOffset;
}
public Set<EntityPropertyImpl<?>> getAllowedProperties() {
return allowedProperties;
}
private static NpcTypeImpl define(Builder builder) {
return define(builder.build());
}
private static NpcTypeImpl define(NpcTypeImpl type) {
BY_NAME.put(type.getName(), type);
return type;
}
public static final NpcTypeImpl PLAYER = define(
new Builder("player", EntityTypes.PLAYER)
.addProperties(EntityPropertyImpl.SKIN, EntityPropertyImpl.SKIN_LAYERS)
.setHologramOffset(-0.45D));
public static final NpcTypeImpl CREEPER = define(
new Builder("creeper", EntityTypes.CREEPER)
.setHologramOffset(-0.6D));
public static final NpcTypeImpl ZOMBIE = define(
new Builder("zombie", EntityTypes.ZOMBIE)
.setHologramOffset(-0.3D));
public static final NpcTypeImpl SKELETON = define(
new Builder("skeleton", EntityTypes.SKELETON)
.setHologramOffset(-0.3D));
private static final class Builder {
private final String name;
private final EntityType type;
private final List<EntityPropertyImpl<?>> allowedProperties = new ArrayList<>();
private boolean globalProperties = true;
private double hologramOffset = 0;
private Builder(String name, EntityType type) {
this.name = name;
this.type = type;
}
public Builder addProperties(EntityPropertyImpl<?>... properties) {
allowedProperties.addAll(Arrays.asList(properties));
return this;
}
public Builder setEnableGlobalProperties(boolean enabled) {
globalProperties = enabled;
return this;
}
public Builder setHologramOffset(double hologramOffset) {
this.hologramOffset = hologramOffset;
return this;
}
public NpcTypeImpl build() {
if (globalProperties) {
allowedProperties.add(EntityPropertyImpl.FIRE);
allowedProperties.add(EntityPropertyImpl.INVISIBLE);
allowedProperties.add(EntityPropertyImpl.SILENT);
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9))
allowedProperties.add(EntityPropertyImpl.GLOW);
}
return new NpcTypeImpl(name, type, hologramOffset, new HashSet<>(allowedProperties));
}
}
}

@ -3,7 +3,7 @@ package lol.pyr.znpcsplus.packets;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSpawnEntity;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.util.ZLocation;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import org.bukkit.entity.Player;
@ -15,7 +15,7 @@ public class V1_14Factory extends V1_9Factory {
ZLocation location = entity.getLocation();
sendPacket(player, new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(),
location.toVector3d(), location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.empty()));
if (properties.hasProperty(EntityProperty.GLOW)) createTeam(player, entity, properties);
if (properties.hasProperty(EntityPropertyImpl.GLOW)) createTeam(player, entity, properties);
sendAllMetadata(player, entity, properties);
}
}

@ -12,7 +12,7 @@ import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import com.github.retrooper.packetevents.wrapper.play.server.*;
import lol.pyr.znpcsplus.ZNpcsPlus;
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.metadata.MetadataFactory;
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
@ -94,7 +94,7 @@ public class V1_8Factory implements PacketFactory {
Component.empty(), Component.empty(), Component.empty(),
WrapperPlayServerTeams.NameTagVisibility.NEVER,
WrapperPlayServerTeams.CollisionRule.NEVER,
properties.hasProperty(EntityProperty.GLOW) ? properties.getProperty(EntityProperty.GLOW) : NamedTextColor.WHITE,
properties.hasProperty(EntityPropertyImpl.GLOW) ? properties.getProperty(EntityPropertyImpl.GLOW) : NamedTextColor.WHITE,
WrapperPlayServerTeams.OptionData.NONE
)));
sendPacket(player, new WrapperPlayServerTeams("npc_team_" + entity.getEntityId(), WrapperPlayServerTeams.TeamMode.ADD_ENTITIES, (WrapperPlayServerTeams.ScoreBoardTeamInfo) null,
@ -109,12 +109,12 @@ public class V1_8Factory implements PacketFactory {
@Override
public void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties) {
ArrayList<EntityData> data = new ArrayList<>();
if (entity.getType() == EntityTypes.PLAYER && properties.getProperty(EntityProperty.SKIN_LAYERS)) data.add(MetadataFactory.get().skinLayers());
boolean fire = properties.getProperty(EntityProperty.FIRE);
boolean invisible = properties.getProperty(EntityProperty.INVISIBLE);
if (entity.getType() == EntityTypes.PLAYER && properties.getProperty(EntityPropertyImpl.SKIN_LAYERS)) data.add(MetadataFactory.get().skinLayers());
boolean fire = properties.getProperty(EntityPropertyImpl.FIRE);
boolean invisible = properties.getProperty(EntityPropertyImpl.INVISIBLE);
if (fire || invisible) data.add(MetadataFactory.get().effects(fire, false, invisible));
if (properties.getProperty(EntityProperty.SILENT)) data.add(MetadataFactory.get().silent());
if (properties.hasProperty(EntityProperty.NAME)) data.addAll(MetadataFactory.get().name(properties.getProperty(EntityProperty.NAME)));
if (properties.getProperty(EntityPropertyImpl.SILENT)) data.add(MetadataFactory.get().silent());
if (properties.hasProperty(EntityPropertyImpl.NAME)) data.addAll(MetadataFactory.get().name(properties.getProperty(EntityPropertyImpl.NAME)));
sendMetadata(player, entity, data);
}
@ -128,8 +128,8 @@ public class V1_8Factory implements PacketFactory {
}
protected CompletableFuture<UserProfile> skinned(Player player, PropertyHolder properties, UserProfile profile) {
if (!properties.hasProperty(EntityProperty.SKIN)) return CompletableFuture.completedFuture(profile);
BaseSkinDescriptor descriptor = (BaseSkinDescriptor) properties.getProperty(EntityProperty.SKIN);
if (!properties.hasProperty(EntityPropertyImpl.SKIN)) return CompletableFuture.completedFuture(profile);
BaseSkinDescriptor descriptor = (BaseSkinDescriptor) properties.getProperty(EntityPropertyImpl.SKIN);
if (descriptor.supportsInstant(player)) {
descriptor.fetchInstant(player).apply(profile);
return CompletableFuture.completedFuture(profile);

@ -4,7 +4,7 @@ import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.metadata.MetadataFactory;
import lol.pyr.znpcsplus.api.entity.EntityProperty;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import org.bukkit.entity.Player;
@ -14,13 +14,13 @@ public class V1_9Factory extends V1_8Factory {
@Override
public void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties) {
ArrayList<EntityData> data = new ArrayList<>();
if (entity.getType() == EntityTypes.PLAYER && properties.getProperty(EntityProperty.SKIN_LAYERS)) data.add(MetadataFactory.get().skinLayers());
boolean glow = properties.hasProperty(EntityProperty.GLOW);
boolean fire = properties.getProperty(EntityProperty.FIRE);
boolean invisible = properties.getProperty(EntityProperty.INVISIBLE);
if (entity.getType() == EntityTypes.PLAYER && properties.getProperty(EntityPropertyImpl.SKIN_LAYERS)) data.add(MetadataFactory.get().skinLayers());
boolean glow = properties.hasProperty(EntityPropertyImpl.GLOW);
boolean fire = properties.getProperty(EntityPropertyImpl.FIRE);
boolean invisible = properties.getProperty(EntityPropertyImpl.INVISIBLE);
if (glow || fire || invisible) data.add(MetadataFactory.get().effects(fire, glow, invisible));
if (properties.getProperty(EntityProperty.SILENT)) data.add(MetadataFactory.get().silent());
if (properties.hasProperty(EntityProperty.NAME)) data.addAll(MetadataFactory.get().name(properties.getProperty(EntityProperty.NAME)));
if (properties.getProperty(EntityPropertyImpl.SILENT)) data.add(MetadataFactory.get().silent());
if (properties.hasProperty(EntityPropertyImpl.NAME)) data.addAll(MetadataFactory.get().name(properties.getProperty(EntityPropertyImpl.NAME)));
sendMetadata(player, entity, data);
}
}

@ -1,11 +1,33 @@
package lol.pyr.znpcsplus.skin;
import com.github.retrooper.packetevents.protocol.player.TextureProperty;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.FetchingDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface BaseSkinDescriptor {
public interface BaseSkinDescriptor extends SkinDescriptor {
CompletableFuture<Skin> fetch(Player player);
Skin fetchInstant(Player player);
boolean supportsInstant(Player player);
String serialize();
static BaseSkinDescriptor deserialize(String str) {
String[] arr = str.split(";");
if (arr[0].equalsIgnoreCase("mirror")) return new MirrorDescriptor();
else if (arr[0].equalsIgnoreCase("fetching")) return new FetchingDescriptor(arr[1]);
else if (arr[0].equalsIgnoreCase("prefetched")) {
List<TextureProperty> properties = new ArrayList<>();
for (int i = 0; i < (arr.length - 1) / 3; i++) {
properties.add(new TextureProperty(arr[i + 1], arr[i + 2], arr[i + 3]));
}
return new PrefetchedDescriptor(new Skin(properties));
}
throw new IllegalArgumentException("Unknown SkinDescriptor type!");
}
}

@ -8,6 +8,7 @@ import com.mojang.authlib.properties.PropertyMap;
import lol.pyr.znpcsplus.util.list.ListUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@ -23,6 +24,10 @@ public class Skin {
this.properties.addAll(ListUtil.immutableList(properties));
}
public Skin(Collection<TextureProperty> properties) {
this.properties.addAll(properties);
}
public Skin(PropertyMap properties) {
this.properties.addAll(properties.values().stream()
.map(property -> new TextureProperty(property.getName(), property.getValue(), property.getSignature()))
@ -32,7 +37,7 @@ public class Skin {
public Skin(JsonObject obj) {
for (JsonElement e : obj.get("properties").getAsJsonArray()) {
JsonObject o = e.getAsJsonObject();
properties.add(new TextureProperty(o.get("name").getAsString(), o.get("value").getAsString(), o.has("signature") ? o.get("signature").getAsString() : null));
properties.add(new TextureProperty(o.get("getName").getAsString(), o.get("value").getAsString(), o.has("signature") ? o.get("signature").getAsString() : null));
}
}
@ -41,6 +46,10 @@ public class Skin {
return profile;
}
public List<TextureProperty> getProperties() {
return properties;
}
public boolean isExpired() {
return System.currentTimeMillis() - timestamp > 60000L;
}

@ -36,4 +36,13 @@ public class FetchingDescriptor implements BaseSkinDescriptor, SkinDescriptor {
if (ZNpcsPlus.PLACEHOLDERS_SUPPORTED) return PlaceholderAPI.setPlaceholders(player, name);
return name;
}
public String getName() {
return name;
}
@Override
public String serialize() {
return "fetching;" + name;
}
}

@ -26,4 +26,9 @@ public class MirrorDescriptor implements BaseSkinDescriptor, SkinDescriptor {
public boolean supportsInstant(Player player) {
return true;
}
@Override
public String serialize() {
return "mirror";
}
}

@ -1,5 +1,6 @@
package lol.pyr.znpcsplus.skin.descriptor;
import com.github.retrooper.packetevents.protocol.player.TextureProperty;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.skin.BaseSkinDescriptor;
import lol.pyr.znpcsplus.skin.Skin;
@ -28,4 +29,20 @@ public class PrefetchedDescriptor implements BaseSkinDescriptor, SkinDescriptor
public boolean supportsInstant(Player player) {
return true;
}
public Skin getSkin() {
return skin;
}
@Override
public String serialize() {
StringBuilder sb = new StringBuilder();
sb.append("prefetched;");
for (TextureProperty property : skin.getProperties()) {
sb.append(property.getName()).append(";");
sb.append(property.getValue()).append(";");
sb.append(property.getSignature()).append(";");
}
return sb.toString();
}
}

@ -0,0 +1,10 @@
package lol.pyr.znpcsplus.storage;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import java.util.Collection;
public interface NpcStorage {
Collection<NpcEntryImpl> loadNpcs();
void saveNpcs(Collection<NpcEntryImpl> npcs);
}

@ -0,0 +1,14 @@
package lol.pyr.znpcsplus.storage;
import lol.pyr.znpcsplus.storage.yaml.YamlStorage;
public enum NpcStorageType {
YAML {
@Override
public NpcStorage create() {
return new YamlStorage();
}
};
public abstract NpcStorage create();
}

@ -0,0 +1,7 @@
package lol.pyr.znpcsplus.storage;
public class SerializationException extends RuntimeException {
public SerializationException(String message) {
super(message);
}
}

@ -0,0 +1,133 @@
package lol.pyr.znpcsplus.storage.yaml;
import lol.pyr.znpcsplus.ZNpcsPlus;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.hologram.HologramLine;
import lol.pyr.znpcsplus.interaction.NpcAction;
import lol.pyr.znpcsplus.interaction.NpcActionType;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.npc.NpcTypeImpl;
import lol.pyr.znpcsplus.storage.NpcStorage;
import lol.pyr.znpcsplus.util.ZLocation;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class YamlStorage implements NpcStorage {
private final File npcsFolder;
public YamlStorage() {
npcsFolder = new File(ZNpcsPlus.PLUGIN_FOLDER, "npcs");
if (npcsFolder.exists()) npcsFolder.mkdirs();
}
@SuppressWarnings("ConstantConditions")
@Override
public Collection<NpcEntryImpl> loadNpcs() {
File[] files = npcsFolder.listFiles();
if (files == null || files.length == 0) return Collections.emptyList();
List<NpcEntryImpl> npcs = new ArrayList<>();
for (File file : files) if (file.isFile() && file.getName().toLowerCase().endsWith(".yml")) {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
NpcImpl npc = new NpcImpl(config.getString("world"), NpcTypeImpl.byName(config.getString("type")),
deserializeLocation(config.getConfigurationSection("location")));
ConfigurationSection properties = config.getConfigurationSection("properties");
for (String key : properties.getKeys(false)) {
EntityPropertyImpl<?> property = EntityPropertyImpl.getByName(key);
_setProperty(npc, property, property.deserialize(properties.getString(key)));
}
for (String line : config.getStringList("hologram")) {
npc.getHologram().addLine(MiniMessage.miniMessage().deserialize(line));
}
int amt = config.getInt("action-amount");
for (int i = 1; i <= amt; i++) {
String key = "actions." + i;
npc.addAction(NpcActionType.valueOf(config.getString(key + ".type"))
.deserialize(config.getInt(key + ".cooldown"), config.getString(key + ".argument")));
}
NpcEntryImpl entry = new NpcEntryImpl(config.getString("id"), npc);
entry.setProcessed(config.getBoolean("is-processed"));
entry.setAllowCommandModification(config.getBoolean("allow-commands"));
entry.setSave(true);
npcs.add(entry);
}
return npcs;
}
@SuppressWarnings("unchecked")
private <T> void _setProperty(NpcImpl npc, EntityPropertyImpl<?> property, Object value) {
npc.setProperty((EntityPropertyImpl<T>) property, (T) value);
}
@Override
public void saveNpcs(Collection<NpcEntryImpl> npcs) {
for (NpcEntryImpl entry : npcs) if (entry.isSave()) try {
YamlConfiguration config = new YamlConfiguration();
config.set("id", entry.getId());
config.set("is-processed", entry.isProcessed());
config.set("allow-commands", entry.isAllowCommandModification());
NpcImpl npc = entry.getNpc();
config.set("world", npc.getWorldName());
config.set("location", serializeLocation(npc.getLocation()));
config.set("type", npc.getType().getName());
for (EntityPropertyImpl<?> property : npc.getAppliedProperties()) {
config.set("properties." + property.getName(), property.serialize(npc));
}
List<String> lines = new ArrayList<>();
for (HologramLine line : npc.getHologram().getLines()) {
lines.add(MiniMessage.miniMessage().serialize(line.getText()));
}
config.set("hologram", lines);
int i = 0;
for (NpcAction action : npc.getActions()) { i++;
String key = "actions." + i;
config.set(key + ".type", action.getType().name());
config.set(key + ".cooldown", action.getCooldown());
config.set(key + ".argument", action.getArgument());
}
config.set("action-amount", i);
config.save(new File(entry.getId() + ".yml"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public ZLocation deserializeLocation(ConfigurationSection section) {
return new ZLocation(
section.getDouble("x"),
section.getDouble("y"),
section.getDouble("z"),
(float) section.getDouble("yaw"),
(float) section.getDouble("pitch")
);
}
public YamlConfiguration serializeLocation(ZLocation location) {
YamlConfiguration config = new YamlConfiguration();
config.set("x", location.getX());
config.set("y", location.getY());
config.set("z", location.getZ());
config.set("yaw", location.getYaw());
config.set("pitch", location.getPitch());
return config;
}
}