Compare commits

..

8 Commits

Author SHA1 Message Date
D3v1s0m
2813a92062 added player knockback properties
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-05 20:01:33 +02:00
D3v1s0m
64d6fc900b fix client side desync when interacting with allays 2024-05-05 20:01:33 +02:00
D3v1s0m
7607541583 villager_profession property for 1.13 and below 2024-05-05 20:01:33 +02:00
D3v1s0m
cc73738ac1 fix baby property for 1.8 2024-05-05 20:01:33 +02:00
D3v1s0m
0dd8284f31 fix dinnerbone property for 1.12 and below 2024-05-05 20:01:33 +02:00
D3v1s0m
bde20e57ef added camel_sitting property 2024-05-05 20:01:33 +02:00
D3v1s0m
cb96dd90cd support for FancyNpcs conversion 2024-05-05 20:01:33 +02:00
D3v1s0m
a56459b69e fix UpdateChecker 2024-05-05 20:01:33 +02:00
13 changed files with 378 additions and 30 deletions

@ -1,29 +1,40 @@
package lol.pyr.znpcsplus.util;
public enum VillagerProfession {
NONE(0),
ARMORER(1),
BUTCHER(2),
CARTOGRAPHER(3),
CLERIC(4),
FARMER(5),
FISHERMAN(6),
FLETCHER(7),
LEATHER_WORKER(8),
LIBRARIAN(9),
NONE(0, 0),
ARMORER(1, 3),
BUTCHER(2, 4),
CARTOGRAPHER(3, 1),
CLERIC(4, 2),
FARMER(5, 0),
FISHERMAN(6, 0),
FLETCHER(7, 0),
LEATHER_WORKER(8, 4),
LIBRARIAN(9, 1),
MASON(10),
NITWIT(11),
SHEPHERD(12),
TOOL_SMITH(13),
WEAPON_SMITH(14);
NITWIT(11, 5),
SHEPHERD(12, 0),
TOOL_SMITH(13, 3),
WEAPON_SMITH(14, 3);
private final int id;
private final int legacyId;
VillagerProfession(int id) {
this.id = id;
this.legacyId = 0;
}
VillagerProfession(int id, int legacyId) {
this.id = id;
this.legacyId = legacyId;
}
public int getId() {
return id;
}
public int getLegacyId() {
return legacyId;
}
}

