Citizens2/main/src/main/java/net/citizensnpcs/trait/text/Text.java

316 lines
9.6 KiB
Java

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.command.CommandSender;
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.persistence.Persist;
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;
import net.citizensnpcs.api.util.Placeholders;
import net.citizensnpcs.editor.Editor;
import net.citizensnpcs.trait.HologramTrait;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.Util;
/**
* Persists text metadata, i.e. text that will be said by an NPC on certain triggers.
*/
@TraitName("text")
public class Text extends Trait implements Runnable, Listener {
private final Map<UUID, Long> cooldowns = Maps.newHashMap();
private int currentIndex;
@Persist
private int delay = -1;
@Persist(value = "talkitem")
private String itemInHandPattern = "default";
private final Plugin plugin;
@Persist(value = "random-talker")
private boolean randomTalker = Setting.DEFAULT_RANDOM_TALKER.asBoolean();
private double range = Setting.DEFAULT_TALK_CLOSE_RANGE.asDouble();
@Persist(value = "realistic-looking")
private boolean realisticLooker = Setting.DEFAULT_REALISTIC_LOOKING.asBoolean();
@Persist(value = "speech-bubbles")
private boolean speechBubbles;
@Persist(value = "talk-close")
private boolean talkClose = Setting.DEFAULT_TALK_CLOSE.asBoolean();
private final List<String> text = new ArrayList<>();
public Text() {
super("text");
plugin = CitizensAPI.getPlugin();
}
/**
* Adds a piece of text that will be said by the NPC.
*
* @param string
* the text to say
*/
public void add(String string) {
text.add(string);
}
/**
* 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) {
text.set(index, newText);
}
/**
* Builds a text editor in game for the supplied {@link Player}.
*/
public Editor getEditor(Player player) {
Conversation conversation = new ConversationFactory(plugin).withLocalEcho(false).withEscapeSequence("/npc text")
.withEscapeSequence("exit").withModality(false).withFirstPrompt(new TextBasePrompt(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();
}
};
}
String getPageText(int page) {
Paginator paginator = new Paginator().header("Current Texts");
for (int i = 0; i < text.size(); i++) {
paginator.addLine("<green>" + i + " <gray>- <yellow>" + text.get(i));
}
return paginator.getPageText(page);
}
/**
* @return The list of all texts
*/
public List<String> getTexts() {
return text;
}
/**
* @return whether there is text at a certain index
*/
public boolean hasIndex(int index) {
return index >= 0 && text.size() > index;
}
boolean hasPage(int page) {
return new Paginator(text.size()).hasPage(page);
}
public boolean isRandomTalker() {
return randomTalker;
}
@Override
public void load(DataKey key) throws NPCLoadException {
text.clear();
for (DataKey sub : key.getRelative("text").getIntegerSubKeys()) {
text.add(sub.getString(""));
}
if (text.isEmpty()) {
populateDefaultText();
}
range = key.getDouble("range");
}
@EventHandler
private void onRightClick(NPCRightClickEvent event) {
if (!event.getNPC().equals(npc) || text.size() == 0)
return;
String localPattern = itemInHandPattern.equals("default") ? Setting.TALK_ITEM.asString() : itemInHandPattern;
if (Util.matchesItemInHand(event.getClicker(), localPattern) && !shouldTalkClose()) {
talk(event.getClicker());
event.setDelayedCancellation(true);
}
}
private void populateDefaultText() {
text.addAll(Setting.DEFAULT_TEXT.asList());
}
/**
* Remove text at a given index.
*/
public void remove(int index) {
text.remove(index);
}
@Override
public void run() {
if (!npc.isSpawned() || !talkClose || text.size() == 0)
return;
for (Player player : CitizensAPI.getLocationLookup().getNearbyVisiblePlayers(npc.getEntity(), range)) {
talk(player);
}
}
@Override
public void save(DataKey key) {
key.setDouble("range", range);
key.removeKey("text");
for (int i = 0; i < text.size(); i++) {
key.setString("text." + String.valueOf(i), text.get(i));
}
}
boolean sendPage(CommandSender player, int page) {
Paginator paginator = new Paginator().header("Current Texts").enablePageSwitcher("/npc text page $page");
for (int i = 0; i < text.size(); i++) {
paginator.addLine(text.get(i) + " <green>(<click:suggest_command:edit " + i
+ " ><yellow>edit</click>) (<hover:show_text:Remove this text><click:run_command:/npc text remove "
+ i + "><red>-</click></hover>)");
}
return paginator.sendPage(player, page);
}
private boolean sendText(Player player) {
if (text.size() == 0)
return false;
int index = 0;
if (randomTalker) {
index = RANDOM.nextInt(text.size());
} else {
if (currentIndex > text.size() - 1) {
currentIndex = 0;
}
index = currentIndex++;
}
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));
}
return true;
}
/**
* Set the text delay between messages.
*
* @param delay
* the delay in ticks
*/
public void setDelay(int delay) {
this.delay = delay;
}
/**
* Sets the item in hand pattern required to talk to NPCs, if enabled.
*
* @param pattern
* The new pattern
*/
public void setItemInHandPattern(String pattern) {
itemInHandPattern = pattern;
}
/**
* Set the range in blocks before text will be sent.
*
* @param range
*/
public void setRange(double range) {
this.range = range;
}
/**
* @return Whether talking close is enabled.
*/
public boolean shouldTalkClose() {
return talkClose;
}
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);
int delay = this.delay == -1
? Setting.DEFAULT_TEXT_DELAY_MIN.asTicks() + Util.getFastRandom()
.nextInt(Setting.DEFAULT_TEXT_DELAY_MAX.asTicks() - Setting.DEFAULT_TEXT_DELAY_MIN.asTicks())
: this.delay;
if (delay <= 0)
return;
cooldowns.put(player.getUniqueId(), System.currentTimeMillis() + delay * 50);
}
/**
* Toggles talking at random intervals.
*/
public boolean toggleRandomTalker() {
return randomTalker = !randomTalker;
}
/**
* Toggles requiring line of sight before talking.
*/
public boolean toggleRealisticLooking() {
return realisticLooker = !realisticLooker;
}
/**
* Toggles using speech bubbles instead of messages.
*/
public boolean toggleSpeechBubbles() {
return speechBubbles = !speechBubbles;
}
/**
* Toggles talking to nearby Players.
*/
public boolean toggleTalkClose() {
return talkClose = !talkClose;
}
public boolean useRealisticLooking() {
return realisticLooker;
}
public boolean useSpeechBubbles() {
return speechBubbles;
}
private static Random RANDOM = Util.getFastRandom();
}