diff --git a/main/pom.xml b/main/pom.xml
index 34f46e55f..a3da9f783 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -55,9 +55,9 @@
provided
- net.sf.trove4j
- trove4j
- 3.0.3
+ it.unimi.dsi
+ fastutil
+ 8.5.14
provided
@@ -202,8 +202,8 @@
- gnu.trove
- clib.trove
+ it.unimi.dsi
+ clib.fastutil
net.kyori
diff --git a/main/src/main/java/net/citizensnpcs/Citizens.java b/main/src/main/java/net/citizensnpcs/Citizens.java
index 5917ea4f9..40e17b20a 100644
--- a/main/src/main/java/net/citizensnpcs/Citizens.java
+++ b/main/src/main/java/net/citizensnpcs/Citizens.java
@@ -291,8 +291,8 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
// Unfortunately, transitive dependency management is not supported in this library.
lib.loadLibrary(
Library.builder().groupId("ch{}ethz{}globis{}phtree").artifactId("phtree").version("2.8.0").build());
- lib.loadLibrary(Library.builder().groupId("net{}sf{}trove4j").artifactId("trove4j").version("3.0.3")
- .relocate("gnu{}trove", "clib{}trove").build());
+ lib.loadLibrary(Library.builder().groupId("it{}unimi{}dsi").artifactId("fastutil").version("8.5.14")
+ .relocate("it{}unimi{}dsi", "clib{}fastutil").build());
lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-text-minimessage")
.version("4.17.0").relocate("net{}kyori", "clib{}net{}kyori").build());
lib.loadLibrary(Library.builder().groupId("net{}kyori").artifactId("adventure-api").version("4.17.0")
diff --git a/main/src/main/java/net/citizensnpcs/EventListen.java b/main/src/main/java/net/citizensnpcs/EventListen.java
index b63e9e76c..c9709680a 100644
--- a/main/src/main/java/net/citizensnpcs/EventListen.java
+++ b/main/src/main/java/net/citizensnpcs/EventListen.java
@@ -2,6 +2,7 @@ package net.citizensnpcs;
import java.lang.reflect.Method;
import java.util.List;
+import java.util.Objects;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@@ -65,7 +66,6 @@ import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import com.google.common.base.Joiner;
-import com.google.common.base.Predicates;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -222,7 +222,7 @@ public class EventListen implements Listener {
}
private Iterable getAllNPCs() {
- return Iterables.filter(Iterables.concat(CitizensAPI.getNPCRegistries()), Predicates.notNull());
+ return Iterables.filter(Iterables.concat(CitizensAPI.getNPCRegistries()), Objects::nonNull);
}
void loadNPCs(ChunkEvent event) {
diff --git a/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java b/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java
index c4cd484c6..396646344 100644
--- a/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java
+++ b/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java
@@ -14,6 +14,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -55,7 +56,6 @@ import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import com.google.common.base.Joiner;
-import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
@@ -2811,7 +2811,7 @@ public class NPCCommands {
}
}
List search = location.getWorld().getNearbyEntities(location, range, range, range).stream()
- .map(registry::getNPC).filter(Predicates.notNull()).collect(Collectors.toList());
+ .map(registry::getNPC).filter(Objects::nonNull).collect(Collectors.toList());
search.sort((o1, o2) -> Double.compare(o1.getEntity().getLocation().distanceSquared(location),
o2.getEntity().getLocation().distanceSquared(location)));
for (NPC test : search) {
diff --git a/main/src/main/java/net/citizensnpcs/npc/CitizensNPCRegistry.java b/main/src/main/java/net/citizensnpcs/npc/CitizensNPCRegistry.java
index 50cc649c1..2d7ee494c 100644
--- a/main/src/main/java/net/citizensnpcs/npc/CitizensNPCRegistry.java
+++ b/main/src/main/java/net/citizensnpcs/npc/CitizensNPCRegistry.java
@@ -17,7 +17,7 @@ import org.bukkit.inventory.ItemStack;
import com.google.common.collect.Maps;
-import gnu.trove.map.hash.TIntObjectHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.citizensnpcs.Settings.Setting;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.event.DespawnReason;
@@ -36,7 +36,7 @@ import net.citizensnpcs.util.NMS;
public class CitizensNPCRegistry implements NPCRegistry {
private final String name;
- private final TIntObjectHashMap npcs = new TIntObjectHashMap<>();
+ private final Int2ObjectOpenHashMap npcs = new Int2ObjectOpenHashMap<>();
private final NPCDataStore saves;
private final Map uniqueNPCs = Maps.newHashMap();
@@ -130,7 +130,7 @@ public class CitizensNPCRegistry implements NPCRegistry {
try {
npc.despawn(reason);
} catch (Throwable e) {
- e.printStackTrace(); // ensure that all entities are despawned
+ e.printStackTrace();
}
itr.remove();
}
@@ -200,7 +200,7 @@ public class CitizensNPCRegistry implements NPCRegistry {
@Override
public Iterator iterator() {
return new Iterator() {
- Iterator itr = npcs.valueCollection().iterator();
+ Iterator itr = npcs.values().iterator();
UUID lastUUID;
@Override
@@ -236,7 +236,7 @@ public class CitizensNPCRegistry implements NPCRegistry {
@Override
public Iterable sorted() {
- List vals = new ArrayList<>(npcs.valueCollection());
+ List vals = new ArrayList<>(npcs.values());
vals.sort(Comparator.comparing(NPC::getId));
return vals;
}
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 46ee39205..8e46ce6d8 100644
--- a/main/src/main/java/net/citizensnpcs/npc/skin/SkinUpdateTracker.java
+++ b/main/src/main/java/net/citizensnpcs/npc/skin/SkinUpdateTracker.java
@@ -21,7 +21,6 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
-import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
@@ -91,7 +90,7 @@ public class SkinUpdateTracker {
}
private Iterable getAllNPCs() {
- return Iterables.filter(Iterables.concat(CitizensAPI.getNPCRegistries()), Predicates.notNull());
+ return Iterables.filter(Iterables.concat(CitizensAPI.getNPCRegistries()), Objects::nonNull);
}
private List getNearbyNPCs(Player player, boolean reset, boolean checkFov) {
diff --git a/main/src/main/java/net/citizensnpcs/trait/SkinTrait.java b/main/src/main/java/net/citizensnpcs/trait/SkinTrait.java
index d0488436a..7787d2efd 100644
--- a/main/src/main/java/net/citizensnpcs/trait/SkinTrait.java
+++ b/main/src/main/java/net/citizensnpcs/trait/SkinTrait.java
@@ -3,6 +3,7 @@ package net.citizensnpcs.trait;
import java.util.Objects;
import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
import com.google.common.base.Charsets;
import com.google.common.io.BaseEncoding;
@@ -16,6 +17,8 @@ import net.citizensnpcs.api.util.Messaging;
import net.citizensnpcs.api.util.Placeholders;
import net.citizensnpcs.npc.skin.Skin;
import net.citizensnpcs.npc.skin.SkinnableEntity;
+import net.citizensnpcs.util.NMS;
+import net.citizensnpcs.util.SkinProperty;
@TraitName("skintrait")
public class SkinTrait extends Trait {
@@ -150,6 +153,17 @@ public class SkinTrait extends Trait {
skinName = ChatColor.stripColor(name);
}
+ /**
+ * Set skin data copying from a {@link Player}. Not subject to rate limiting from Mojang.
+ *
+ * @param player
+ * The player to copy
+ */
+ public void setSkinPersistent(Player player) {
+ SkinProperty sp = SkinProperty.fromMojangProfile(NMS.getProfile(player));
+ setSkinPersistent(sp.name, sp.signature, sp.value);
+ }
+
/**
* Sets the skin data directly, respawning the NPC if spawned.
*
diff --git a/main/src/main/java/net/citizensnpcs/trait/text/Text.java b/main/src/main/java/net/citizensnpcs/trait/text/Text.java
index ab2d01bb2..29b62bcac 100644
--- a/main/src/main/java/net/citizensnpcs/trait/text/Text.java
+++ b/main/src/main/java/net/citizensnpcs/trait/text/Text.java
@@ -1,5 +1,7 @@
package net.citizensnpcs.trait.text;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -47,6 +49,8 @@ public class Text extends Trait implements Runnable, Listener {
private double range = Setting.DEFAULT_TALK_CLOSE_RANGE.asDouble();
@Persist(value = "realistic-looking")
private boolean realisticLooker = Setting.DEFAULT_REALISTIC_LOOKING.asBoolean();
+ @Persist(value = "speech-bubble-duration")
+ private int speechBubbleDuration = Setting.DEFAULT_TEXT_SPEECH_BUBBLE_DURATION.asTicks();
@Persist(value = "speech-bubbles")
private boolean speechBubbles;
@Persist(value = "talk-close")
@@ -205,11 +209,9 @@ public class Text extends Trait implements Runnable, Listener {
}
if (speechBubbles) {
HologramTrait trait = npc.getOrAddTrait(HologramTrait.class);
- trait.addTemporaryLine(Placeholders.replace(text.get(index), player, npc),
- Setting.DEFAULT_TEXT_SPEECH_BUBBLE_DURATION.asTicks());
- } else {
- npc.getDefaultSpeechController().speak(new SpeechContext(text.get(index), player));
+ trait.addTemporaryLine(Placeholders.replace(text.get(index), player, npc), speechBubbleDuration);
}
+ npc.getDefaultSpeechController().speak(new SpeechContext(text.get(index), player));
return true;
}
@@ -242,6 +244,10 @@ public class Text extends Trait implements Runnable, Listener {
this.range = range;
}
+ public void setSpeechBubbleDuration(Duration duration) {
+ this.speechBubbleDuration = (int) (duration.get(ChronoUnit.MILLIS) / 50);
+ }
+
/**
* @return Whether talking close is enabled.
*/
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 c358faa00..9204a8df6 100644
--- a/main/src/main/java/net/citizensnpcs/trait/text/TextBasePrompt.java
+++ b/main/src/main/java/net/citizensnpcs/trait/text/TextBasePrompt.java
@@ -1,5 +1,6 @@
package net.citizensnpcs.trait.text;
+import java.time.Duration;
import java.util.Arrays;
import org.bukkit.ChatColor;
@@ -13,6 +14,7 @@ import com.google.common.base.Joiner;
import net.citizensnpcs.Settings.Setting;
import net.citizensnpcs.api.util.Messaging;
+import net.citizensnpcs.api.util.SpigotUtil;
import net.citizensnpcs.util.Messages;
public class TextBasePrompt extends StringPrompt {
@@ -76,6 +78,15 @@ public class TextBasePrompt extends StringPrompt {
text.toggleRealisticLooking();
} else if (original.trim().equalsIgnoreCase("speech bubbles")) {
text.toggleSpeechBubbles();
+ } else if (original.trim().startsWith("speech bubbles duration")) {
+ try {
+ Duration duration = SpigotUtil.parseDuration(original.replace("speech bubbles duration", "").trim(),
+ null);
+ text.setSpeechBubbleDuration(duration);
+ Messaging.sendErrorTr(sender, Messages.SPEECH_BUBBLES_DURATION_SET, duration);
+ } catch (Exception exception) {
+ Messaging.sendErrorTr(sender, Messages.INVALID_SPEECH_BUBBLES_DURATION);
+ }
} else if (input.equalsIgnoreCase("close") || original.trim().equalsIgnoreCase("talk close")) {
text.toggleTalkClose();
} else if (input.equalsIgnoreCase("range")) {
diff --git a/main/src/main/java/net/citizensnpcs/util/Messages.java b/main/src/main/java/net/citizensnpcs/util/Messages.java
index a44fa53be..25d0a4928 100644
--- a/main/src/main/java/net/citizensnpcs/util/Messages.java
+++ b/main/src/main/java/net/citizensnpcs/util/Messages.java
@@ -214,6 +214,7 @@ public class Messages {
public static final String INVALID_SKIN_FILE = "citizens.commands.npc.skin.invalid-file";
public static final String INVALID_SOUND = "citizens.commands.npc.sound.invalid-sound";
public static final String INVALID_SPAWN_LOCATION = "citizens.commands.npc.create.invalid-location";
+ public static final String INVALID_SPEECH_BUBBLES_DURATION = "citizens.commands.npc.text.invalid-speech-bubbles-duration";
public static final String INVALID_TRIGGER_TELEPORT_FORMAT = "citizens.editors.waypoints.triggers.teleport.invalid-format";
public static final String INVALID_TROPICALFISH_COLOR = "citizens.commands.npc.tropicalfish.invalid-color";
public static final String INVALID_TROPICALFISH_PATTERN = "citizens.commands.npc.tropicalfish.invalid-pattern";
@@ -399,6 +400,7 @@ public class Messages {
public static final String SNOWMAN_FORM_SNOW_STOPPED = "citizens.commands.npc.snowman.form-snow-stopped";
public static final String SOUND_INFO = "citizens.commands.npc.sound.info";
public static final String SOUND_SET = "citizens.commands.npc.sound.set";
+ public static final String SPEECH_BUBBLES_DURATION_SET = "citizens.commands.npc.text.speech-bubbles-duration-set";
public static final String SPEED_MODIFIER_SET = "citizens.commands.npc.speed.set";
public static final String SPEED_TRIGGER_PROMPT = "citizens.editors.waypoints.triggers.speed.prompt";
public static final String SPELL_SET = "citizens.commands.npc.spellcaster.spell-set";
diff --git a/main/src/main/resources/en.json b/main/src/main/resources/en.json
index 5cff1704d..72174844a 100644
--- a/main/src/main/resources/en.json
+++ b/main/src/main/resources/en.json
@@ -642,6 +642,8 @@
"citizens.commands.template.conflict" : "A template by that name already exists.",
"citizens.commands.template.list.description" : "Lists available templates",
"citizens.commands.template.list.header" : "]]Available templates:",
+ "citizens.commands.npc.text.invalid-speech-bubbles-duration" : "Invalid speech bubble duration.",
+ "citizens.commands.npc.text.speech-bubbles-duration-set" : "Speech bubble duration set to [[{0}]].",
"citizens.commands.template.list.help" : "",
"citizens.commands.template.missing" : "Template not found.",
"citizens.commands.template.namespace-already-exists" : "Namespace [[{0}]] already exists",
@@ -707,7 +709,7 @@
"citizens.editors.text.range-set" : "[[Range]] set to [[{0}]].",
"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 text | default to clear)\">item | range | delay
{0}talk close | {1}random | {2}speech bubbles | {3}realistic",
+ "citizens.editors.text.start-prompt" : "Add text | default to clear)\">item | range | delay
{0}talk close | {1}random | {2}speech bubble duration | {2}speech bubbles | {3}realistic",
"citizens.editors.text.talk-item-set" : "[[Talk item pattern]] set to [[{0}]].",
"citizens.editors.text.text-list-header" : "Current text:",
"citizens.editors.waypoints.guided.added-available" : "Added a [[destination]] waypoint which the NPC will randomly pathfind between.",