@ -138,7 +138,7 @@ public class ZNpcsPlus {
typeRegistry.registerDefault(packetEvents, propertyRegistry);
actionRegistry.registerTypes(scheduler, adventure, textSerializer, bungeeConnector);
packetEvents.getEventManager().registerListener(new InteractionPacketListener(userManager, npcRegistry, scheduler), PacketListenerPriority.MONITOR);
packetEvents.getEventManager().registerListener(new InteractionPacketListener(userManager, npcRegistry, typeRegistry, scheduler), PacketListenerPriority.MONITOR);
new Metrics(bootstrap, 18244);
pluginManager.registerEvents(new UserListener(userManager), bootstrap);
@ -152,7 +152,7 @@ public class ZNpcsPlus {
pluginManager.registerEvents(new UpdateNotificationListener(this, adventure, updateChecker, scheduler), bootstrap);
}
scheduler.runDelayedTimerAsync(new NpcProcessorTask(npcRegistry, propertyRegistry), 60L, 3L);
scheduler.runDelayedTimerAsync(new NpcProcessorTask(npcRegistry, propertyRegistry, userManager), 60L, 3L);
scheduler.runDelayedTimerAsync(new HologramRefreshTask(npcRegistry), 60L, 20L);
scheduler.runDelayedTimerAsync(new SkinCacheCleanTask(skinCache), 1200, 1200);
pluginManager.registerEvents(new ViewableHideOnLeaveListener(), bootstrap);
@ -275,6 +275,7 @@ public class ZNpcsPlus {
registerEnumParser(manager, SnifferState.class, incorrectUsageMessage);
registerEnumParser(manager, RabbitType.class, incorrectUsageMessage);
registerEnumParser(manager, AttachDirection.class, incorrectUsageMessage);
registerEnumParser(manager, Sound.class, incorrectUsageMessage);
manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root"))
.addSubcommand("center", new CenterCommand(npcRegistry))

@ -2,6 +2,7 @@ package lol.pyr.znpcsplus.conversion;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.conversion.citizens.CitizensImporter;
import lol.pyr.znpcsplus.conversion.fancynpcs.FancyNpcsImporter;
import lol.pyr.znpcsplus.conversion.znpcs.ZNpcImporter;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
@ -34,6 +35,8 @@ public class DataImporterRegistry {
packetFactory, textSerializer, typeRegistry, propertyRegistry, skinCache, new File(pluginsFolder, "ZNPCsPlusLegacy/data.json"), bungeeConnector)));
register("citizens", LazyLoader.of(() -> new CitizensImporter(configManager, adventure, taskScheduler,
packetFactory, textSerializer, typeRegistry, propertyRegistry, skinCache, new File(pluginsFolder, "Citizens/saves.yml"), npcRegistry)));
register("fancynpcs", LazyLoader.of(() -> new FancyNpcsImporter(configManager, adventure, taskScheduler,
packetFactory, textSerializer, typeRegistry, propertyRegistry, skinCache, new File(pluginsFolder, "FancyNpcs/npcs.yml"), npcRegistry)));
}
private void register(String id, LazyLoader<DataImporter> loader) {

@ -0,0 +1,183 @@
package lol.pyr.znpcsplus.conversion.fancynpcs;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lol.pyr.znpcsplus.api.interaction.InteractionType;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.conversion.DataImporter;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.interaction.consolecommand.ConsoleCommandAction;
import lol.pyr.znpcsplus.interaction.message.MessageAction;
import lol.pyr.znpcsplus.interaction.playercommand.PlayerCommandAction;
import lol.pyr.znpcsplus.npc.*;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.skin.SkinImpl;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.skin.descriptor.MirrorDescriptor;
import lol.pyr.znpcsplus.skin.descriptor.PrefetchedDescriptor;
import lol.pyr.znpcsplus.util.LookType;
import lol.pyr.znpcsplus.util.NamedColor;
import lol.pyr.znpcsplus.util.NpcLocation;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import java.io.File;
import java.util.*;
public class FancyNpcsImporter implements DataImporter {
private final ConfigManager configManager;
private final BukkitAudiences adventure;
private final TaskScheduler scheduler;
private final PacketFactory packetFactory;
private final LegacyComponentSerializer textSerializer;
private final NpcTypeRegistryImpl typeRegistry;
private final EntityPropertyRegistryImpl propertyRegistry;
private final MojangSkinCache skinCache;
private final File dataFile;
private final NpcRegistryImpl npcRegistry;
public FancyNpcsImporter(ConfigManager configManager, BukkitAudiences adventure,
TaskScheduler taskScheduler, PacketFactory packetFactory, LegacyComponentSerializer textSerializer,
NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, MojangSkinCache skinCache,
File dataFile, NpcRegistryImpl npcRegistry) {
this.configManager = configManager;
this.adventure = adventure;
this.scheduler = taskScheduler;
this.packetFactory = packetFactory;
this.textSerializer = textSerializer;
this.typeRegistry = typeRegistry;
this.propertyRegistry = propertyRegistry;
this.skinCache = skinCache;
this.dataFile = dataFile;
this.npcRegistry = npcRegistry;
}
@Override
public Collection<NpcEntryImpl> importData() {
YamlConfiguration config = YamlConfiguration.loadConfiguration(dataFile);
ConfigurationSection npcsSection = config.getConfigurationSection("npcs");
if (npcsSection == null) {
return Collections.emptyList();
}
ArrayList<NpcEntryImpl> entries = new ArrayList<>();
npcsSection.getKeys(false).forEach(key -> {
ConfigurationSection npcSection = npcsSection.getConfigurationSection(key);
if (npcSection == null) {
return;
}
String name = npcSection.getString("name", "FancyNPC");
UUID uuid = UUID.fromString(key);
String world = npcSection.getString("location.world");
if (world == null) {
world = Bukkit.getWorlds().get(0).getName();
}
NpcLocation location = new NpcLocation(
npcSection.getDouble("location.x"),
npcSection.getDouble("location.y"),
npcSection.getDouble("location.z"),
(float) npcSection.getDouble("location.yaw"),
(float) npcSection.getDouble("location.pitch")
);
String typeString = npcSection.getString("type");
NpcTypeImpl type = typeRegistry.getByName(typeString);
if (type == null) {
type = typeRegistry.getByName("player");
}
NpcImpl npc = new NpcImpl(uuid, propertyRegistry, configManager, packetFactory, textSerializer, world, type, location);
npc.getType().applyDefaultProperties(npc);
npc.getHologram().addTextLineComponent(textSerializer.deserialize(name));
boolean glowing = npcSection.getBoolean("glowing", false);
if (glowing) {
NamedColor color;
try {
color = NamedColor.valueOf(npcSection.getString("glowingColor", "white"));
} catch (IllegalArgumentException ignored) {
color = NamedColor.WHITE;
}
EntityPropertyImpl<NamedColor> property = propertyRegistry.getByName("glow", NamedColor.class);
npc.setProperty(property, color);
}
if (npcSection.getBoolean("turnToPlayer", false)) {
EntityPropertyImpl<LookType> property = propertyRegistry.getByName("look", LookType.class);
npc.setProperty(property, LookType.CLOSEST_PLAYER);
}
if (npcSection.isConfigurationSection("skin")) {
ConfigurationSection skinSection = npcSection.getConfigurationSection("skin");
String texture = skinSection.getString("value");
String signature = skinSection.getString("signature");
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new PrefetchedDescriptor(new SkinImpl(texture, signature)));
}
if (npcSection.isConfigurationSection("equipment")) {
ConfigurationSection equipmentSection = npcSection.getConfigurationSection("equipment");
for (String slot : equipmentSection.getKeys(false)) {
ItemStack item = equipmentSection.getItemStack(slot);
if (item != null) {
npc.setProperty(propertyRegistry.getByName(getEquipmentPropertyName(slot),
com.github.retrooper.packetevents.protocol.item.ItemStack.class), SpigotConversionUtil.fromBukkitItemStack(item));
}
}
}
if (npcSection.getBoolean("mirrorSkin")) {
npc.setProperty(propertyRegistry.getByName("skin", SkinDescriptor.class), new MirrorDescriptor(skinCache));
}
List<String> playerCommands = npcSection.getStringList("playerCommands");
if (!playerCommands.isEmpty()) {
long cooldown = npcSection.getLong("interactionCooldown", 0);
for (String command : playerCommands) {
npc.addAction(new PlayerCommandAction(scheduler, command, InteractionType.ANY_CLICK, cooldown, 0));
}
}
String serverCommand = npcSection.getString("serverCommand");
if (serverCommand != null) {
long cooldown = npcSection.getLong("interactionCooldown", 0);
npc.addAction(new ConsoleCommandAction(scheduler, serverCommand, InteractionType.ANY_CLICK, cooldown, 0));
}
List<String> messages = npcSection.getStringList("messages");
if (!messages.isEmpty()) {
long cooldown = npcSection.getLong("interactionCooldown", 0);
for (String message : messages) {
npc.addAction(new MessageAction(adventure, message, InteractionType.ANY_CLICK, textSerializer, cooldown, 0));
}
}
String id = npcSection.getString("name");
while (npcRegistry.getById(id) != null) {
id += "_";
}
NpcEntryImpl entry = new NpcEntryImpl(id, npc);
entry.enableEverything();
entries.add(entry);
});
return entries;
}
private String getEquipmentPropertyName(String slot) {
switch (slot) {
case "MAINHAND":
return "hand";
case "OFFHAND":
return "offhand";
case "FEET":
return "boots";
case "LEGS":
return "leggings";
case "CHEST":
return "chestplate";
case "HEAD":
return "helmet";
default:
return null;
}
}
@Override
public boolean isValid() {
return dataFile.isFile();
}
}

