diff --git a/main/src/main/java/net/citizensnpcs/EventListen.java b/main/src/main/java/net/citizensnpcs/EventListen.java index c2f5b12aa..684c5c87d 100644 --- a/main/src/main/java/net/citizensnpcs/EventListen.java +++ b/main/src/main/java/net/citizensnpcs/EventListen.java @@ -47,8 +47,10 @@ import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.event.vehicle.VehicleDamageEvent; import org.bukkit.event.vehicle.VehicleDestroyEvent; import org.bukkit.event.vehicle.VehicleEnterEvent; +import org.bukkit.event.world.ChunkEvent; import org.bukkit.event.world.ChunkLoadEvent; import org.bukkit.event.world.ChunkUnloadEvent; +import org.bukkit.event.world.EntitiesLoadEvent; import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldUnloadEvent; import org.bukkit.inventory.meta.SkullMeta; @@ -151,8 +153,7 @@ public class EventListen implements Listener { Predicates.notNull()); } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onChunkLoad(ChunkLoadEvent event) { + private void loadNPCs(ChunkEvent event) { ChunkCoord coord = new ChunkCoord(event.getChunk()); Runnable runnable = new Runnable() { @Override @@ -170,6 +171,14 @@ public class EventListen implements Listener { } } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onChunkLoad(ChunkLoadEvent event) { + if (usingEntitiesLoadEvents()) + return; + + loadNPCs(event); + } + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onChunkUnload(final ChunkUnloadEvent event) { final List toDespawn = Lists.newArrayList(); @@ -232,6 +241,11 @@ public class EventListen implements Listener { checkCreationEvent(event); } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onEntitiesLoad(EntitiesLoadEvent event) { + loadNPCs(event); + } + /* * Entity events */ @@ -732,4 +746,18 @@ public class EventListen implements Listener { } return npc.spawn(spawn, SpawnReason.CHUNK_LOAD); } + + private static boolean usingEntitiesLoadEvents() { + if (USING_ENTITIES_LOAD == null) { + try { + Class.forName("org.bukkit.event.world.EntitiesLoadEvent"); + USING_ENTITIES_LOAD = true; + } catch (ClassNotFoundException swallow) { + USING_ENTITIES_LOAD = false; + } + } + return USING_ENTITIES_LOAD; + } + + private static Boolean USING_ENTITIES_LOAD; } diff --git a/main/src/main/java/net/citizensnpcs/Settings.java b/main/src/main/java/net/citizensnpcs/Settings.java index 5be62b6c5..c8b6ac509 100644 --- a/main/src/main/java/net/citizensnpcs/Settings.java +++ b/main/src/main/java/net/citizensnpcs/Settings.java @@ -110,7 +110,7 @@ public class Settings { DISABLE_LOOKCLOSE_WHILE_NAVIGATING("npc.default.look-close.disable-while-navigating", true), DISABLE_MC_NAVIGATION_FALLBACK("npc.pathfinding.disable-mc-fallback-navigation", true), DISABLE_TABLIST("npc.tablist.disable", true), - ENTITY_SPAWN_WAIT_TICKS("general.entity-spawn-wait-ticks", 10), + ENTITY_SPAWN_WAIT_TICKS("general.entity-spawn-wait-ticks", 20), ERROR_COLOUR("general.color-scheme.message-error", ""), FOLLOW_ACROSS_WORLDS("npc.follow.teleport-across-worlds", true), HIGHLIGHT_COLOUR("general.color-scheme.message-highlight", ""), diff --git a/main/src/main/java/net/citizensnpcs/commands/EditorCommands.java b/main/src/main/java/net/citizensnpcs/commands/EditorCommands.java index a5c622427..dcfa53a4a 100644 --- a/main/src/main/java/net/citizensnpcs/commands/EditorCommands.java +++ b/main/src/main/java/net/citizensnpcs/commands/EditorCommands.java @@ -62,9 +62,12 @@ public class EditorCommands { desc = "Toggle the text editor", modifiers = { "text" }, min = 1, - max = 1, permission = "citizens.npc.edit.text") public void text(CommandContext args, Player player, NPC npc) { + if (player.isConversing() && Editor.hasEditor(player) && args.argsLength() > 1) { + player.acceptConversationInput(args.getJoinedStrings(1)); + return; + } Editor.enterOrLeave(player, npc.getOrAddTrait(Text.class).getEditor(player)); } } diff --git a/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java b/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java index f07a8c4cd..afbbaeff2 100644 --- a/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java +++ b/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java @@ -1580,8 +1580,8 @@ public class NPCCommands { permission = "citizens.npc.passive") public void passive(CommandContext args, CommandSender sender, NPC npc) throws CommandException { boolean passive = args.hasValueFlag("set") ? Boolean.parseBoolean(args.getFlag("set")) - : npc.data().get(NPC.DAMAGE_OTHERS_METADATA, true); - npc.data().setPersistent(NPC.DAMAGE_OTHERS_METADATA, !passive); + : !npc.data().get(NPC.DAMAGE_OTHERS_METADATA, true); + npc.data().setPersistent(NPC.DAMAGE_OTHERS_METADATA, passive); Messaging.sendTr(sender, passive ? Messages.PASSIVE_SET : Messages.PASSIVE_UNSET, npc.getName()); } diff --git a/main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java b/main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java index cb28e027d..30f37fedb 100644 --- a/main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java +++ b/main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java @@ -3,6 +3,7 @@ package net.citizensnpcs.npc; import java.util.ArrayList; import java.util.Collection; import java.util.UUID; +import java.util.function.Consumer; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -19,6 +20,7 @@ import org.bukkit.scheduler.BukkitRunnable; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import com.google.common.collect.HashMultimap; +import com.google.common.collect.Iterables; import com.google.common.collect.SetMultimap; import net.citizensnpcs.NPCNeedsRespawnEvent; @@ -292,22 +294,23 @@ public class CitizensNPC extends AbstractNPC { NMS.setBodyYaw(getEntity(), at.getYaw()); final Location to = at; - BukkitRunnable postSpawn = new BukkitRunnable() { + Consumer postSpawn = new Consumer() { private int timer; @Override - public void run() { - if (timer++ > Setting.ENTITY_SPAWN_WAIT_TICKS.asInt()) { - Messaging.debug("Couldn't spawn", CitizensNPC.this, "entity not added to world"); - entityController.remove(); - cancel(); - Bukkit.getPluginManager().callEvent(new NPCNeedsRespawnEvent(CitizensNPC.this, to)); + public void accept(Runnable cancel) { + if (getEntity() == null || !getEntity().isValid()) { + if (timer++ > Setting.ENTITY_SPAWN_WAIT_TICKS.asInt()) { + Messaging.debug("Couldn't spawn ", CitizensNPC.this, "waited", timer, + "ticks but entity not added to world"); + entityController.remove(); + cancel.run(); + Bukkit.getPluginManager().callEvent(new NPCNeedsRespawnEvent(CitizensNPC.this, to)); + } + return; } - if (getEntity() == null || !getEntity().isValid()) - return; - // Set the spawned state getOrAddTrait(CurrentLocation.class).setLocation(to); getOrAddTrait(Spawned.class).setSpawned(true); @@ -319,14 +322,13 @@ public class CitizensNPC extends AbstractNPC { Messaging.debug("Couldn't spawn", CitizensNPC.this, "SpawnReason." + reason, "due to event cancellation."); entityController.remove(); - cancel(); + cancel.run(); return; } navigator.onSpawn(); - Collection onSpawn = traits.values(); - for (Trait trait : onSpawn.toArray(new Trait[onSpawn.size()])) { + for (Trait trait : Iterables.toArray(traits.values(), Trait.class)) { try { trait.onSpawn(); } catch (Throwable ex) { @@ -335,17 +337,20 @@ public class CitizensNPC extends AbstractNPC { } } - if (getEntity() instanceof LivingEntity) { + EntityType type = getEntity().getType(); + if (type.isAlive()) { LivingEntity entity = (LivingEntity) getEntity(); entity.setRemoveWhenFarAway(false); if (NMS.getStepHeight(entity) < 1) { NMS.setStepHeight(entity, 1); } - if (getEntity() instanceof Player) { + + if (type == EntityType.PLAYER) { NMS.replaceTrackerEntry((Player) getEntity()); PlayerUpdateTask.registerPlayer(getEntity()); } + if (SUPPORT_NODAMAGE_TICKS && (Setting.DEFAULT_SPAWN_NODAMAGE_TICKS.asInt() != 20 || data().has(NPC.Metadata.SPAWN_NODAMAGE_TICKS))) { try { @@ -366,13 +371,19 @@ public class CitizensNPC extends AbstractNPC { updateCustomName(); Messaging.debug("Spawned", CitizensNPC.this, "SpawnReason." + reason); - cancel(); + cancel.run(); } }; - if (isSpawned()) { - postSpawn.runTask(CitizensAPI.getPlugin()); + if (getEntity() != null && getEntity().isValid()) { + postSpawn.accept(() -> { + }); } else { - postSpawn.runTaskTimer(CitizensAPI.getPlugin(), 0, 1); + new BukkitRunnable() { + @Override + public void run() { + postSpawn.accept(() -> cancel()); + } + }.runTaskTimer(CitizensAPI.getPlugin(), 0, 1); } return true; diff --git a/main/src/main/java/net/citizensnpcs/npc/skin/SkinPacketTracker.java b/main/src/main/java/net/citizensnpcs/npc/skin/SkinPacketTracker.java index 0fc06557e..2adbc31c8 100644 --- a/main/src/main/java/net/citizensnpcs/npc/skin/SkinPacketTracker.java +++ b/main/src/main/java/net/citizensnpcs/npc/skin/SkinPacketTracker.java @@ -171,8 +171,6 @@ public class SkinPacketTracker { * The radius. */ public void updateNearbyViewers(double radius) { - radius *= radius; - org.bukkit.World world = entity.getBukkitEntity().getWorld(); Player from = entity.getBukkitEntity(); Location location = from.getLocation(); @@ -181,12 +179,12 @@ public class SkinPacketTracker { if (player == null || player.hasMetadata("NPC")) continue; - player.getLocation(CACHE_LOCATION); - if (!player.canSee(from) || !location.getWorld().equals(CACHE_LOCATION.getWorld())) + if (!location.getWorld().equals(player.getLocation(CACHE_LOCATION).getWorld()) || !player.canSee(from)) continue; - if (location.distanceSquared(CACHE_LOCATION) > radius) + if (location.distance(CACHE_LOCATION) > radius) continue; + updateViewer(player); } } diff --git a/main/src/main/java/net/citizensnpcs/npc/skin/SkinUpdateTracker.java b/main/src/main/java/net/citizensnpcs/npc/skin/SkinUpdateTracker.java index c86169a91..c74e2b20d 100644 --- a/main/src/main/java/net/citizensnpcs/npc/skin/SkinUpdateTracker.java +++ b/main/src/main/java/net/citizensnpcs/npc/skin/SkinUpdateTracker.java @@ -74,10 +74,7 @@ public class SkinUpdateTracker { Location playerLoc = player.getLocation(CACHE_LOCATION); Location skinLoc = entity.getLocation(NPC_LOCATION); - double viewDistance = Setting.NPC_SKIN_VIEW_DISTANCE.asDouble(); - viewDistance *= viewDistance; - - if (playerLoc.distanceSquared(skinLoc) > viewDistance) + if (playerLoc.distance(skinLoc) > Setting.NPC_SKIN_VIEW_DISTANCE.asDouble()) return false; // see if the NPC is within the players field of view diff --git a/main/src/main/java/net/citizensnpcs/trait/SmoothRotationTrait.java b/main/src/main/java/net/citizensnpcs/trait/SmoothRotationTrait.java index 3f3e3fff4..018a714a4 100644 --- a/main/src/main/java/net/citizensnpcs/trait/SmoothRotationTrait.java +++ b/main/src/main/java/net/citizensnpcs/trait/SmoothRotationTrait.java @@ -66,7 +66,7 @@ public class SmoothRotationTrait extends Trait { * The target location to face */ public void rotateToFace(Location target) { - this.globalSession.setTarget(target); + globalSession.setTarget(target); } public void rotateToHave(float yaw, float pitch) { @@ -119,13 +119,22 @@ public class SmoothRotationTrait extends Trait { } } - public static class RotationParams implements Persistable { + public static class RotationParams implements Persistable, Cloneable { private boolean headOnly = false; private boolean immediate = false; private float maxPitchPerTick = 10; private float maxYawPerTick = 40; - private final float[] pitchRange = { 0, 0 }; - private final float[] yawRange = { 0, 0 }; + private float[] pitchRange = { -180, 180 }; + private float[] yawRange = { -180, 180 }; + + @Override + public RotationParams clone() { + try { + return (RotationParams) super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } public RotationParams headOnly(boolean headOnly) { this.headOnly = headOnly; @@ -151,6 +160,14 @@ public class SmoothRotationTrait extends Trait { if (key.keyExists("maxYawPerTick")) { maxYawPerTick = (float) key.getDouble("maxYawPerTick"); } + if (key.keyExists("yawRange")) { + String[] parts = key.getString("yawRange").split(","); + yawRange = new float[] { Float.parseFloat(parts[0]), Float.parseFloat(parts[1]) }; + } + if (key.keyExists("pitchRange")) { + String[] parts = key.getString("pitchRange").split(","); + pitchRange = new float[] { Float.parseFloat(parts[0]), Float.parseFloat(parts[1]) }; + } } public RotationParams maxPitchPerTick(float val) { @@ -163,13 +180,22 @@ public class SmoothRotationTrait extends Trait { return this; } + public RotationParams pitchRange(float[] val) { + this.pitchRange = val; + return this; + } + public float rotateHeadYawTowards(int t, float yaw, float targetYaw) { - return rotateTowards(yaw, targetYaw, maxYawPerTick); + float out = rotateTowards(yaw, targetYaw, maxYawPerTick); + return clamp(out, yawRange[0], yawRange[1]); } public float rotatePitchTowards(int t, float pitch, float targetPitch) { - return rotateTowards(pitch, targetPitch, maxPitchPerTick); - }/* + float out = rotateTowards(pitch, targetPitch, maxPitchPerTick); + return clamp(out, pitchRange[0], pitchRange[1]); + } + + /* * public Vector3 SuperSmoothVector3Lerp( Vector3 pastPosition, Vector3 pastTargetPosition, Vector3 targetPosition, float time, float speed ){ Vector3 f = pastPosition - pastTargetPosition + (targetPosition - pastTargetPosition) / (speed * time); return targetPosition - (targetPosition - pastTargetPosition) / (speed*time) + f * Mathf.Exp(-speed*time); @@ -189,12 +215,35 @@ public class SmoothRotationTrait extends Trait { if (immediate) { key.setBoolean("immediate", immediate); } + if (maxPitchPerTick != 10) { key.setDouble("maxPitchPerTick", maxPitchPerTick); + } else { + key.removeKey("maxPitchPerTick"); } + if (maxYawPerTick != 40) { key.setDouble("maxYawPerTick", maxYawPerTick); + } else { + key.removeKey("maxYawPerTick"); } + + if (pitchRange[0] != -180 || pitchRange[1] != 180) { + key.setString("pitchRange", pitchRange[0] + "," + pitchRange[1]); + } else { + key.removeKey("pitchRange"); + } + + if (yawRange[0] != -180 || yawRange[1] != 180) { + key.setString("yawRange", yawRange[0] + "," + yawRange[1]); + } else { + key.removeKey("yawRange"); + } + } + + public RotationParams yawRange(float[] val) { + this.yawRange = val; + return this; } } @@ -207,7 +256,7 @@ public class SmoothRotationTrait extends Trait { this.params = params; } - private float getTargetPitch() { + public float getTargetPitch() { double dx = tx - getX(); double dy = ty - (getY() + getEyeY()); double dz = tz - getZ(); @@ -215,10 +264,22 @@ public class SmoothRotationTrait extends Trait { return (float) -Math.toDegrees(Math.atan2(dy, diag)); } - private float getTargetYaw() { + public double getTargetX() { + return tx; + } + + public double getTargetY() { + return ty; + } + + public float getTargetYaw() { return (float) Math.toDegrees(Math.atan2(tz - getZ(), tx - getX())) - 90.0F; } + public double getTargetZ() { + return tz; + } + public boolean hasTarget() { return t >= 0; } diff --git a/main/src/main/java/net/citizensnpcs/trait/text/TextBasePrompt.java b/main/src/main/java/net/citizensnpcs/trait/text/TextBasePrompt.java index 246b7a7fa..b55dd017c 100644 --- a/main/src/main/java/net/citizensnpcs/trait/text/TextBasePrompt.java +++ b/main/src/main/java/net/citizensnpcs/trait/text/TextBasePrompt.java @@ -26,6 +26,7 @@ public class TextBasePrompt extends StringPrompt { public Prompt acceptInput(ConversationContext context, String original) { String[] parts = ChatColor.stripColor(original.trim()).split(" "); String input = parts[0]; + CommandSender sender = (CommandSender) context.getForWhom(); if (input.equalsIgnoreCase("add")) { text.add(Joiner.on(' ').join(Arrays.copyOfRange(parts, 1, parts.length))); diff --git a/main/src/main/java/net/citizensnpcs/util/Util.java b/main/src/main/java/net/citizensnpcs/util/Util.java index b23c7b601..7d90ab9e3 100644 --- a/main/src/main/java/net/citizensnpcs/util/Util.java +++ b/main/src/main/java/net/citizensnpcs/util/Util.java @@ -26,7 +26,9 @@ import org.bukkit.util.Vector; import com.google.common.base.Joiner; import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import io.netty.util.Version; import net.citizensnpcs.api.event.NPCCollisionEvent; import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.npc.NPC; @@ -326,6 +328,27 @@ public class Util { to.getBlockZ(), (int) to.getYaw(), (int) to.getPitch()); } + public static boolean requiresNettyChannelMetadata() { + if (REQUIRES_CHANNEL_METADATA != null) + return REQUIRES_CHANNEL_METADATA; + + Version version = Version.identify().get("netty-common"); + if (version == null) { + version = Version.identify().get("netty-all"); + } + if (version == null) + return REQUIRES_CHANNEL_METADATA = false; + try { + Integer[] parts = Iterables.toArray( + Iterables.transform(Splitter.on('.').split(version.artifactVersion()), s -> Integer.parseInt(s)), + int.class); + return REQUIRES_CHANNEL_METADATA = parts[0] > 4 || (parts[0] == 4 && parts[1] > 1); + } catch (Throwable t) { + t.printStackTrace(); + return REQUIRES_CHANNEL_METADATA = true; + } + } + /** * Sets the entity's yaw and pitch directly including head yaw. */ @@ -334,6 +357,8 @@ public class Util { } private static final Location AT_LOCATION = new Location(null, 0, 0, 0); + private static final Scoreboard DUMMY_SCOREBOARD = Bukkit.getScoreboardManager().getNewScoreboard(); private static String MINECRAFT_REVISION; + private static Boolean REQUIRES_CHANNEL_METADATA; } diff --git a/main/src/main/resources/messages_en.properties b/main/src/main/resources/messages_en.properties index 5863d5686..2afd84b90 100644 --- a/main/src/main/resources/messages_en.properties +++ b/main/src/main/resources/messages_en.properties @@ -339,14 +339,14 @@ citizens.editors.text.range-set=[[Range]] set to [[{0}]]. citizens.editors.text.delay-set=[[Delay]] set to [[{0}]] seconds. citizens.editors.text.realistic-looking-set=[[Realistic looking]] set to [[{0}]]. citizens.editors.text.speech-bubbles-set=[[Speech bubbles]] set to [[{0}]]. -citizens.editors.text.start-prompt=<<[[add:command(add ):Add text>> | <<[[item:suggest(item ):Set the talk item in hand pattern (set to ''default'' to clear)>> | <<[[range:suggest(range ):Set the talking range in blocks>> | <<[[delay:suggest(delay ):Set the talking delay in seconds>>
<<{0}talk close:command(close):Toggle sending messages when players get close>> | <<{1}random:command(random):Toggle random talking>> | <<{2}speech bubbles:command(speech bubbles):Toggle showing text as holograms instead of messages>> | <<{3}realistic:command(realistic looking):Toggle requiring line of sight before speaking>> +citizens.editors.text.start-prompt=<<[[add:suggest(add ):Add text>> | <<[[item:suggest(item ):Set the talk item in hand pattern (set to ''default'' to clear)>> | <<[[range:suggest(range ):Set the talking range in blocks>> | <<[[delay:suggest(delay ):Set the talking delay in seconds>>
<<{0}talk close:command(/npc text close):Toggle sending messages when players get close>> | <<{1}random:command(/npc text random):Toggle random talking>> | <<{2}speech bubbles:command(/npc text speech bubbles):Toggle showing text as holograms instead of messages>> | <<{3}realistic:command(/npc text realistic looking):Toggle requiring line of sight before speaking>> citizens.editors.text.talk-item-set=[[Talk item pattern]] set to [[{0}]]. citizens.editors.text.text-list-header=Current text: citizens.editors.waypoints.wander.editing-regions-stop=Exited the region editor. citizens.editors.waypoints.wander.worldguard-region-not-found=WorldGuard region not found. citizens.editors.waypoints.wander.worldguard-region-set=WorldGuard region set to [[{0}]]. citizens.editors.waypoints.wander.range-set=Wander range set to xrange [[{0}]] and yrange [[{1}]]. -citizens.editors.waypoints.wander.begin=Entered the wander waypoint editor.
<> | <> | <>
<> | <> +citizens.editors.waypoints.wander.begin=Entered the wander waypoint editor.
<> | <> | <>
<> | <> citizens.editors.waypoints.wander.end=Exited the wander waypoint editor. citizens.editors.waypoints.wander.delay-set=Delay between wanders set to [[{0}]] ticks. citizens.editors.waypoints.wander.invalid-delay=Invalid delay specified. diff --git a/v1_19_R1/src/main/java/net/citizensnpcs/nms/v1_19_R1/util/NMSImpl.java b/v1_19_R1/src/main/java/net/citizensnpcs/nms/v1_19_R1/util/NMSImpl.java index cc96c4a4d..ce7ddce47 100644 --- a/v1_19_R1/src/main/java/net/citizensnpcs/nms/v1_19_R1/util/NMSImpl.java +++ b/v1_19_R1/src/main/java/net/citizensnpcs/nms/v1_19_R1/util/NMSImpl.java @@ -953,6 +953,7 @@ public class NMSImpl implements NMSBridge { @Override public void look(org.bukkit.entity.Entity entity, Location to, boolean headOnly, boolean immediate) { Entity handle = NMSImpl.getHandle(entity); + if (immediate || headOnly || BAD_CONTROLLER_LOOK.contains(handle.getBukkitEntity().getType()) || (!(handle instanceof Mob) && !(handle instanceof EntityHumanNPC))) { Location fromLocation = entity.getLocation(FROM_LOCATION);