2017-12-30 08:36:36 +01:00
|
|
|
package net.citizensnpcs.trait.text;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Random;
|
|
|
|
import java.util.UUID;
|
|
|
|
|
|
|
|
import org.bukkit.GameMode;
|
2022-06-26 07:57:51 +02:00
|
|
|
import org.bukkit.command.CommandSender;
|
2017-12-30 08:36:36 +01:00
|
|
|
import org.bukkit.conversations.Conversation;
|
|
|
|
import org.bukkit.conversations.ConversationFactory;
|
|
|
|
import org.bukkit.entity.Player;
|
|
|
|
import org.bukkit.event.EventHandler;
|
|
|
|
import org.bukkit.event.Listener;
|
|
|
|
import org.bukkit.plugin.Plugin;
|
|
|
|
|
|
|
|
import com.google.common.collect.Maps;
|
|
|
|
|
|
|
|
import net.citizensnpcs.Settings.Setting;
|
|
|
|
import net.citizensnpcs.api.CitizensAPI;
|
|
|
|
import net.citizensnpcs.api.ai.speech.SpeechContext;
|
|
|
|
import net.citizensnpcs.api.event.NPCRightClickEvent;
|
|
|
|
import net.citizensnpcs.api.exception.NPCLoadException;
|
|
|
|
import net.citizensnpcs.api.trait.Trait;
|
|
|
|
import net.citizensnpcs.api.trait.TraitName;
|
|
|
|
import net.citizensnpcs.api.util.DataKey;
|
|
|
|
import net.citizensnpcs.api.util.Messaging;
|
|
|
|
import net.citizensnpcs.api.util.Paginator;
|
2022-08-21 10:15:40 +02:00
|
|
|
import net.citizensnpcs.api.util.Placeholders;
|
2017-12-30 08:36:36 +01:00
|
|
|
import net.citizensnpcs.editor.Editor;
|
2021-07-19 17:02:41 +02:00
|
|
|
import net.citizensnpcs.trait.HologramTrait;
|
2017-12-30 08:36:36 +01:00
|
|
|
import net.citizensnpcs.util.Messages;
|
|
|
|
import net.citizensnpcs.util.Util;
|
|
|
|
|
2019-05-16 13:58:29 +02:00
|
|
|
/**
|
|
|
|
* Persists text metadata, i.e. text that will be said by an NPC on certain triggers.
|
|
|
|
*/
|
2017-12-30 08:36:36 +01:00
|
|
|
@TraitName("text")
|
2022-12-12 16:51:19 +01:00
|
|
|
public class Text extends Trait implements Runnable, Listener {
|
2018-04-09 11:41:53 +02:00
|
|
|
private final Map<UUID, Long> cooldowns = Maps.newHashMap();
|
2017-12-30 08:36:36 +01:00
|
|
|
private int currentIndex;
|
2018-02-15 16:29:11 +01:00
|
|
|
private int delay = -1;
|
2017-12-30 08:36:36 +01:00
|
|
|
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();
|
2021-07-19 17:02:41 +02:00
|
|
|
private boolean speechBubbles;
|
2017-12-30 08:36:36 +01:00
|
|
|
private boolean talkClose = Setting.DEFAULT_TALK_CLOSE.asBoolean();
|
|
|
|
private final List<String> text = new ArrayList<String>();
|
|
|
|
|
|
|
|
public Text() {
|
|
|
|
super("text");
|
|
|
|
this.plugin = CitizensAPI.getPlugin();
|
|
|
|
}
|
|
|
|
|
2019-05-16 13:58:29 +02:00
|
|
|
/**
|
|
|
|
* Adds a piece of text that will be said by the NPC.
|
|
|
|
*
|
|
|
|
* @param string
|
|
|
|
* the text to say
|
|
|
|
*/
|
|
|
|
public void add(String string) {
|
2017-12-30 08:36:36 +01:00
|
|
|
text.add(string);
|
|
|
|
}
|
|
|
|
|
2019-05-16 13:58:29 +02:00
|
|
|
/**
|
|
|
|
* Edit the text at a given index to a new text.
|
|
|
|
*
|
|
|
|
* @param index
|
|
|
|
* the text's index
|
|
|
|
* @param newText
|
|
|
|
* the new text to use
|
|
|
|
*/
|
|
|
|
public void edit(int index, String newText) {
|
2017-12-30 08:36:36 +01:00
|
|
|
text.set(index, newText);
|
|
|
|
}
|
|
|
|
|
2019-05-16 13:58:29 +02:00
|
|
|
/**
|
|
|
|
* Builds a text editor in game for the supplied {@link Player}.
|
|
|
|
*/
|
2017-12-30 08:36:36 +01:00
|
|
|
public Editor getEditor(final Player player) {
|
2022-12-12 16:51:19 +01:00
|
|
|
final Conversation conversation = new ConversationFactory(plugin).withLocalEcho(false)
|
|
|
|
.withEscapeSequence("/npc text").withEscapeSequence("exit").withModality(false)
|
2021-07-19 17:02:41 +02:00
|
|
|
.withFirstPrompt(new TextBasePrompt(this)).buildConversation(player);
|
2017-12-30 08:36:36 +01:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-07-19 17:02:41 +02:00
|
|
|
String getPageText(int page) {
|
|
|
|
Paginator paginator = new Paginator().header("Current Texts");
|
2022-10-15 13:32:25 +02:00
|
|
|
for (int i = 0; i < text.size(); i++) {
|
2022-10-15 14:37:27 +02:00
|
|
|
paginator.addLine("<green>" + i + " <gray>- <yellow>" + text.get(i));
|
2022-10-15 13:32:25 +02:00
|
|
|
}
|
2021-07-19 17:02:41 +02:00
|
|
|
return paginator.getPageText(page);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return The list of all texts
|
|
|
|
*/
|
|
|
|
public List<String> getTexts() {
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
|
2019-05-16 13:58:29 +02:00
|
|
|
/**
|
|
|
|
* @return whether there is text at a certain index
|
|
|
|
*/
|
|
|
|
public boolean hasIndex(int index) {
|
2017-12-30 08:36:36 +01:00
|
|
|
return index >= 0 && text.size() > index;
|
|
|
|
}
|
|
|
|
|
2022-06-26 07:57:51 +02:00
|
|
|
boolean hasPage(int page) {
|
|
|
|
return new Paginator(text.size()).hasPage(page);
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isRandomTalker() {
|
|
|
|
return randomTalker;
|
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
@Override
|
|
|
|
public void load(DataKey key) throws NPCLoadException {
|
|
|
|
text.clear();
|
|
|
|
for (DataKey sub : key.getRelative("text").getIntegerSubKeys()) {
|
|
|
|
text.add(sub.getString(""));
|
|
|
|
}
|
2022-10-15 14:40:05 +02:00
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
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);
|
2018-02-15 16:29:11 +01:00
|
|
|
delay = key.getInt("delay", delay);
|
2022-06-26 05:47:40 +02:00
|
|
|
speechBubbles = key.getBoolean("speech-bubbles", speechBubbles);
|
2017-12-30 08:36:36 +01:00
|
|
|
itemInHandPattern = key.getString("talkitem", itemInHandPattern);
|
|
|
|
}
|
|
|
|
|
|
|
|
@EventHandler
|
2019-05-16 13:58:29 +02:00
|
|
|
private void onRightClick(NPCRightClickEvent event) {
|
2023-01-01 08:26:35 +01:00
|
|
|
if (!event.getNPC().equals(npc) || text.size() == 0)
|
2017-12-30 08:36:36 +01:00
|
|
|
return;
|
|
|
|
String localPattern = itemInHandPattern.equals("default") ? Setting.TALK_ITEM.asString() : itemInHandPattern;
|
|
|
|
if (Util.matchesItemInHand(event.getClicker(), localPattern) && !shouldTalkClose()) {
|
2023-01-01 08:26:35 +01:00
|
|
|
talk(event.getClicker());
|
2023-03-01 16:05:25 +01:00
|
|
|
event.setDelayedCancellation(true);
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void populateDefaultText() {
|
|
|
|
text.addAll(Setting.DEFAULT_TEXT.asList());
|
|
|
|
}
|
|
|
|
|
2019-05-16 13:58:29 +02:00
|
|
|
/**
|
|
|
|
* Remove text at a given index.
|
|
|
|
*/
|
|
|
|
public void remove(int index) {
|
2017-12-30 08:36:36 +01:00
|
|
|
text.remove(index);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
2023-01-01 08:26:35 +01:00
|
|
|
if (!npc.isSpawned() || !talkClose || text.size() == 0)
|
2017-12-30 08:36:36 +01:00
|
|
|
return;
|
2022-09-03 18:22:20 +02:00
|
|
|
|
2022-12-03 17:59:16 +01:00
|
|
|
for (Player player : CitizensAPI.getLocationLookup().getNearbyPlayers(npc.getEntity().getLocation(), range)) {
|
|
|
|
if (player.getGameMode() == GameMode.SPECTATOR)
|
2017-12-30 08:36:36 +01:00
|
|
|
continue;
|
2023-01-01 08:26:35 +01:00
|
|
|
talk(player);
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void save(DataKey key) {
|
2018-02-15 16:29:11 +01:00
|
|
|
key.setInt("delay", delay);
|
2017-12-30 08:36:36 +01:00
|
|
|
key.setBoolean("talk-close", talkClose);
|
|
|
|
key.setBoolean("random-talker", randomTalker);
|
|
|
|
key.setBoolean("realistic-looking", realisticLooker);
|
|
|
|
key.setDouble("range", range);
|
|
|
|
key.setString("talkitem", itemInHandPattern);
|
2022-06-26 05:47:40 +02:00
|
|
|
key.setBoolean("speech-bubbles", speechBubbles);
|
2017-12-30 08:36:36 +01:00
|
|
|
key.removeKey("text");
|
2021-07-19 17:02:41 +02:00
|
|
|
for (int i = 0; i < text.size(); i++) {
|
2017-12-30 08:36:36 +01:00
|
|
|
key.setString("text." + String.valueOf(i), text.get(i));
|
2021-07-19 17:02:41 +02:00
|
|
|
}
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
|
2022-06-26 07:57:51 +02:00
|
|
|
boolean sendPage(CommandSender player, int page) {
|
2022-12-17 04:22:14 +01:00
|
|
|
Paginator paginator = new Paginator().header("Current Texts").enablePageSwitcher("/npc text page $page");
|
2022-08-17 17:33:29 +02:00
|
|
|
for (int i = 0; i < text.size(); i++) {
|
2022-10-16 04:41:34 +02:00
|
|
|
paginator.addLine(text.get(i) + " <green>(<click:suggest_command:edit " + i
|
2022-10-26 06:33:23 +02:00
|
|
|
+ " ><yellow>edit</click>) (<hover:show_text:Remove this text><click:run_command:/npc text remove "
|
|
|
|
+ i + "><red>-</click></hover>)");
|
2022-08-17 17:33:29 +02:00
|
|
|
}
|
2017-12-30 08:36:36 +01:00
|
|
|
return paginator.sendPage(player, page);
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean sendText(Player player) {
|
|
|
|
if (text.size() == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
int index = 0;
|
2021-07-19 17:02:41 +02:00
|
|
|
if (randomTalker) {
|
2017-12-30 08:36:36 +01:00
|
|
|
index = RANDOM.nextInt(text.size());
|
2021-07-19 17:02:41 +02:00
|
|
|
} else {
|
|
|
|
if (currentIndex > text.size() - 1) {
|
2017-12-30 08:36:36 +01:00
|
|
|
currentIndex = 0;
|
2021-07-19 17:02:41 +02:00
|
|
|
}
|
2017-12-30 08:36:36 +01:00
|
|
|
index = currentIndex++;
|
|
|
|
}
|
|
|
|
|
2021-07-19 17:02:41 +02:00
|
|
|
if (speechBubbles) {
|
|
|
|
HologramTrait trait = npc.getOrAddTrait(HologramTrait.class);
|
2022-09-03 18:22:20 +02:00
|
|
|
trait.addTemporaryLine(Placeholders.replace(text.get(index), player),
|
2023-03-12 15:29:41 +01:00
|
|
|
Setting.DEFAULT_TEXT_SPEECH_BUBBLE_DURATION.asTicks());
|
2021-07-19 17:02:41 +02:00
|
|
|
} else {
|
|
|
|
npc.getDefaultSpeechController().speak(new SpeechContext(text.get(index), player));
|
|
|
|
}
|
2017-12-30 08:36:36 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-05-16 13:58:29 +02:00
|
|
|
/**
|
|
|
|
* Set the text delay between messages.
|
|
|
|
*
|
|
|
|
* @param delay
|
|
|
|
* the delay in ticks
|
|
|
|
*/
|
|
|
|
public void setDelay(int delay) {
|
2018-02-15 16:29:11 +01:00
|
|
|
this.delay = delay;
|
|
|
|
}
|
|
|
|
|
2021-07-19 17:02:41 +02:00
|
|
|
/**
|
|
|
|
* Sets the item in hand pattern required to talk to NPCs, if enabled.
|
|
|
|
*
|
|
|
|
* @param pattern
|
|
|
|
* The new pattern
|
|
|
|
*/
|
|
|
|
public void setItemInHandPattern(String pattern) {
|
2017-12-30 08:36:36 +01:00
|
|
|
itemInHandPattern = pattern;
|
|
|
|
}
|
|
|
|
|
2019-05-16 13:58:29 +02:00
|
|
|
/**
|
|
|
|
* Set the range in blocks before text will be sent.
|
|
|
|
*
|
|
|
|
* @param range
|
|
|
|
*/
|
|
|
|
public void setRange(double range) {
|
2017-12-30 08:36:36 +01:00
|
|
|
this.range = range;
|
|
|
|
}
|
|
|
|
|
2021-07-19 17:02:41 +02:00
|
|
|
/**
|
|
|
|
* @return Whether talking close is enabled.
|
|
|
|
*/
|
|
|
|
public boolean shouldTalkClose() {
|
2017-12-30 08:36:36 +01:00
|
|
|
return talkClose;
|
|
|
|
}
|
|
|
|
|
2023-01-01 08:26:35 +01:00
|
|
|
private void talk(Player player) {
|
|
|
|
Long cooldown = cooldowns.get(player.getUniqueId());
|
|
|
|
if (cooldown != null) {
|
|
|
|
if (System.currentTimeMillis() < cooldown)
|
|
|
|
return;
|
|
|
|
|
|
|
|
cooldowns.remove(player.getUniqueId());
|
|
|
|
}
|
|
|
|
|
|
|
|
sendText(player);
|
|
|
|
|
2023-01-02 01:37:27 +01:00
|
|
|
int delay = this.delay == -1
|
2023-01-26 18:54:13 +01:00
|
|
|
? Setting.DEFAULT_TEXT_DELAY_MIN.asInt() + Util.getFastRandom()
|
2023-03-12 15:29:41 +01:00
|
|
|
.nextInt(Setting.DEFAULT_TEXT_DELAY_MAX.asTicks() - Setting.DEFAULT_TEXT_DELAY_MIN.asTicks())
|
2023-01-02 01:37:27 +01:00
|
|
|
: this.delay;
|
2023-01-01 08:26:35 +01:00
|
|
|
if (delay <= 0)
|
|
|
|
return;
|
|
|
|
cooldowns.put(player.getUniqueId(), System.currentTimeMillis() + (delay * 50));
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
|
2019-05-16 13:58:29 +02:00
|
|
|
/**
|
|
|
|
* Toggles talking at random intervals.
|
|
|
|
*/
|
|
|
|
public boolean toggleRandomTalker() {
|
2017-12-30 08:36:36 +01:00
|
|
|
return (randomTalker = !randomTalker);
|
|
|
|
}
|
|
|
|
|
2019-05-16 13:58:29 +02:00
|
|
|
/**
|
|
|
|
* Toggles requiring line of sight before talking.
|
|
|
|
*/
|
|
|
|
public boolean toggleRealisticLooking() {
|
2017-12-30 08:36:36 +01:00
|
|
|
return (realisticLooker = !realisticLooker);
|
|
|
|
}
|
|
|
|
|
2021-07-19 17:02:41 +02:00
|
|
|
/**
|
|
|
|
* Toggles using speech bubbles instead of messages.
|
|
|
|
*/
|
|
|
|
public boolean toggleSpeechBubbles() {
|
|
|
|
return (speechBubbles = !speechBubbles);
|
2017-12-30 08:36:36 +01:00
|
|
|
}
|
|
|
|
|
2022-06-26 07:57:51 +02:00
|
|
|
/**
|
|
|
|
* Toggles talking to nearby Players.
|
|
|
|
*/
|
|
|
|
public boolean toggleTalkClose() {
|
|
|
|
return (talkClose = !talkClose);
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean useRealisticLooking() {
|
|
|
|
return realisticLooker;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean useSpeechBubbles() {
|
|
|
|
return speechBubbles;
|
|
|
|
}
|
|
|
|
|
2017-12-30 08:36:36 +01:00
|
|
|
private static Random RANDOM = Util.getFastRandom();
|
2012-03-02 21:42:34 +01:00
|
|
|
}
|