@ -24,6 +24,7 @@ import lol.pyr.znpcsplus.util.*;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.Color;
import org.bukkit.DyeColor;
import org.bukkit.Sound;
import java.util.*;
import java.util.stream.Collectors;
@ -84,6 +85,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
registerEnumSerializer(SnifferState.class);
registerEnumSerializer(RabbitType.class);
registerEnumSerializer(AttachDirection.class);
registerEnumSerializer(Sound.class);
registerPrimitiveSerializers(Integer.class, Boolean.class, Double.class, Float.class, Long.class, Short.class, Byte.class, String.class);
@ -122,6 +124,20 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
register(new DummyProperty<>("permission_required", false));
register(new DummyProperty<>("player_knockback", false));
register(new DummyProperty<>("player_knockback_exempt_permission", String.class));
register(new DummyProperty<>("player_knockback_distance", 0.4));
register(new DummyProperty<>("player_knockback_vertical", 0.4));
register(new DummyProperty<>("player_knockback_horizontal", 0.9));
register(new DummyProperty<>("player_knockback_cooldown", 1500));
register(new DummyProperty<>("player_knockback_sound", false));
register(new DummyProperty<>("player_knockback_sound_volume", 1.0f));
register(new DummyProperty<>("player_knockback_sound_pitch", 1.0f));
register(new DummyProperty<>("player_knockback_sound_name", Sound.valueOf(
PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_9) ?
"VILLAGER_NO" : "ENTITY_VILLAGER_NO"
)));
register(new GlowProperty(packetFactory));
register(new BitsetProperty("fire", 0, 0x01));
register(new BitsetProperty("invisible", 0, 0x20));
@ -154,7 +170,11 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_10)) babyIndex = 12;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_9)) babyIndex = 11;
else babyIndex = 12;
register(new BooleanProperty("baby", babyIndex, false, legacyBooleans));
if (ver.isOlderThan(ServerVersion.V_1_9)) {
register(new EncodedByteProperty<>("baby", false, babyIndex, obj -> (byte) (obj ? -1 : 0)));
} else {
register(new BooleanProperty("baby", babyIndex, false, legacyBooleans));
}
// Player
register(new DummyProperty<>("skin", SkinDescriptor.class, false));
@ -369,6 +389,15 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
register(new EncodedByteProperty<>("sheep_color", DyeColor.WHITE, sheepIndex, DyeColor::getWoolData));
register(new BitsetProperty("sheep_sheared", sheepIndex, 0x10, false, legacyBooleans)); // no need to link because sheep_sheared is only visible when sheep_color is WHITE
// Villager
int villagerIndex;
if (ver.isOlderThan(ServerVersion.V_1_14)) {
if (ver.isNewerThanOrEquals(ServerVersion.V_1_10)) villagerIndex = 13;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_9)) villagerIndex = 12;
else villagerIndex = 16;
register(new EncodedIntegerProperty<>("villager_profession", VillagerProfession.NONE, villagerIndex, VillagerProfession::getLegacyId));
}
// Wolf
int wolfIndex;
if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) wolfIndex = 19;
@ -497,12 +526,8 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
register(new CustomTypeProperty<>("pose", 6, NpcPose.STANDING, EntityDataTypes.ENTITY_POSE, npcPose -> EntityPose.valueOf(npcPose.name())));
// Villager
final int villagerIndex;
if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) villagerIndex = 18;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) villagerIndex = 17;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_14)) villagerIndex = 16;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_10)) villagerIndex = 13;
else if (ver.isNewerThanOrEquals(ServerVersion.V_1_9)) villagerIndex = 12;
else villagerIndex = 16;
register(new VillagerTypeProperty("villager_type", villagerIndex, VillagerType.PLAINS));
register(new VillagerProfessionProperty("villager_profession", villagerIndex, VillagerProfession.NONE));
@ -612,7 +637,9 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
if (!ver.isNewerThanOrEquals(ServerVersion.V_1_20)) return;
// Camel
register(new BooleanProperty("bashing", 18, false, legacyBooleans));
int camelIndex = 18;
register(new BooleanProperty("bashing", camelIndex++, false, legacyBooleans));
register(new CamelSittingProperty(6, camelIndex));
// Sniffer
register(new CustomTypeProperty<>("sniffer_state", 17, SnifferState.IDLING, EntityDataTypes.SNIFFER_STATE, state -> com.github.retrooper.packetevents.protocol.entity.sniffer.SnifferState.valueOf(state.name())));

