Citizens2/main/src/main/java/net/citizensnpcs/trait/HologramTrait.java

536 lines
18 KiB
Java
Raw Normal View History

2020-06-30 12:17:14 +02:00
package net.citizensnpcs.trait;
2021-06-03 14:36:45 +02:00
import java.util.Collection;
import java.util.Collections;
2020-07-03 09:14:55 +02:00
import java.util.List;
2023-04-25 17:56:50 +02:00
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
2022-10-16 04:41:34 +02:00
import java.util.stream.Collectors;
import java.util.stream.IntStream;
2020-07-03 09:14:55 +02:00
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
2020-07-03 09:14:55 +02:00
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
2020-06-30 12:17:14 +02:00
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
2020-07-03 09:14:55 +02:00
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.inventory.ItemStack;
2020-07-03 09:14:55 +02:00
import com.google.common.collect.ImmutableList;
2020-07-03 09:14:55 +02:00
import com.google.common.collect.Lists;
2020-06-30 12:17:14 +02:00
2020-07-03 09:14:55 +02:00
import net.citizensnpcs.Settings.Setting;
2020-06-30 12:17:14 +02:00
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.command.Arg.CompletionsProvider;
import net.citizensnpcs.api.command.CommandContext;
2020-06-30 12:17:14 +02:00
import net.citizensnpcs.api.npc.MemoryNPCDataStore;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.npc.NPCRegistry;
import net.citizensnpcs.api.persistence.Persist;
import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.api.trait.TraitName;
2022-09-03 18:22:20 +02:00
import net.citizensnpcs.api.util.DataKey;
2020-06-30 12:17:14 +02:00
import net.citizensnpcs.api.util.Placeholders;
2022-08-12 07:48:54 +02:00
import net.citizensnpcs.api.util.SpigotUtil;
2020-07-06 02:42:46 +02:00
import net.citizensnpcs.util.NMS;
2022-10-16 04:41:34 +02:00
import net.citizensnpcs.util.Util;
2020-06-30 12:17:14 +02:00
/**
* Manages a set of <em>holograms</em> attached to the NPC. Holograms are lines of text or items that follow the NPC at
* some offset (typically vertically offset).
2020-06-30 12:17:14 +02:00
*/
// TODO: refactor this class
2020-06-30 12:17:14 +02:00
@TraitName("hologramtrait")
public class HologramTrait extends Trait {
2020-07-03 09:14:55 +02:00
private Location currentLoc;
2023-04-25 17:56:50 +02:00
private BiFunction<String, Player, String> customHologramSupplier;
2022-04-21 09:25:57 +02:00
private double lastEntityHeight = 0;
private boolean lastNameplateVisible;
2020-06-30 12:17:14 +02:00
@Persist
2020-07-03 09:14:55 +02:00
private double lineHeight = -1;
2022-09-03 18:22:20 +02:00
private final List<HologramLine> lines = Lists.newArrayList();
private HologramLine nameLine;
2021-02-04 03:10:48 +01:00
private final NPCRegistry registry = CitizensAPI.createCitizensBackedNPCRegistry(new MemoryNPCDataStore());
2023-02-20 16:34:12 +01:00
private int t;
private boolean useDisplayEntities = Setting.DISPLAY_ENTITY_HOLOGRAMS.asBoolean();
@Persist
private int viewRange = -1;
2020-06-30 12:17:14 +02:00
public HologramTrait() {
super("hologramtrait");
}
2021-06-03 14:36:45 +02:00
/**
* Adds a new hologram line which will displayed over an NPC's head.
*
* @param text
* The new line to add
*/
2020-07-03 09:14:55 +02:00
public void addLine(String text) {
2022-09-03 18:22:20 +02:00
lines.add(new HologramLine(text, true));
reloadLineHolograms();
2020-07-03 09:14:55 +02:00
}
2022-09-03 18:22:20 +02:00
/**
* Adds a new hologram line which will displayed over an NPC's head. It will not persist to disk and will last for
* the specified amount of ticks.
*
* @param text
* The new line to add
* @param ticks
* The number of ticks to last for
*/
public void addTemporaryLine(String text, int ticks) {
lines.add(new HologramLine(text, false, ticks));
2022-10-16 04:41:34 +02:00
reloadLineHolograms();
2022-09-03 18:22:20 +02:00
}
2021-06-03 14:36:45 +02:00
/**
* Clears all hologram lines
*/
2020-07-06 19:07:01 +02:00
public void clear() {
2022-10-16 04:41:34 +02:00
for (HologramLine line : lines) {
line.removeNPC();
}
2020-07-06 19:07:01 +02:00
lines.clear();
}
@SuppressWarnings("deprecation")
private NPC createHologram(String line, double heightOffset) {
NPC hologramNPC = null;
if (useDisplayEntities) {
hologramNPC = registry.createNPC(EntityType.INTERACTION, line);
2023-03-20 10:28:17 +01:00
hologramNPC.addTrait(new ClickRedirectTrait(npc));
} else {
hologramNPC = registry.createNPC(EntityType.ARMOR_STAND, line);
hologramNPC.getOrAddTrait(ArmorStandTrait.class).setAsHelperEntityWithName(npc);
}
2023-11-15 15:39:09 +01:00
hologramNPC.data().set(NPC.Metadata.HOLOGRAM_FOR, npc.getUniqueId().toString());
if (Setting.PACKET_HOLOGRAMS.asBoolean()) {
hologramNPC.addTrait(PacketNPC.class);
}
if (viewRange != -1) {
hologramNPC.data().set(NPC.Metadata.TRACKING_RANGE, viewRange);
}
2023-10-28 20:09:08 +02:00
hologramNPC.spawn(currentLoc.clone().add(0, getEntityHeight() + heightOffset, 0));
2023-03-19 09:14:53 +01:00
Matcher itemMatcher = ITEM_MATCHER.matcher(line);
if (itemMatcher.matches()) {
2022-08-12 07:48:54 +02:00
Material item = SpigotUtil.isUsing1_13API() ? Material.matchMaterial(itemMatcher.group(1), false)
: Material.matchMaterial(itemMatcher.group(1));
ItemStack itemStack = new ItemStack(item, 1);
NPC itemNPC = registry.createNPCUsingItem(EntityType.DROPPED_ITEM, "", itemStack);
2023-02-13 15:20:13 +01:00
itemNPC.data().setPersistent(NPC.Metadata.NAMEPLATE_VISIBLE, false);
if (itemMatcher.group(2) != null) {
if (itemMatcher.group(2).charAt(1) == '{') {
Bukkit.getUnsafe().modifyItemStack(itemStack, itemMatcher.group(2).substring(1));
itemNPC.setItemProvider(() -> itemStack);
} else {
itemNPC.getOrAddTrait(ScoreboardTrait.class)
.setColor(Util.matchEnum(ChatColor.values(), itemMatcher.group(2).substring(1)));
}
2022-10-16 04:41:34 +02:00
}
2023-03-19 09:14:53 +01:00
itemNPC.getOrAddTrait(MountTrait.class).setMountedOn(hologramNPC.getUniqueId());
itemNPC.spawn(currentLoc);
NPC hn = hologramNPC;
2023-03-19 09:14:53 +01:00
itemNPC.addRunnable(() -> {
if (!itemNPC.isSpawned() || !hn.isSpawned()) {
2023-03-19 09:14:53 +01:00
itemNPC.destroy();
}
});
}
2022-04-21 09:25:57 +02:00
lastEntityHeight = getEntityHeight();
return hologramNPC;
}
2020-07-06 02:42:46 +02:00
private double getEntityHeight() {
return NMS.getHeight(npc.getEntity());
2020-07-06 02:42:46 +02:00
}
2020-07-03 09:14:55 +02:00
private double getHeight(int lineNumber) {
double base = lastNameplateVisible ? 0 : -getLineHeight();
for (int i = 0; i <= lineNumber; i++) {
HologramLine line = lines.get(i);
base += line.mb + getLineHeight();
if (i != lineNumber) {
base += line.mt;
}
}
return base;
2020-07-03 09:14:55 +02:00
}
2021-06-03 14:36:45 +02:00
/**
* Note: this is implementation-specific and may be removed at a later date.
*/
public Collection<Entity> getHologramEntities() {
2022-10-16 04:41:34 +02:00
return lines.stream().filter(l -> l.hologram != null && l.hologram.getEntity() != null)
.map(l -> l.hologram.getEntity()).collect(Collectors.toList());
2021-06-03 14:36:45 +02:00
}
/**
* @return The line height between each hologram line, in blocks
*/
2020-10-07 13:16:41 +02:00
public double getLineHeight() {
2022-08-12 07:48:54 +02:00
return lineHeight == -1 ? Setting.DEFAULT_NPC_HOLOGRAM_LINE_HEIGHT.asDouble() : lineHeight;
2020-10-07 13:16:41 +02:00
}
2021-06-03 14:36:45 +02:00
/**
* @return the hologram lines, in bottom-up order
*/
2020-07-03 09:14:55 +02:00
public List<String> getLines() {
2023-07-29 17:26:02 +02:00
return Lists.transform(lines, l -> l.text);
2020-07-03 09:14:55 +02:00
}
2021-06-03 14:36:45 +02:00
/**
* Note: this is implementation-specific and may be removed at a later date.
*/
public Entity getNameEntity() {
return nameLine != null && nameLine.hologram.isSpawned() ? nameLine.hologram.getEntity() : null;
2021-06-03 14:36:45 +02:00
}
public double getViewRange() {
return viewRange;
}
public boolean isHologramSneaking(NPC hologram, Player player) {
if (nameLine != null && hologram == nameLine.hologram && npc.getEntity() instanceof Player
&& ((Player) npc.getEntity()).isSneaking())
return true;
return false;
}
2022-09-03 18:22:20 +02:00
@Override
public void load(DataKey root) {
2022-10-16 04:41:34 +02:00
clear();
2022-09-03 18:22:20 +02:00
for (DataKey key : root.getRelative("lines").getIntegerSubKeys()) {
HologramLine line = new HologramLine(key.keyExists("text") ? key.getString("text") : key.getString(""),
true);
line.mt = key.keyExists("margin.top") ? key.getDouble("margin.top") : 0.0;
line.mb = key.keyExists("margin.bottom") ? key.getDouble("margin.bottom") : 0.0;
lines.add(line);
2022-09-03 18:22:20 +02:00
}
}
2020-06-30 12:17:14 +02:00
@Override
public void onDespawn() {
if (nameLine != null) {
nameLine.removeNPC();
nameLine = null;
2021-07-04 07:07:15 +02:00
}
2022-10-16 04:41:34 +02:00
for (HologramLine line : lines) {
line.removeNPC();
2021-07-04 07:07:15 +02:00
}
2020-06-30 12:17:14 +02:00
}
public void onHologramSeenByPlayer(NPC hologram, Player player) {
if (useDisplayEntities && npc.isSpawned()) {
double height = -1;
if (nameLine != null && hologram.equals(nameLine.hologram)) {
height = 0;
} else {
for (int i = 0; i < lines.size(); i++) {
if (hologram.equals(lines.get(i).hologram)) {
height = getHeight(i);
break;
}
}
}
if (height == -1)
return;
NMS.linkTextInteraction(player, hologram.getEntity(), npc.getEntity(), height);
}
}
@Override
public void onRemove() {
2021-07-04 07:07:15 +02:00
onDespawn();
}
2020-06-30 12:17:14 +02:00
@Override
public void onSpawn() {
if (!npc.isSpawned())
return;
2022-10-16 04:41:34 +02:00
lastNameplateVisible = Boolean
.parseBoolean(npc.data().<Object> get(NPC.Metadata.NAMEPLATE_VISIBLE, true).toString());
2021-07-04 07:07:15 +02:00
currentLoc = npc.getStoredLocation();
if (npc.requiresNameHologram() && lastNameplateVisible) {
nameLine = new HologramLine(npc.getRawName(), false);
nameLine.spawnNPC(0);
2021-07-04 07:07:15 +02:00
}
for (int i = 0; i < lines.size(); i++) {
2022-10-16 04:41:34 +02:00
lines.get(i).spawnNPC(getHeight(i));
}
}
private void reloadLineHolograms() {
2022-10-16 04:41:34 +02:00
for (HologramLine line : lines) {
line.removeNPC();
}
if (!npc.isSpawned())
return;
2022-09-03 18:22:20 +02:00
2021-07-04 07:07:15 +02:00
for (int i = 0; i < lines.size(); i++) {
2022-10-16 04:41:34 +02:00
lines.get(i).spawnNPC(getHeight(i));
2021-07-04 07:07:15 +02:00
}
2020-07-03 09:14:55 +02:00
}
2021-06-03 14:36:45 +02:00
/**
* Removes the line at the specified index
*
* @param idx
*/
2020-07-03 09:14:55 +02:00
public void removeLine(int idx) {
if (idx < 0 || idx >= lines.size())
return;
2022-10-16 04:41:34 +02:00
lines.remove(idx).removeNPC();
reloadLineHolograms();
2020-06-30 12:17:14 +02:00
}
@Override
public void run() {
2020-07-03 09:14:55 +02:00
if (!npc.isSpawned()) {
2021-07-04 07:07:15 +02:00
onDespawn();
2020-06-30 12:17:14 +02:00
return;
}
2021-09-05 08:59:19 +02:00
if (currentLoc == null) {
2023-03-30 17:13:38 +02:00
currentLoc = npc.getStoredLocation().clone();
2021-09-05 08:59:19 +02:00
}
boolean nameplateVisible = Boolean
.parseBoolean(npc.data().<Object> get(NPC.Metadata.NAMEPLATE_VISIBLE, true).toString());
2020-07-10 06:19:29 +02:00
if (npc.requiresNameHologram()) {
if (nameLine != null && !nameplateVisible) {
nameLine.removeNPC();
nameLine = null;
} else if (nameLine == null && nameplateVisible) {
nameLine = new HologramLine(npc.getRawName(), false);
nameLine.spawnNPC(0);
2020-07-10 06:19:29 +02:00
}
}
2023-10-22 19:58:06 +02:00
Location npcLoc = npc.getStoredLocation();
boolean updatePosition = Setting.HOLOGRAM_ALWAYS_UPDATE_POSITION.asBoolean()
|| currentLoc.getWorld() != npcLoc.getWorld() || currentLoc.distance(npcLoc) >= 0.001
|| lastNameplateVisible != nameplateVisible || Math.abs(lastEntityHeight - getEntityHeight()) >= 0.05;
2023-02-20 16:34:12 +01:00
boolean updateName = false;
2023-10-22 19:58:06 +02:00
if (t++ >= Setting.HOLOGRAM_UPDATE_RATE.asTicks() + Util.getFastRandom().nextInt(3) /* add some jitter */) {
2023-02-20 16:34:12 +01:00
t = 0;
updateName = true;
}
lastNameplateVisible = nameplateVisible;
2022-10-16 04:41:34 +02:00
if (updatePosition) {
2023-10-22 19:58:06 +02:00
currentLoc = npcLoc.clone();
2022-04-21 09:25:57 +02:00
lastEntityHeight = getEntityHeight();
2020-07-03 09:14:55 +02:00
}
if (nameLine != null && nameLine.hologram.isSpawned()) {
if (updatePosition && !useDisplayEntities) {
2023-10-22 19:58:06 +02:00
nameLine.hologram.teleport(npcLoc.clone().add(0, getEntityHeight(), 0), TeleportCause.PLUGIN);
}
2023-02-20 16:34:12 +01:00
if (updateName) {
nameLine.setText(npc.getRawName());
2023-02-20 16:34:12 +01:00
}
2023-10-31 17:58:28 +01:00
if (useDisplayEntities && nameLine.hologram.getEntity().getVehicle() == null) {
npc.getEntity().addPassenger(nameLine.hologram.getEntity());
}
}
2022-10-16 04:41:34 +02:00
for (int i = 0; i < lines.size(); i++) {
HologramLine line = lines.get(i);
NPC hologramNPC = line.hologram;
2023-10-22 19:58:06 +02:00
if (hologramNPC == null || !hologramNPC.isSpawned()) {
2020-07-03 09:14:55 +02:00
continue;
}
2023-10-22 19:58:06 +02:00
if (line.ticks > 0 && --line.ticks == 0) {
line.removeNPC();
lines.remove(i--);
continue;
}
if (updatePosition && !useDisplayEntities) {
2023-10-28 20:09:08 +02:00
Location tp = npcLoc.clone().add(0, lastEntityHeight + getHeight(i), 0);
2023-03-19 09:14:53 +01:00
hologramNPC.teleport(tp, TeleportCause.PLUGIN);
2020-07-03 09:14:55 +02:00
}
2023-10-31 17:58:28 +01:00
if (useDisplayEntities && hologramNPC.getEntity().getVehicle() == null) {
npc.getEntity().addPassenger(hologramNPC.getEntity());
}
2022-09-03 18:22:20 +02:00
String text = line.text;
if (ITEM_MATCHER.matcher(text).matches()) {
hologramNPC.data().set(NPC.Metadata.NAMEPLATE_VISIBLE, false);
continue;
}
if (!updateName) {
2023-02-20 16:34:12 +01:00
continue;
}
line.setText(text);
2020-06-30 12:17:14 +02:00
}
}
2022-09-03 18:22:20 +02:00
@Override
public void save(DataKey root) {
root.removeKey("lines");
int i = 0;
for (HologramLine line : lines) {
if (!line.persist) {
2022-09-03 18:22:20 +02:00
continue;
}
root.setString("lines." + i + ".text", line.text);
root.setDouble("lines." + i + ".margin.top", line.mt);
root.setDouble("lines." + i + ".margin.bottom", line.mb);
2022-09-03 18:22:20 +02:00
i++;
}
}
2021-06-03 14:36:45 +02:00
/**
* Sets the hologram line at a specific index
*
* @param idx
* The index
* @param text
* The new line
*/
2020-07-03 09:14:55 +02:00
public void setLine(int idx, String text) {
2020-08-11 16:57:32 +02:00
if (idx == lines.size()) {
addLine(text);
return;
2020-08-11 16:57:32 +02:00
}
2022-10-16 04:41:34 +02:00
HologramLine line = lines.get(idx);
line.setText(text);
if (line.hologram == null) {
reloadLineHolograms();
}
2020-07-03 09:14:55 +02:00
}
2021-06-03 14:36:45 +02:00
/**
* Sets the line height
*
* @see #getLineHeight()
* @param height
* The line height in blocks
*/
2020-07-03 09:14:55 +02:00
public void setLineHeight(double height) {
lineHeight = height;
reloadLineHolograms();
2020-07-03 09:14:55 +02:00
}
2020-07-27 11:34:07 +02:00
/**
* Sets the margin of a line at a specific index
*
* @param idx
* The index
* @param type
* The margin type, top or bottom
* @param margin
* The margin
*/
public void setMargin(int idx, String type, double margin) {
if (type.equalsIgnoreCase("top")) {
lines.get(idx).mt = margin;
} else if (type.equalsIgnoreCase("bottom")) {
lines.get(idx).mb = margin;
}
reloadLineHolograms();
}
2023-04-25 17:56:50 +02:00
/**
* Implementation-specific method: {@see NPC.Metadata#HOLOGRAM_LINE_SUPPLIER}
*/
public void setPerPlayerTextSupplier(BiFunction<String, Player, String> nameSupplier) {
customHologramSupplier = nameSupplier;
2023-04-25 17:56:50 +02:00
}
public void setUseDisplayEntities(boolean use) {
useDisplayEntities = use;
reloadLineHolograms();
}
public void setViewRange(int range) {
this.viewRange = range;
2023-03-20 10:28:17 +01:00
reloadLineHolograms();
2023-03-19 09:14:53 +01:00
}
private class HologramLine implements Function<Player, String> {
2022-10-16 04:41:34 +02:00
NPC hologram;
double mb, mt;
2022-09-03 18:22:20 +02:00
boolean persist;
String text;
2022-10-16 04:41:34 +02:00
int ticks;
2022-09-03 18:22:20 +02:00
public HologramLine(String text, boolean persist) {
this(text, persist, -1);
}
public HologramLine(String text, boolean persist, int ticks) {
setText(text);
this.persist = persist;
this.ticks = ticks;
if (ITEM_MATCHER.matcher(text).matches()) {
mb = 0.21;
mt = 0.07;
}
2022-09-03 18:22:20 +02:00
}
2022-10-16 04:41:34 +02:00
@Override
public String apply(Player viewer) {
return Placeholders.replace(text, viewer, npc);
}
2022-10-16 04:41:34 +02:00
public void removeNPC() {
if (hologram == null)
return;
hologram.destroy();
hologram = null;
}
public void setText(String text) {
this.text = text == null ? "" : text;
2022-10-16 04:41:34 +02:00
if (hologram != null) {
2023-04-16 19:47:32 +02:00
String name = Placeholders.replace(text, null, npc);
hologram.setName(name);
hologram.data().set(NPC.Metadata.NAMEPLATE_VISIBLE, ChatColor.stripColor(name).length() > 0);
if (Placeholders.containsPlayerPlaceholder(text)) {
hologram.data().set(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER, this);
} else {
hologram.data().remove(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER);
}
2022-10-16 04:41:34 +02:00
}
}
public void spawnNPC(double height) {
2023-04-16 19:47:32 +02:00
String name = Placeholders.replace(text, null, npc);
hologram = createHologram(name, height);
2023-04-25 17:56:50 +02:00
if (customHologramSupplier != null) {
hologram.data().set(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER,
(Function<Player, String>) p -> customHologramSupplier.apply(text, p));
} else if (Placeholders.containsPlayerPlaceholder(text)) {
hologram.data().set(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER, this);
}
2022-10-16 04:41:34 +02:00
}
2022-09-03 18:22:20 +02:00
}
public static class TabCompletions implements CompletionsProvider {
@Override
public Collection<String> getCompletions(CommandContext args, CommandSender sender, NPC npc) {
if (npc != null && LINE_ARGS.contains(args.getString(1).toLowerCase())) {
HologramTrait ht = npc.getOrAddTrait(HologramTrait.class);
return IntStream.range(0, ht.getLines().size()).mapToObj(Integer::toString)
.collect(Collectors.toList());
}
return Collections.emptyList();
2023-03-20 10:28:17 +01:00
}
private static List<String> LINE_ARGS = ImmutableList.of("set", "remove", "margintop", "marginbottom");
2023-03-20 10:28:17 +01:00
}
private static Pattern ITEM_MATCHER = Pattern.compile("<item:(.*?)([:].*?)?>");
2020-06-30 12:17:14 +02:00
}