From 2be6437a63cca997b57b1b4bcb0ba2d334606bb5 Mon Sep 17 00:00:00 2001 From: Jeremy Schroeder Date: Sat, 22 Dec 2012 22:44:33 -0500 Subject: [PATCH] Updates to SpeechController. (Not working for some reason, will debug tomorrow.) --- pom.xml | 2 +- src/main/java/net/citizensnpcs/Settings.java | 1 - .../net/citizensnpcs/npc/ai/speech/Chat.java | 113 +++-- .../npc/ai/speech/CitizensSpeechFactory.java | 4 +- .../net/citizensnpcs/trait/text/Text.java | 467 +++++++++--------- 5 files changed, 308 insertions(+), 279 deletions(-) diff --git a/pom.xml b/pom.xml index 7e147c3e2..ca677bb43 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ UTF-8 - LATEST + 1.4.5-R1.0 2.0.6-SNAPSHOT 1.2.19-SNAPSHOT 1.4.12 diff --git a/src/main/java/net/citizensnpcs/Settings.java b/src/main/java/net/citizensnpcs/Settings.java index 0bf6f4dc7..06ba0030a 100644 --- a/src/main/java/net/citizensnpcs/Settings.java +++ b/src/main/java/net/citizensnpcs/Settings.java @@ -46,7 +46,6 @@ public class Settings { CHAT_FORMAT("npc.chat.format.no-targets", "[]: "), CHAT_FORMAT_TO_TARGET("npc.chat.format.to-target", "[] -> You: "), CHAT_FORMAT_TO_BYSTANDERS("npc.chat.prefix.to-bystanders", "[] -> []: "), - CHAT_FORMAT_WITH_TARGETS_TO_TARGET("npc.chat.format.with-target-to-target", "[] -> []: "), CHAT_FORMAT_WITH_TARGETS_TO_BYSTANDERS("npc.chat.format.with-target-to-bystanders", "[] -> []: "), CHAT_RANGE("npc.chat.options.range", 5), CHAT_BYSTANDERS_HEAR_TARGETED_CHAT("npc.chat.options.bystanders-hear-targeted-chat", true), diff --git a/src/main/java/net/citizensnpcs/npc/ai/speech/Chat.java b/src/main/java/net/citizensnpcs/npc/ai/speech/Chat.java index 5b42cb394..39ac3d4ce 100644 --- a/src/main/java/net/citizensnpcs/npc/ai/speech/Chat.java +++ b/src/main/java/net/citizensnpcs/npc/ai/speech/Chat.java @@ -1,6 +1,8 @@ package net.citizensnpcs.npc.ai.speech; +import java.util.Collections; import java.util.List; +import java.util.logging.Level; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; @@ -9,79 +11,102 @@ import net.citizensnpcs.Settings.Setting; import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.ai.speech.Talkable; import net.citizensnpcs.api.ai.speech.TalkableEntity; -import net.citizensnpcs.api.ai.speech.Tongue; +import net.citizensnpcs.api.ai.speech.SpeechContext; import net.citizensnpcs.api.ai.speech.VocalChord; import net.citizensnpcs.api.npc.NPC; +import net.citizensnpcs.util.Messaging; public class Chat implements VocalChord { - /* - CHAT_FORMAT("npc.chat.format.no-targets", "[]: "), - CHAT_FORMAT_TO_TARGET("npc.chat.format.to-target", "[] -> You: "), - CHAT_FORMAT_TO_BYSTANDERS("npc.chat.prefix.to-bystanders", "[] -> []: "), - CHAT_FORMAT_WITH_TARGETS_TO_BYSTANDERS("npc.chat.format.with-target-to-bystanders", "[] -> []: "), - CHAT_RANGE("npc.chat.options.range", 5), - CHAT_BYSTANDERS_HEAR_TARGETED_CHAT("npc.chat.options.bystanders-hear-targeted-chat", true), - CHAT_MAX_NUMBER_OF_TARGETS("npc.chat.options.max-number-of-targets-to-show", 2), - CHAT_MULTIPLE_TARGETS_FORMAT("npc.chat.options.multiple-targets-format", ",||& |& others"), - */ - public final String VOCAL_CHORD_NAME = "chat"; - - @Override - public void talk(Tongue tongue) { - NPC npc = CitizensAPI.getNPCRegistry().getNPC(tongue.getTalker().getEntity()); + @Override + public void talk(SpeechContext context) { + + NPC npc = CitizensAPI.getNPCRegistry().getNPC(context.getTalker().getEntity()); // If no recipients, chat to the world with CHAT_FORMAT and CHAT_RANGE settings - if (!tongue.isTargeted()) { - String text = Setting.CHAT_FORMAT.asString().replace("", npc.getName()).replace("", tongue.getContents()); - talkToBystanders(npc, text, tongue); + if (!context.hasRecipients()) { + String text = Setting.CHAT_FORMAT.asString().replace("", npc.getName()).replace("", context.getMessage()); + talkToBystanders(npc, text, context); return; } // Assumed recipients at this point - else if (tongue.getRecipients().size() <= 1) { // One recipient - String text = Setting.CHAT_FORMAT_TO_TARGET.asString().replace("", npc.getName()).replace("", tongue.getContents()); - tongue.getRecipients().get(0).talkTo(tongue, text, this); + else if (context.size() <= 1) { // One recipient + String text = Setting.CHAT_FORMAT_TO_TARGET.asString().replace("", npc.getName()).replace("", context.getMessage()); + String targetName = ""; + // For each recipient + for (Talkable entity : context) { + entity.talkTo(context, text, this); + targetName = entity.getName(); + } + // Check if bystanders hear targeted chat if (!Setting.CHAT_BYSTANDERS_HEAR_TARGETED_CHAT.asBoolean()) return; - String bystanderText = Setting.CHAT_FORMAT_TO_BYSTANDERS.asString().replace("", npc.getName()).replace("", tongue.getRecipients().get(0).getName()).replace("", tongue.getContents()); - talkToBystanders(npc, bystanderText, tongue); + // Format message with config setting and send to bystanders + String bystanderText = Setting.CHAT_FORMAT_TO_BYSTANDERS.asString().replace("", npc.getName()).replace("", targetName).replace("", context.getMessage()); + talkToBystanders(npc, bystanderText, context); return; } - - else { // Multiple recipients - // Set up text - String text = Setting.CHAT_FORMAT_TO_TARGET.asString().replace("", npc.getName()).replace("", tongue.getContents()); - tongue.getRecipients().get(0).talkTo(tongue, text, this); - if (!Setting.CHAT_BYSTANDERS_HEAR_TARGETED_CHAT.asBoolean()) return; - String bystanders = null; - bystanders = bystanders + ""; - String bystanderText = Setting.CHAT_FORMAT_WITH_TARGETS_TO_BYSTANDERS.asString().replace("", npc.getName()).replace("", tongue.getRecipients().get(0).getName()).replace("", tongue.getContents()); - talkToBystanders(npc, bystanderText, tongue); - // TODO: Finish multiple recipients - + else { // Multiple recipients + String text = Setting.CHAT_FORMAT_TO_TARGET.asString().replace("", npc.getName()).replace("", context.getMessage()); + List targetNames = Collections.emptyList(); + // Talk to each recipient + for (Talkable entity : context) { + entity.talkTo(context, text, this); + targetNames.add(entity.getName()); + } + + if (!Setting.CHAT_BYSTANDERS_HEAR_TARGETED_CHAT.asBoolean()) return; + String targets = ""; + int max = Setting.CHAT_MAX_NUMBER_OF_TARGETS.asInt(); + String[] format = Setting.CHAT_FORMAT_WITH_TARGETS_TO_BYSTANDERS.asString().split("\\|"); + if (format.length != 4) Messaging.log(Level.WARNING, "npc.chat.format.with-target-to-bystanders invalid!"); + if (max == 1) { + targets = format[0].replace("", targetNames.get(0)) + format[3]; + } + else if (max == 2 || targetNames.size() == 2) { + if (targetNames.size() == 2) + targets = format[0].replace("", targetNames.get(0)) + format[2].replace("", targetNames.get(1)); + else + targets = format[0].replace("", targetNames.get(0)) + format[1].replace("", targetNames.get(1)) + format[3]; + } + else if (max >= 3) { + targets = format[0].replace("", targetNames.get(0)); + + int x = 1; + for (x = 1; x < max - 1; x++) { + if (targetNames.size() - 1 == x) break; + targets = targets + format[1].replace("", targetNames.get(x)); + } + if (targetNames.size() == max) + targets = targets + format[2].replace("", targetNames.get(x)); + else targets = targets + format[3]; + } + + String bystanderText = Setting.CHAT_FORMAT_WITH_TARGETS_TO_BYSTANDERS.asString().replace("", npc.getName()).replace("", targets).replace("", context.getMessage()); + talkToBystanders(npc, bystanderText, context); + } } - private void talkToBystanders(NPC npc, String text, Tongue tongue) { + private void talkToBystanders(NPC npc, String text, SpeechContext context) { // Get list of nearby entities List bystanderEntities = npc.getBukkitEntity().getNearbyEntities(Setting.CHAT_RANGE.asDouble(), Setting.CHAT_RANGE.asDouble(), Setting.CHAT_RANGE.asDouble()); for (Entity bystander : bystanderEntities) // Continue if a LivingEntity, which is compatible with TalkableEntity if (bystander instanceof LivingEntity) { - // Exclude Targets - if (tongue.isTargeted()) { - for (Talkable target : tongue.getRecipients()) + // Exclude targeted recipients + if (context.hasRecipients()) { + for (Talkable target : context) if (target.getEntity() == bystander) continue; } else - // Found a nearby LivingEntity, make it Talkable and talkNear it - new TalkableEntity((LivingEntity) bystander).talkNear(tongue, text, this); + // Found a nearby LivingEntity, make it Talkable and talkNear it + new TalkableEntity((LivingEntity) bystander).talkNear(context, text, this); } } - - + @Override public String getName() { return VOCAL_CHORD_NAME; diff --git a/src/main/java/net/citizensnpcs/npc/ai/speech/CitizensSpeechFactory.java b/src/main/java/net/citizensnpcs/npc/ai/speech/CitizensSpeechFactory.java index 9564045d7..983017cc1 100644 --- a/src/main/java/net/citizensnpcs/npc/ai/speech/CitizensSpeechFactory.java +++ b/src/main/java/net/citizensnpcs/npc/ai/speech/CitizensSpeechFactory.java @@ -1,6 +1,6 @@ package net.citizensnpcs.npc.ai.speech; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -9,7 +9,7 @@ import net.citizensnpcs.api.ai.speech.VocalChord; public class CitizensSpeechFactory implements SpeechFactory { - Map> registered = Collections.emptyMap(); + Map> registered = new HashMap>(); @Override public void register(Class clazz, String name) { diff --git a/src/main/java/net/citizensnpcs/trait/text/Text.java b/src/main/java/net/citizensnpcs/trait/text/Text.java index d9ef72d95..8013d2275 100644 --- a/src/main/java/net/citizensnpcs/trait/text/Text.java +++ b/src/main/java/net/citizensnpcs/trait/text/Text.java @@ -1,232 +1,237 @@ -package net.citizensnpcs.trait.text; - -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.TimeUnit; - -import net.citizensnpcs.Settings.Setting; -import net.citizensnpcs.api.CitizensAPI; -import net.citizensnpcs.api.event.NPCRightClickEvent; -import net.citizensnpcs.api.exception.NPCLoadException; -import net.citizensnpcs.api.trait.Trait; -import net.citizensnpcs.api.util.DataKey; -import net.citizensnpcs.editor.Editor; -import net.citizensnpcs.trait.Toggleable; -import net.citizensnpcs.util.Messages; -import net.citizensnpcs.util.Messaging; -import net.citizensnpcs.util.Paginator; -import net.citizensnpcs.util.Util; - -import org.bukkit.Bukkit; -import org.bukkit.conversations.Conversation; -import org.bukkit.conversations.ConversationAbandonedEvent; -import org.bukkit.conversations.ConversationAbandonedListener; -import org.bukkit.conversations.ConversationFactory; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.plugin.Plugin; - -public class Text extends Trait implements Runnable, Toggleable, Listener, ConversationAbandonedListener { - private final Map cooldowns = new HashMap(); - private int currentIndex; - private String itemInHandPattern = "default"; - private final Plugin plugin; - private boolean randomTalker = Setting.DEFAULT_RANDOM_TALKER.asBoolean(); - private double range = Setting.DEFAULT_TALK_CLOSE_RANGE.asDouble(); - private boolean realisticLooker = Setting.DEFAULT_REALISTIC_LOOKING.asBoolean(); - private boolean talkClose = Setting.DEFAULT_TALK_CLOSE.asBoolean(); - private final List text = new ArrayList(); - - public Text() { - super("text"); - this.plugin = CitizensAPI.getPlugin(); - } - - void add(String string) { - text.add(string); - } - - @Override - public void conversationAbandoned(ConversationAbandonedEvent event) { - Bukkit.dispatchCommand((Player) event.getContext().getForWhom(), "npc text"); - } - - void edit(int index, String newText) { - text.set(index, newText); - } - - public Editor getEditor(final Player player) { - final Conversation conversation = new ConversationFactory(plugin) - .addConversationAbandonedListener(this).withLocalEcho(false).withEscapeSequence("/npc text") - .withEscapeSequence("exit").withModality(false).withFirstPrompt(new TextStartPrompt(this)) - .buildConversation(player); - return new Editor() { - - @Override - public void begin() { - Messaging.sendTr(player, Messages.TEXT_EDITOR_BEGIN); - conversation.begin(); - } - - @Override - public void end() { - Messaging.sendTr(player, Messages.TEXT_EDITOR_END); - conversation.abandon(); - } - }; - } - - boolean hasIndex(int index) { - return index >= 0 && text.size() > index; - } - - @Override - public void load(DataKey key) throws NPCLoadException { - text.clear(); - // TODO: legacy, remove later - for (DataKey sub : key.getIntegerSubKeys()) - text.add(sub.getString("")); - for (DataKey sub : key.getRelative("text").getIntegerSubKeys()) - text.add(sub.getString("")); - if (text.isEmpty()) - populateDefaultText(); - - talkClose = key.getBoolean("talk-close", talkClose); - realisticLooker = key.getBoolean("realistic-looking", realisticLooker); - randomTalker = key.getBoolean("random-talker", randomTalker); - range = key.getDouble("range", range); - itemInHandPattern = key.getString("talkitem", itemInHandPattern); - } - - @EventHandler - public void onRightClick(NPCRightClickEvent event) { - if (!event.getNPC().equals(npc)) - return; - String localPattern = itemInHandPattern.equals("default") ? Setting.TALK_ITEM.asString() - : itemInHandPattern; - if (Util.matchesItemInHand(event.getClicker(), localPattern) && !shouldTalkClose()) - sendText(event.getClicker()); - } - - private void populateDefaultText() { - text.addAll(Setting.DEFAULT_TEXT.asList()); - } - - void remove(int index) { - text.remove(index); - } - - @Override - public void run() { - if (!talkClose || !npc.isSpawned()) - return; - List nearby = npc.getBukkitEntity().getNearbyEntities(range, range, range); - for (Entity search : nearby) { - if (!(search instanceof Player)) - continue; - Player player = (Player) search; - // If the cooldown is not expired, do not send text - Date cooldown = cooldowns.get(player.getName()); - if (cooldown != null) { - if (!new Date().after(cooldown)) - return; - cooldowns.remove(player.getName()); - } - if (!sendText(player)) - return; - // Add a cooldown if the text was successfully sent - Date wait = new Date(); - int secondsDelta = RANDOM.nextInt(Setting.TALK_CLOSE_MAXIMUM_COOLDOWN.asInt()) - + Setting.TALK_CLOSE_MINIMUM_COOLDOWN.asInt(); - if (secondsDelta <= 0) - return; - long millisecondsDelta = TimeUnit.MILLISECONDS.convert(secondsDelta, TimeUnit.SECONDS); - wait.setTime(wait.getTime() + millisecondsDelta); - cooldowns.put(player.getName(), wait); - } - } - - @Override - public void save(DataKey key) { - key.setBoolean("talk-close", talkClose); - key.setBoolean("random-talker", randomTalker); - key.setBoolean("realistic-looking", realisticLooker); - key.setDouble("range", range); - key.setString("talkitem", itemInHandPattern); - // TODO: legacy, remove later - for (int i = 0; i < 100; i++) - key.removeKey(String.valueOf(i)); - key.removeKey("text"); - for (int i = 0; i < text.size(); i++) - key.setString("text." + String.valueOf(i), text.get(i)); - } - - boolean sendPage(Player player, int page) { - Paginator paginator = new Paginator().header(npc.getName() + "'s Text Entries"); - for (int i = 0; i < text.size(); i++) - paginator.addLine("" + i + " <7>- " + text.get(i)); - - return paginator.sendPage(player, page); - } - - private boolean sendText(Player player) { - if (!player.hasPermission("citizens.admin") && !player.hasPermission("citizens.npc.talk")) - return false; - if (text.size() == 0) - return false; - - int index = 0; - if (randomTalker) - index = new Random().nextInt(text.size()); - else { - if (currentIndex > text.size() - 1) - currentIndex = 0; - index = currentIndex++; - } - Messaging.sendWithNPC(player, Setting.CHAT_PREFIX.asString() + text.get(index), npc); - return true; - } - - void setItemInHandPattern(String pattern) { - itemInHandPattern = pattern; - } - - void setRange(double range) { - this.range = range; - } - - boolean shouldTalkClose() { - return talkClose; - } - - @Override - public boolean toggle() { - return (talkClose = !talkClose); - } - - boolean toggleRandomTalker() { - return (randomTalker = !randomTalker); - } - - boolean toggleRealisticLooking() { - return (realisticLooker = !realisticLooker); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Text{talk-close=" + talkClose + ",text="); - for (String line : text) - builder.append(line + ","); - builder.append("}"); - return builder.toString(); - } - - private static Random RANDOM = Util.getFastRandom(); +package net.citizensnpcs.trait.text; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import net.citizensnpcs.Settings.Setting; +import net.citizensnpcs.api.CitizensAPI; +import net.citizensnpcs.api.ai.speech.SpeechContext; +import net.citizensnpcs.api.ai.speech.Talkable; +import net.citizensnpcs.api.ai.speech.TalkableEntity; +import net.citizensnpcs.api.event.NPCRightClickEvent; +import net.citizensnpcs.api.exception.NPCLoadException; +import net.citizensnpcs.api.trait.Trait; +import net.citizensnpcs.api.util.DataKey; +import net.citizensnpcs.editor.Editor; +import net.citizensnpcs.trait.Toggleable; +import net.citizensnpcs.util.Messages; +import net.citizensnpcs.util.Messaging; +import net.citizensnpcs.util.Paginator; +import net.citizensnpcs.util.Util; + +import org.bukkit.Bukkit; +import org.bukkit.conversations.Conversation; +import org.bukkit.conversations.ConversationAbandonedEvent; +import org.bukkit.conversations.ConversationAbandonedListener; +import org.bukkit.conversations.ConversationFactory; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public class Text extends Trait implements Runnable, Toggleable, Listener, ConversationAbandonedListener { + private final Map cooldowns = new HashMap(); + private int currentIndex; + private String itemInHandPattern = "default"; + private final Plugin plugin; + private boolean randomTalker = Setting.DEFAULT_RANDOM_TALKER.asBoolean(); + private double range = Setting.DEFAULT_TALK_CLOSE_RANGE.asDouble(); + private boolean realisticLooker = Setting.DEFAULT_REALISTIC_LOOKING.asBoolean(); + private boolean talkClose = Setting.DEFAULT_TALK_CLOSE.asBoolean(); + private final List text = new ArrayList(); + + public Text() { + super("text"); + this.plugin = CitizensAPI.getPlugin(); + } + + void add(String string) { + text.add(string); + } + + @Override + public void conversationAbandoned(ConversationAbandonedEvent event) { + Bukkit.dispatchCommand((Player) event.getContext().getForWhom(), "npc text"); + } + + void edit(int index, String newText) { + text.set(index, newText); + } + + public Editor getEditor(final Player player) { + final Conversation conversation = new ConversationFactory(plugin) + .addConversationAbandonedListener(this).withLocalEcho(false).withEscapeSequence("/npc text") + .withEscapeSequence("exit").withModality(false).withFirstPrompt(new TextStartPrompt(this)) + .buildConversation(player); + return new Editor() { + + @Override + public void begin() { + Messaging.sendTr(player, Messages.TEXT_EDITOR_BEGIN); + conversation.begin(); + } + + @Override + public void end() { + Messaging.sendTr(player, Messages.TEXT_EDITOR_END); + conversation.abandon(); + } + }; + } + + boolean hasIndex(int index) { + return index >= 0 && text.size() > index; + } + + @Override + public void load(DataKey key) throws NPCLoadException { + text.clear(); + // TODO: legacy, remove later + for (DataKey sub : key.getIntegerSubKeys()) + text.add(sub.getString("")); + for (DataKey sub : key.getRelative("text").getIntegerSubKeys()) + text.add(sub.getString("")); + if (text.isEmpty()) + populateDefaultText(); + + talkClose = key.getBoolean("talk-close", talkClose); + realisticLooker = key.getBoolean("realistic-looking", realisticLooker); + randomTalker = key.getBoolean("random-talker", randomTalker); + range = key.getDouble("range", range); + itemInHandPattern = key.getString("talkitem", itemInHandPattern); + } + + @EventHandler + public void onRightClick(NPCRightClickEvent event) { + if (!event.getNPC().equals(npc)) + return; + String localPattern = itemInHandPattern.equals("default") ? Setting.TALK_ITEM.asString() + : itemInHandPattern; + if (Util.matchesItemInHand(event.getClicker(), localPattern) && !shouldTalkClose()) + sendText(event.getClicker()); + } + + private void populateDefaultText() { + text.addAll(Setting.DEFAULT_TEXT.asList()); + } + + void remove(int index) { + text.remove(index); + } + + @Override + public void run() { + if (!talkClose || !npc.isSpawned()) + return; + List nearby = npc.getBukkitEntity().getNearbyEntities(range, range, range); + for (Entity search : nearby) { + if (!(search instanceof Player)) + continue; + Player player = (Player) search; + // If the cooldown is not expired, do not send text + Date cooldown = cooldowns.get(player.getName()); + if (cooldown != null) { + if (!new Date().after(cooldown)) + return; + cooldowns.remove(player.getName()); + } + if (!sendText(player)) + return; + // Add a cooldown if the text was successfully sent + Date wait = new Date(); + int secondsDelta = RANDOM.nextInt(Setting.TALK_CLOSE_MAXIMUM_COOLDOWN.asInt()) + + Setting.TALK_CLOSE_MINIMUM_COOLDOWN.asInt(); + if (secondsDelta <= 0) + return; + long millisecondsDelta = TimeUnit.MILLISECONDS.convert(secondsDelta, TimeUnit.SECONDS); + wait.setTime(wait.getTime() + millisecondsDelta); + cooldowns.put(player.getName(), wait); + } + } + + @Override + public void save(DataKey key) { + key.setBoolean("talk-close", talkClose); + key.setBoolean("random-talker", randomTalker); + key.setBoolean("realistic-looking", realisticLooker); + key.setDouble("range", range); + key.setString("talkitem", itemInHandPattern); + // TODO: legacy, remove later + for (int i = 0; i < 100; i++) + key.removeKey(String.valueOf(i)); + key.removeKey("text"); + for (int i = 0; i < text.size(); i++) + key.setString("text." + String.valueOf(i), text.get(i)); + } + + boolean sendPage(Player player, int page) { + Paginator paginator = new Paginator().header(npc.getName() + "'s Text Entries"); + for (int i = 0; i < text.size(); i++) + paginator.addLine("" + i + " <7>- " + text.get(i)); + + return paginator.sendPage(player, page); + } + + private boolean sendText(Player player) { + if (!player.hasPermission("citizens.admin") && !player.hasPermission("citizens.npc.talk")) + return false; + if (text.size() == 0) + return false; + + int index = 0; + if (randomTalker) + index = new Random().nextInt(text.size()); + else { + if (currentIndex > text.size() - 1) + currentIndex = 0; + index = currentIndex++; + } + + npc.getDefaultSpeechController().speak(new SpeechContext(text.get(index), new TalkableEntity(player))); + // Messaging.sendWithNPC(player, Setting.CHAT_PREFIX.asString() + text.get(index), npc); + return true; + } + + void setItemInHandPattern(String pattern) { + itemInHandPattern = pattern; + } + + void setRange(double range) { + this.range = range; + } + + boolean shouldTalkClose() { + return talkClose; + } + + @Override + public boolean toggle() { + return (talkClose = !talkClose); + } + + boolean toggleRandomTalker() { + return (randomTalker = !randomTalker); + } + + boolean toggleRealisticLooking() { + return (realisticLooker = !realisticLooker); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Text{talk-close=" + talkClose + ",text="); + for (String line : text) + builder.append(line + ","); + builder.append("}"); + return builder.toString(); + } + + private static Random RANDOM = Util.getFastRandom(); } \ No newline at end of file