@ -0,0 +1,33 @@
package lol.pyr.znpcsplus.entity.properties;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
import com.github.retrooper.packetevents.protocol.entity.pose.EntityPose;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.PacketEntity;
import org.bukkit.entity.Player;
import java.util.Map;
public class CamelSittingProperty extends EntityPropertyImpl<Boolean> {
private final int poseIndex;
private final int lastPoseTickIndex;
public CamelSittingProperty(int poseIndex, int lastPoseTickIndex) {
super("camel_sitting", false, Boolean.class);
this.poseIndex = poseIndex;
this.lastPoseTickIndex = lastPoseTickIndex;
}
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
boolean value = entity.getProperty(this);
if (value) {
properties.put(poseIndex, newEntityData(poseIndex, EntityDataTypes.ENTITY_POSE, EntityPose.SITTING));
properties.put(lastPoseTickIndex, newEntityData(lastPoseTickIndex, EntityDataTypes.LONG, -1L));
} else {
properties.put(poseIndex, newEntityData(poseIndex, EntityDataTypes.ENTITY_POSE, EntityPose.STANDING));
properties.put(lastPoseTickIndex, newEntityData(lastPoseTickIndex, EntityDataTypes.LONG, 0L));
}
}
}

@ -14,11 +14,13 @@ import java.util.Map;
import java.util.Optional;
public class DinnerboneProperty extends EntityPropertyImpl<Boolean> {
private final boolean optional;
private final Object serialized;
private final EntityDataType<?> type;
public DinnerboneProperty(boolean legacy, boolean optional) {
super("dinnerbone", false, Boolean.class);
this.optional = optional;
Component name = Component.text("Dinnerbone");
Object serialized = legacy ? AdventureSerializer.getLegacyGsonSerializer().serialize(name) :
optional ? name : LegacyComponentSerializer.legacySection().serialize(name);
@ -28,6 +30,6 @@ public class DinnerboneProperty extends EntityPropertyImpl<Boolean> {
@Override
public void apply(Player player, PacketEntity entity, boolean isSpawned, Map<Integer, EntityData> properties) {
properties.put(2, new EntityData(2, type, entity.getProperty(this) ? serialized : null));
properties.put(2, new EntityData(2, type, entity.getProperty(this) ? serialized : optional ? null : ""));
}
}

@ -1,29 +1,39 @@
package lol.pyr.znpcsplus.interaction;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketListener;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.item.ItemStack;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.Equipment;
import com.github.retrooper.packetevents.protocol.player.EquipmentSlot;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityEquipment;
import lol.pyr.znpcsplus.api.event.NpcInteractEvent;
import lol.pyr.znpcsplus.api.interaction.InteractionAction;
import lol.pyr.znpcsplus.api.interaction.InteractionType;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.user.User;
import lol.pyr.znpcsplus.user.UserManager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Collections;
public class InteractionPacketListener implements PacketListener {
private final UserManager userManager;
private final NpcRegistryImpl npcRegistry;
private final NpcTypeRegistryImpl typeRegistry;
private final TaskScheduler scheduler;
public InteractionPacketListener(UserManager userManager, NpcRegistryImpl npcRegistry, TaskScheduler scheduler) {
public InteractionPacketListener(UserManager userManager, NpcRegistryImpl npcRegistry, NpcTypeRegistryImpl typeRegistry, TaskScheduler scheduler) {
this.userManager = userManager;
this.npcRegistry = npcRegistry;
this.typeRegistry = typeRegistry;
this.scheduler = scheduler;
}
@ -34,14 +44,25 @@ public class InteractionPacketListener implements PacketListener {
if (player == null) return;
WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
User user = userManager.get(player);
if (!user.canInteract()) return;
NpcEntryImpl entry = npcRegistry.getByEntityId(packet.getEntityId());
if (entry == null || !entry.isProcessed()) return;
NpcImpl npc = entry.getNpc();
if ((packet.getAction().equals(WrapperPlayClientInteractEntity.InteractAction.INTERACT)
|| packet.getAction().equals(WrapperPlayClientInteractEntity.InteractAction.INTERACT_AT))
&& npc.getType().equals(typeRegistry.getByName("allay"))) {
PacketEvents.getAPI().getPlayerManager().sendPacket(player,
new WrapperPlayServerEntityEquipment(packet.getEntityId(), Collections.singletonList(
new Equipment(EquipmentSlot.MAIN_HAND, ItemStack.EMPTY))));
player.updateInventory();
}
InteractionType type = wrapClickType(packet.getAction());
User user = userManager.get(player);
if (!user.canInteract()) return;
NpcInteractEvent interactEvent = new NpcInteractEvent(player, entry, type);
Bukkit.getPluginManager().callEvent(interactEvent);
if (interactEvent.isCancelled()) return;

@ -115,7 +115,10 @@ public class NpcTypeImpl implements NpcType {
public NpcTypeImpl build() {
ServerVersion version = PacketEvents.getAPI().getServerManager().getVersion();
addProperties("fire", "invisible", "silent", "look", "look_distance", "view_distance",
"potion_color", "potion_ambient", "display_name", "permission_required");
"potion_color", "potion_ambient", "display_name", "permission_required",
"player_knockback", "player_knockback_exempt_permission", "player_knockback_distance", "player_knockback_vertical",
"player_knockback_horizontal", "player_knockback_cooldown", "player_knockback_sound", "player_knockback_sound_name",
"player_knockback_sound_volume", "player_knockback_sound_pitch");
if (!type.equals(EntityTypes.PLAYER)) addProperties("dinnerbone");
// TODO: make this look nicer after completing the rest of the properties
if (version.isNewerThanOrEquals(ServerVersion.V_1_9)) addProperties("glow");

@ -367,7 +367,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
register(builder(p, "camel", EntityTypes.CAMEL)
.setHologramOffset(0.25)
.addProperties("bashing"));
.addProperties("bashing", "camel_sitting"));
}
public Collection<NpcType> getAll() {

@ -7,20 +7,26 @@ import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.user.User;
import lol.pyr.znpcsplus.user.UserManager;
import lol.pyr.znpcsplus.util.LookType;
import lol.pyr.znpcsplus.util.NpcLocation;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.NumberConversions;
import org.bukkit.util.Vector;
public class NpcProcessorTask extends BukkitRunnable {
private final NpcRegistryImpl npcRegistry;
private final EntityPropertyRegistryImpl propertyRegistry;
private final UserManager userManager;
public NpcProcessorTask(NpcRegistryImpl npcRegistry, EntityPropertyRegistryImpl propertyRegistry) {
public NpcProcessorTask(NpcRegistryImpl npcRegistry, EntityPropertyRegistryImpl propertyRegistry,UserManager userManager) {
this.npcRegistry = npcRegistry;
this.propertyRegistry = propertyRegistry;
this.userManager = userManager;
}
public void run() {
@ -28,7 +34,28 @@ public class NpcProcessorTask extends BukkitRunnable {
EntityPropertyImpl<LookType> lookProperty = propertyRegistry.getByName("look", LookType.class);
EntityPropertyImpl<Double> lookDistanceProperty = propertyRegistry.getByName("look_distance", Double.class);
EntityPropertyImpl<Boolean> permissionRequiredProperty = propertyRegistry.getByName("permission_required", Boolean.class);
EntityPropertyImpl<Boolean> playerKnockbackProperty = propertyRegistry.getByName("player_knockback", Boolean.class);
EntityPropertyImpl<String> playerKnockbackExemptPermissionProperty = propertyRegistry.getByName("player_knockback_exempt_permission", String.class);
EntityPropertyImpl<Double> playerKnockbackDistanceProperty = propertyRegistry.getByName("player_knockback_distance", Double.class);
EntityPropertyImpl<Double> playerKnockbackVerticalProperty = propertyRegistry.getByName("player_knockback_vertical", Double.class);
EntityPropertyImpl<Double> playerKnockbackHorizontalProperty = propertyRegistry.getByName("player_knockback_horizontal", Double.class);
EntityPropertyImpl<Integer> playerKnockbackCooldownProperty = propertyRegistry.getByName("player_knockback_cooldown", Integer.class);
EntityPropertyImpl<Boolean> playerKnockbackSoundProperty = propertyRegistry.getByName("player_knockback_sound", Boolean.class);
EntityPropertyImpl<Sound> playerKnockbackSoundNameProperty = propertyRegistry.getByName("player_knockback_sound_name", Sound.class);
EntityPropertyImpl<Float> playerKnockbackSoundVolumeProperty = propertyRegistry.getByName("player_knockback_sound_volume", Float.class);
EntityPropertyImpl<Float> playerKnockbackSoundPitchProperty = propertyRegistry.getByName("player_knockback_sound_pitch", Float.class);
double lookDistance;
boolean permissionRequired;
boolean playerKnockback;
String playerKnockbackExemptPermission = null;
double playerKnockbackDistance = 0;
double playerKnockbackVertical = 0;
double playerKnockbackHorizontal = 0;
int playerKnockbackCooldown = 0;
boolean playerKnockbackSound = false;
Sound playerKnockbackSoundName = null;
float playerKnockbackSoundVolume = 0;
float playerKnockbackSoundPitch = 0;
for (NpcEntryImpl entry : npcRegistry.getProcessable()) {
NpcImpl npc = entry.getNpc();
if (!npc.isEnabled()) continue;
@ -37,7 +64,19 @@ public class NpcProcessorTask extends BukkitRunnable {
Player closest = null;
LookType lookType = npc.getProperty(lookProperty);
lookDistance = NumberConversions.square(npc.getProperty(lookDistanceProperty));
boolean permissionRequired = npc.getProperty(permissionRequiredProperty);
permissionRequired = npc.getProperty(permissionRequiredProperty);
playerKnockback = npc.getProperty(playerKnockbackProperty);
if (playerKnockback) {
playerKnockbackExemptPermission = npc.getProperty(playerKnockbackExemptPermissionProperty);
playerKnockbackDistance = NumberConversions.square(npc.getProperty(playerKnockbackDistanceProperty));
playerKnockbackVertical = npc.getProperty(playerKnockbackVerticalProperty);
playerKnockbackHorizontal = npc.getProperty(playerKnockbackHorizontalProperty);
playerKnockbackCooldown = npc.getProperty(playerKnockbackCooldownProperty);
playerKnockbackSound = npc.getProperty(playerKnockbackSoundProperty);
playerKnockbackSoundName = npc.getProperty(playerKnockbackSoundNameProperty);
playerKnockbackSoundVolume = npc.getProperty(playerKnockbackSoundVolumeProperty);
playerKnockbackSoundPitch = npc.getProperty(playerKnockbackSoundPitchProperty);
}
for (Player player : Bukkit.getOnlinePlayers()) {
if (!player.getWorld().equals(npc.getWorld())) {
if (npc.isVisibleTo(player)) npc.hide(player);
@ -71,6 +110,21 @@ public class NpcProcessorTask extends BukkitRunnable {
NpcLocation expected = npc.getLocation().lookingAt(player.getLocation().add(0, -npc.getType().getHologramOffset(), 0));
if (!expected.equals(npc.getLocation())) npc.setHeadRotation(player, expected.getYaw(), expected.getPitch());
}
// player knockback
User user = userManager.get(player.getUniqueId());
if (playerKnockbackExemptPermission == null || !player.hasPermission(playerKnockbackExemptPermission)) {
if (playerKnockback && distance <= playerKnockbackDistance && user.canKnockback(playerKnockbackCooldown)) {
double x = npc.getLocation().getX() - player.getLocation().getX();
double z = npc.getLocation().getZ() - player.getLocation().getZ();
double angle = Math.atan2(z, x);
double knockbackX = -Math.cos(angle) * playerKnockbackHorizontal;
double knockbackZ = -Math.sin(angle) * playerKnockbackHorizontal;
player.setVelocity(player.getVelocity().add(new Vector(knockbackX, playerKnockbackVertical, knockbackZ)));
if (playerKnockbackSound)
player.playSound(player.getLocation(), playerKnockbackSoundName, playerKnockbackSoundVolume, playerKnockbackSoundPitch);
}
}
}
}
// look property

@ -50,6 +50,7 @@ public class UpdateChecker extends BukkitRunnable {
int currentPart = Integer.parseInt(currentParts[i]);
int newPart = Integer.parseInt(newParts[i]);
if (newPart > currentPart) return Status.UPDATE_NEEDED;
if (newPart < currentPart) return Status.LATEST_VERSION;
}
if (newType.ordinal() > currentType.ordinal()) return Status.UPDATE_NEEDED;
if (newType == currentType) {

@ -11,6 +11,7 @@ import java.util.UUID;
public class User {
private final UUID uuid;
private long lastNpcInteraction;
private long lastNpcKnockback;
private final Map<UUID, Long> actionCooldownMap = new HashMap<>();
public User(UUID uuid) {
@ -29,6 +30,14 @@ public class User {
return false;
}
public boolean canKnockback(int cooldown) {
if (System.currentTimeMillis() - lastNpcKnockback > cooldown) {
lastNpcKnockback = System.currentTimeMillis();
return true;
}
return false;
}
public UUID getUuid() {
return uuid;
}