diff --git a/main/src/main/java/net/citizensnpcs/Citizens.java b/main/src/main/java/net/citizensnpcs/Citizens.java index 69e3605bc..28c69da0c 100644 --- a/main/src/main/java/net/citizensnpcs/Citizens.java +++ b/main/src/main/java/net/citizensnpcs/Citizens.java @@ -521,7 +521,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin { } Translator.setInstance(new File(getDataFolder(), "lang"), locale); if (!locale.getLanguage().equals("en")) { - Messaging.logTr(Messages.CONTRIBUTE_TO_TRANSLATION_PROMPT); + Messaging.logTr(Messages.CONTRIBUTE_TO_TRANSLATION_PROMPT, locale.getLanguage()); } } diff --git a/main/src/main/java/net/citizensnpcs/EventListen.java b/main/src/main/java/net/citizensnpcs/EventListen.java index 53263186b..8f56e431e 100644 --- a/main/src/main/java/net/citizensnpcs/EventListen.java +++ b/main/src/main/java/net/citizensnpcs/EventListen.java @@ -112,7 +112,7 @@ import net.citizensnpcs.trait.ClickRedirectTrait; import net.citizensnpcs.trait.CommandTrait; import net.citizensnpcs.trait.Controllable; import net.citizensnpcs.trait.CurrentLocation; -import net.citizensnpcs.trait.HologramTrait; +import net.citizensnpcs.trait.HologramTrait.HologramRenderer; import net.citizensnpcs.trait.ShopTrait; import net.citizensnpcs.trait.versioned.SnowmanTrait; import net.citizensnpcs.util.ChunkCoord; @@ -469,17 +469,12 @@ public class EventListen implements Listener { if (skinnable.getSkinTracker().getSkin() != null) { skinnable.getSkinTracker().getSkin().apply(skinnable); } - } - if (npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { onNPCPlayerLinkToPlayer(event); } - ClickRedirectTrait crt = npc.getTraitNullable(ClickRedirectTrait.class); - if (crt != null) { - HologramTrait ht = crt.getRedirectNPC().getTraitNullable(HologramTrait.class); - if (ht != null) { - Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), - () -> ht.onHologramSeenByPlayer(npc, event.getPlayer()), 2); - } + if (npc.data().has(NPC.Metadata.HOLOGRAM_RENDERER)) { + HologramRenderer hr = npc.data().get(NPC.Metadata.HOLOGRAM_RENDERER); + Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), + () -> hr.onSeenByPlayer(event.getPlayer()), 2); } } diff --git a/main/src/main/java/net/citizensnpcs/ProtocolLibListener.java b/main/src/main/java/net/citizensnpcs/ProtocolLibListener.java index c6e0c001c..4b93fe10a 100644 --- a/main/src/main/java/net/citizensnpcs/ProtocolLibListener.java +++ b/main/src/main/java/net/citizensnpcs/ProtocolLibListener.java @@ -6,11 +6,9 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.function.Function; import org.bukkit.Bukkit; import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDeathEvent; @@ -44,8 +42,7 @@ import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.trait.trait.MobType; import net.citizensnpcs.api.util.Messaging; import net.citizensnpcs.npc.ai.NPCHolder; -import net.citizensnpcs.trait.ClickRedirectTrait; -import net.citizensnpcs.trait.HologramTrait; +import net.citizensnpcs.trait.HologramTrait.HologramRenderer; import net.citizensnpcs.trait.MirrorTrait; import net.citizensnpcs.trait.RotationTrait; import net.citizensnpcs.trait.RotationTrait.PacketRotationSession; @@ -71,16 +68,13 @@ public class ProtocolLibListener implements Listener { PacketContainer packet = event.getPacket(); int version = manager.getProtocolVersion(event.getPlayer()); - if (npc.data().has(NPC.Metadata.HOLOGRAM_FOR) || npc.data().has(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER)) { - Function hvs = npc.data().get(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER); + if (npc.data().has(NPC.Metadata.HOLOGRAM_RENDERER)) { + HologramRenderer hr = npc.data().get(NPC.Metadata.HOLOGRAM_RENDERER); Object fakeName = null; - if (hvs != null) { - String suppliedName = hvs.apply(event.getPlayer()); - fakeName = version <= 340 ? suppliedName - : Optional.of(Messaging.minecraftComponentFromRawMessage(suppliedName)); - } - boolean sneaking = npc.getOrAddTrait(ClickRedirectTrait.class).getRedirectNPC() - .getOrAddTrait(HologramTrait.class).isHologramSneaking(npc, event.getPlayer()); + String suppliedName = hr.getPerPlayerText(npc, event.getPlayer()); + fakeName = version <= 340 ? suppliedName + : Optional.of(Messaging.minecraftComponentFromRawMessage(suppliedName)); + boolean sneaking = hr.isSneaking(npc, event.getPlayer()); boolean delta = false; if (version < 761) { diff --git a/main/src/main/java/net/citizensnpcs/Settings.java b/main/src/main/java/net/citizensnpcs/Settings.java index 65147c150..cb31f65e0 100644 --- a/main/src/main/java/net/citizensnpcs/Settings.java +++ b/main/src/main/java/net/citizensnpcs/Settings.java @@ -108,6 +108,9 @@ public class Settings { DEFAULT_DISTANCE_MARGIN( "The default MOVEMENT distance in blocks where the NPC will move to before considering a path finished
Note: this is different from the PATHFINDING distance which is specified by path-distance-margin", "npc.pathfinding.default-distance-margin", 1), + DEFAULT_HOLOGRAM_RENDERER( + "The default renderer for holograms, must be one of the following:
interaction - matches inbuilt nametags most closely
display - allows for different colored backgrounds
armorstand - creates an armorstand and teleports it to the player", + "npc.hologram.default-renderer", "interaction"), DEFAULT_LOOK_CLOSE("Enable look close by default", "npc.default.look-close.enabled", false), DEFAULT_LOOK_CLOSE_RANGE("Default look close range in blocks", "npc.default.look-close.range", 10), DEFAULT_NPC_HOLOGRAM_LINE_HEIGHT("Default distance between hologram lines", "npc.hologram.default-line-height", @@ -166,9 +169,6 @@ public class Settings { "Minecraft will pick a 'close-enough' location when pathfinding to a block if it can't find a direct path
Disabled by default", "npc.pathfinding.disable-mc-fallback-navigation", true), DISABLE_TABLIST("Whether to remove NPCs from the tablist", "npc.tablist.disable", true), - DISPLAY_ENTITY_HOLOGRAMS( - "Whether to use display entities for holograms by default (in theory more performant than armor stands)
Requires 1.19.4 or above, defaults to false", - "npc.hologram.use-display-entities", false), ENTITY_SPAWN_WAIT_DURATION( "Entities are no longer spawned until the chunks are loaded from disk
Wait for chunk loading for one second by default, increase if your disk is slow", "general.entity-spawn-wait-ticks", "general.wait-for-entity-spawn", "1s"), @@ -284,11 +284,11 @@ public class Settings { this.value = value; } - Setting(String migrate, String path, Object value) { - if (migrate.contains(".")) { - this.migrate = migrate; + Setting(String migrateOrComments, String path, Object value) { + if (migrateOrComments.contains(".") && !migrateOrComments.contains(" ")) { + migrate = migrateOrComments; } else { - comments = migrate; + comments = migrateOrComments; } this.path = path; this.value = value; diff --git a/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java b/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java index b4ffb0320..f4afd203b 100644 --- a/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java +++ b/main/src/main/java/net/citizensnpcs/commands/NPCCommands.java @@ -735,7 +735,7 @@ public class NPCCommands { throw new CommandException(Messaging.tr(Messages.NPC_CREATE_MISSING_MOBTYPE, args.getFlag("type"))); } int nameLength = SpigotUtil.getMaxNameLength(type); - if (Placeholders.replace(Messaging.parseComponents(name), sender, npc).length() > nameLength) { + if (Placeholders.replace(Messaging.stripColor(name), sender, npc).length() > nameLength) { Messaging.sendErrorTr(sender, Messages.NPC_NAME_TOO_LONG, nameLength); name = name.substring(0, nameLength); } @@ -1042,7 +1042,7 @@ public class NPCCommands { if (!(sender instanceof ConsoleCommandSender) && !followingNPC.getOrAddTrait(Owner.class).isOwnedBy(sender)) throw new CommandException(CommandMessages.MUST_BE_OWNER); - boolean following = !trait.isEnabled(); + boolean following = explicit == null ? !trait.isEnabled() : explicit; trait.follow(following ? followingNPC.getEntity() : null); Messaging.sendTr(sender, following ? Messages.FOLLOW_SET : Messages.FOLLOW_UNSET, npc.getName(), followingNPC.getName()); @@ -2532,9 +2532,9 @@ public class NPCCommands { permission = "citizens.npc.rename") public void rename(CommandContext args, CommandSender sender, NPC npc) { String oldName = npc.getName(); - String newName = Messaging.parseComponents(args.getJoinedStrings(1)); + String newName = args.getJoinedStrings(1); int nameLength = SpigotUtil.getMaxNameLength(npc.getOrAddTrait(MobType.class).getType()); - if (newName.length() > nameLength) { + if (Placeholders.replace(Messaging.stripColor(newName), sender, npc).length() > nameLength) { Messaging.sendErrorTr(sender, Messages.NPC_NAME_TOO_LONG, nameLength); newName = newName.substring(0, nameLength); } diff --git a/main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java b/main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java index d90f81e3b..05e5d0a70 100644 --- a/main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java +++ b/main/src/main/java/net/citizensnpcs/npc/CitizensNPC.java @@ -192,7 +192,7 @@ public class CitizensNPC extends AbstractNPC { @Override public boolean requiresNameHologram() { - return !data().has(NPC.Metadata.HOLOGRAM_FOR) + return !data().has(NPC.Metadata.HOLOGRAM_RENDERER) && (super.requiresNameHologram() || Setting.ALWAYS_USE_NAME_HOLOGRAM.asBoolean()); } diff --git a/main/src/main/java/net/citizensnpcs/trait/FollowTrait.java b/main/src/main/java/net/citizensnpcs/trait/FollowTrait.java index 1f39d2cc4..95a46cf3e 100644 --- a/main/src/main/java/net/citizensnpcs/trait/FollowTrait.java +++ b/main/src/main/java/net/citizensnpcs/trait/FollowTrait.java @@ -38,8 +38,8 @@ public class FollowTrait extends Trait { } private void cancelNavigationIfActive() { - if (npc.getNavigator().isNavigating() && this.entity != null && npc.getNavigator().getEntityTarget() != null - && this.entity == npc.getNavigator().getEntityTarget().getTarget()) { + if (npc.getNavigator().isNavigating() && entity != null && npc.getNavigator().getEntityTarget() != null + && entity == npc.getNavigator().getEntityTarget().getTarget()) { npc.getNavigator().cancelNavigation(); } } @@ -47,10 +47,10 @@ public class FollowTrait extends Trait { /** * Sets the {@link Entity} to follow */ - public void follow(Entity entity) { - followingUUID = entity == null ? null : entity.getUniqueId(); + public void follow(Entity follow) { cancelNavigationIfActive(); - this.entity = null; + followingUUID = follow == null ? null : follow.getUniqueId(); + entity = null; } public Entity getFollowing() { diff --git a/main/src/main/java/net/citizensnpcs/trait/HologramTrait.java b/main/src/main/java/net/citizensnpcs/trait/HologramTrait.java index 3baaa42b5..cb7efeecc 100644 --- a/main/src/main/java/net/citizensnpcs/trait/HologramTrait.java +++ b/main/src/main/java/net/citizensnpcs/trait/HologramTrait.java @@ -3,8 +3,7 @@ package net.citizensnpcs.trait; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.function.BiFunction; -import java.util.function.Function; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -15,14 +14,17 @@ import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Display.Billboard; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; +import org.bukkit.entity.TextDisplay; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Transformation; import org.joml.Vector3d; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import net.citizensnpcs.Settings.Setting; @@ -36,6 +38,7 @@ 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.Placeholders; import net.citizensnpcs.api.util.SpigotUtil; import net.citizensnpcs.util.NMS; @@ -45,11 +48,10 @@ import net.citizensnpcs.util.Util; * Manages a set of holograms attached to the NPC. Holograms are lines of text or items that follow the NPC at * some offset (typically vertically offset). */ -// TODO: refactor this class +// TODO: cleanup @TraitName("hologramtrait") public class HologramTrait extends Trait { private Location currentLoc; - private BiFunction customHologramSupplier; private double lastEntityBbHeight = 0; private boolean lastNameplateVisible; @Persist @@ -58,7 +60,6 @@ public class HologramTrait extends Trait { private HologramLine nameLine; private final NPCRegistry registry = CitizensAPI.createCitizensBackedNPCRegistry(new MemoryNPCDataStore()); private int t; - private boolean useDisplayEntities = Setting.DISPLAY_ENTITY_HOLOGRAMS.asBoolean(); @Persist private int viewRange = -1; @@ -77,6 +78,11 @@ public class HologramTrait extends Trait { reloadLineHolograms(); } + public void addLine(String text, HologramRenderer hr) { + lines.add(new HologramLine(text, hr)); + reloadLineHolograms(); + } + /** * 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. @@ -101,64 +107,6 @@ public class HologramTrait extends Trait { lines.clear(); } - @SuppressWarnings("deprecation") - private NPC createHologram(String line, double heightOffset) { - NPC hologramNPC = null; - if (useDisplayEntities) { - hologramNPC = registry.createNPC(EntityType.INTERACTION, line); - hologramNPC.addTrait(new ClickRedirectTrait(npc)); - } else { - hologramNPC = registry.createNPC(EntityType.ARMOR_STAND, line); - hologramNPC.getOrAddTrait(ArmorStandTrait.class).setAsHelperEntityWithName(npc); - } - 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); - } else if (npc.data().has(NPC.Metadata.TRACKING_RANGE)) { - hologramNPC.data().set(NPC.Metadata.TRACKING_RANGE, npc.data().get(NPC.Metadata.TRACKING_RANGE)); - } - hologramNPC.spawn(currentLoc.clone().add(0, getEntityBbHeight() + heightOffset, 0)); - - Matcher itemMatcher = ITEM_MATCHER.matcher(line); - if (itemMatcher.matches()) { - Material item = SpigotUtil.isUsing1_13API() ? Material.matchMaterial(itemMatcher.group(1), false) - : Material.matchMaterial(itemMatcher.group(1)); - if (item == null) { - hologramNPC.destroy(); - throw new IllegalStateException("Unknown material " + line); - } - ItemStack itemStack = new ItemStack(item, 1); - NPC itemNPC = registry.createNPCUsingItem(EntityType.DROPPED_ITEM, "", itemStack); - 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))); - } - } - if (hologramNPC.data().has(NPC.Metadata.TRACKING_RANGE)) { - itemNPC.data().setPersistent(NPC.Metadata.TRACKING_RANGE, - hologramNPC.data().get(NPC.Metadata.TRACKING_RANGE)); - } - itemNPC.getOrAddTrait(MountTrait.class).setMountedOn(hologramNPC.getUniqueId()); - itemNPC.spawn(currentLoc); - NPC hn = hologramNPC; - itemNPC.addRunnable(() -> { - if (!itemNPC.isSpawned() || !hn.isSpawned()) { - itemNPC.destroy(); - } - }); - } - lastEntityBbHeight = getEntityBbHeight(); - return hologramNPC; - } - private double getEntityBbHeight() { return NMS.getBoundingBoxHeight(npc.getEntity()); } @@ -175,14 +123,6 @@ public class HologramTrait extends Trait { return base; } - /** - * Note: this is implementation-specific and may be removed at a later date. - */ - public Collection getHologramEntities() { - return lines.stream().filter(l -> l.hologram != null && l.hologram.getEntity() != null) - .map(l -> l.hologram.getEntity()).collect(Collectors.toList()); - } - /** * @return The line height between each hologram line, in blocks */ @@ -197,24 +137,10 @@ public class HologramTrait extends Trait { return Lists.transform(lines, l -> l.text); } - /** - * 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; - } - - public double getViewRange() { + public int 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; - } - @Override public void load(DataKey root) { clear(); @@ -229,33 +155,7 @@ public class HologramTrait extends Trait { @Override public void onDespawn() { - if (nameLine != null) { - nameLine.removeNPC(); - nameLine = null; - } - for (HologramLine line : lines) { - line.removeNPC(); - } - } - - 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); - } + reloadLineHolograms(); } @Override @@ -270,32 +170,17 @@ public class HologramTrait extends Trait { lastNameplateVisible = Boolean .parseBoolean(npc.data(). get(NPC.Metadata.NAMEPLATE_VISIBLE, true).toString()); - currentLoc = npc.getStoredLocation(); - if (npc.requiresNameHologram() && lastNameplateVisible) { - nameLine = new HologramLine(npc.getRawName(), false); - nameLine.spawnNPC(0); - } - for (int i = 0; i < lines.size(); i++) { - lines.get(i).spawnNPC(getHeight(i)); - } } private void reloadLineHolograms() { for (HologramLine line : lines) { line.removeNPC(); } - if (!npc.isSpawned()) - return; - if (npc.requiresNameHologram() && lastNameplateVisible) { - if (nameLine != null) { - nameLine.removeNPC(); - } - nameLine = new HologramLine(npc.getRawName(), false); - nameLine.spawnNPC(0); - } - for (int i = 0; i < lines.size(); i++) { - lines.get(i).spawnNPC(getHeight(i)); + if (nameLine != null) { + nameLine.removeNPC(); + nameLine = null; } + currentLoc = null; } /** @@ -318,9 +203,6 @@ public class HologramTrait extends Trait { onDespawn(); return; } - if (currentLoc == null) { - currentLoc = npc.getStoredLocation().clone(); - } boolean nameplateVisible = Boolean .parseBoolean(npc.data(). get(NPC.Metadata.NAMEPLATE_VISIBLE, true).toString()); if (npc.requiresNameHologram()) { @@ -328,12 +210,13 @@ public class HologramTrait extends Trait { nameLine.removeNPC(); nameLine = null; } else if (nameLine == null && nameplateVisible) { - nameLine = new HologramLine(npc.getRawName(), false); - nameLine.spawnNPC(0); + nameLine = new HologramLine(npc.getRawName(), + SUPPORTS_DISPLAY ? new InteractionVehicleRenderer() : new ArmorstandVehicleRenderer()); } } Location npcLoc = npc.getStoredLocation(); - boolean updatePosition = Setting.HOLOGRAM_ALWAYS_UPDATE_POSITION.asBoolean() + Vector3d offset = new Vector3d(); + boolean updatePosition = Setting.HOLOGRAM_ALWAYS_UPDATE_POSITION.asBoolean() || currentLoc == null || currentLoc.getWorld() != npcLoc.getWorld() || currentLoc.distance(npcLoc) >= 0.001 || lastNameplateVisible != nameplateVisible || Math.abs(lastEntityBbHeight - getEntityBbHeight()) >= 0.05; @@ -349,45 +232,28 @@ public class HologramTrait extends Trait { currentLoc = npcLoc.clone(); lastEntityBbHeight = getEntityBbHeight(); } - if (nameLine != null && nameLine.hologram.isSpawned()) { - if (updatePosition && !useDisplayEntities) { - nameLine.hologram.teleport(npcLoc.clone().add(0, getEntityBbHeight(), 0), TeleportCause.PLUGIN); + if (nameLine != null) { + if (updatePosition) { + nameLine.render(offset); } if (updateName) { nameLine.setText(npc.getRawName()); } - if (useDisplayEntities && nameLine.hologram.getEntity().getVehicle() == null) { - npc.getEntity().addPassenger(nameLine.hologram.getEntity()); - } } for (int i = 0; i < lines.size(); i++) { HologramLine line = lines.get(i); - NPC hologramNPC = line.hologram; - - if (hologramNPC == null || !hologramNPC.isSpawned()) - continue; if (line.ticks > 0 && --line.ticks == 0) { lines.remove(i--).removeNPC(); - ; continue; } - if (updatePosition && !useDisplayEntities) { - Location tp = npcLoc.clone().add(0, lastEntityBbHeight + getHeight(i), 0); - hologramNPC.teleport(tp, TeleportCause.PLUGIN); + if (updatePosition) { + offset.y = getHeight(i); + line.render(offset); } - if (useDisplayEntities && hologramNPC.getEntity().getVehicle() == null) { - npc.getEntity().addPassenger(hologramNPC.getEntity()); + if (updateName) { + line.setText(line.text); } - String text = line.text; - if (ITEM_MATCHER.matcher(text).matches()) { - hologramNPC.data().set(NPC.Metadata.NAMEPLATE_VISIBLE, false); - continue; - } - if (!updateName) { - continue; - } - line.setText(text); } } @@ -421,9 +287,7 @@ public class HologramTrait extends Trait { } HologramLine line = lines.get(idx); line.setText(text); - if (line.hologram == null) { - reloadLineHolograms(); - } + reloadLineHolograms(); } /** @@ -442,7 +306,7 @@ public class HologramTrait extends Trait { * Sets the margin of a line at a specific index * * @param idx - * The index + * The line index * @param type * The margin type, top or bottom * @param margin @@ -457,63 +321,43 @@ public class HologramTrait extends Trait { reloadLineHolograms(); } - /** - * Implementation-specific method: {@see NPC.Metadata#HOLOGRAM_LINE_SUPPLIER} - */ - public void setPerPlayerTextSupplier(BiFunction nameSupplier) { - customHologramSupplier = nameSupplier; - } - - public void setUseDisplayEntities(boolean use) { - useDisplayEntities = use; - reloadLineHolograms(); - } - public void setViewRange(int range) { this.viewRange = range; reloadLineHolograms(); } - public abstract class AbstractRenderer implements HologramRenderer { - protected NPC npc; - + public class ArmorstandRenderer extends SingleEntityHologramRenderer { @Override - public void destroy() { - if (npc != null) { - npc.destroy(); - npc = null; - } + protected NPC createNPC(Entity base, String name, Vector3d offset) { + NPC npc = registry.createNPC(EntityType.ARMOR_STAND, name); + npc.getOrAddTrait(ArmorStandTrait.class).setAsHelperEntityWithName(npc); + return npc; } @Override - public void render(Entity base, Vector3d offset) { - if (npc == null) - return; - npc.getEntity().teleport(base.getLocation().clone().add(offset.x, offset.y, offset.z), + protected void render0(NPC npc, Vector3d offset) { + hologram.getEntity().teleport(npc.getStoredLocation().clone().add(offset.x, offset.y, offset.z), TeleportCause.PLUGIN); } + } - protected abstract NPC spawnNPC(Entity base, String text, Vector3d offset); + public class ArmorstandVehicleRenderer extends ArmorstandRenderer { + @Override + protected NPC createNPC(Entity base, String name, Vector3d offset) { + NPC npc = registry.createNPC(EntityType.ARMOR_STAND, name); + npc.getOrAddTrait(ArmorStandTrait.class).setAsHelperEntityWithName(npc); + return npc; + } @Override - public void updateText(Entity base, String text) { - if (npc == null) - return; - NMS.setCustomName(base, text, text); + public void render0(NPC base, Vector3d offset) { + if (hologram.getEntity().getVehicle() == null) { + base.getEntity().addPassenger(hologram.getEntity()); + } } } - public class ArmorstandRenderer extends AbstractRenderer { - @Override - protected NPC spawnNPC(Entity base, String name, Vector3d offset) { - NPC hologramNPC = registry.createNPC(EntityType.ARMOR_STAND, name); - hologramNPC.getOrAddTrait(ArmorStandTrait.class).setAsHelperEntityWithName(npc); - return hologramNPC; - } - } - - private class HologramLine implements Function { - NPC hologram; + private class HologramLine { double mb, mt; boolean persist; HologramRenderer renderer; @@ -521,55 +365,166 @@ public class HologramTrait extends Trait { int ticks; public HologramLine(String text, boolean persist) { - this(text, persist, -1); + this(text, persist, -1, + SUPPORTS_DISPLAY && Setting.DEFAULT_HOLOGRAM_RENDERER.asString().equalsIgnoreCase("interaction") + ? new InteractionVehicleRenderer() + : SUPPORTS_DISPLAY + && Setting.DEFAULT_HOLOGRAM_RENDERER.asString().equalsIgnoreCase("display") + ? new TextDisplayVehicleRenderer() + : new ArmorstandRenderer()); } public HologramLine(String text, boolean persist, int ticks) { - setText(text); - this.persist = persist; - this.ticks = ticks; + this(text, persist, ticks, + SUPPORTS_DISPLAY && Setting.DEFAULT_HOLOGRAM_RENDERER.asString().equalsIgnoreCase("interaction") + ? new InteractionVehicleRenderer() + : SUPPORTS_DISPLAY + && Setting.DEFAULT_HOLOGRAM_RENDERER.asString().equalsIgnoreCase("display") + ? new TextDisplayVehicleRenderer() + : new ArmorstandRenderer()); + } + + public HologramLine(String text, boolean persist, int ticks, HologramRenderer hr) { if (ITEM_MATCHER.matcher(text).matches()) { mb = 0.21; mt = 0.07; + hr = new ItemRenderer(); } + this.persist = persist; + this.ticks = ticks; + this.renderer = hr; + setText(text); } - @Override - public String apply(Player viewer) { - return Placeholders.replace(text, viewer, npc); + public HologramLine(String text, HologramRenderer renderer) { + this(text, false, -1, renderer); } public void removeNPC() { - if (hologram == null) - return; + renderer.destroy(); + } - hologram.destroy(); - renderer = null; - hologram = null; + public void render(Vector3d vector3d) { + renderer.render(npc, vector3d); } public void setText(String text) { this.text = text == null ? "" : text; + if (ITEM_MATCHER.matcher(text).matches()) { + renderer.destroy(); + mb = 0.21; + mt = 0.07; + renderer = new ItemRenderer(); + } + renderer.updateText(npc, text); + } + } - if (hologram != null) { - // renderer.updateText(hologram, text); - String name = Placeholders.replace(text, null, npc); - hologram.setName(name); - if (Placeholders.containsPlaceholders(text)) { - hologram.data().set(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER, this); + public static interface HologramRenderer { + void destroy(); + + String getPerPlayerText(NPC npc, Player viewer); + + default boolean isSneaking(NPC npc, Player player) { + return NMS.isSneaking(player); + } + + default void onSeenByPlayer(Player player) { + } + + void render(NPC npc, Vector3d offset); + + void updateText(NPC npc, String text); + } + + public class InteractionVehicleRenderer extends SingleEntityHologramRenderer { + private Vector3d lastOffset; + + @Override + protected NPC createNPC(Entity base, String name, Vector3d offset) { + lastOffset = new Vector3d(offset); + return registry.createNPC(EntityType.INTERACTION, name); + } + + @Override + public void onSeenByPlayer(Player player) { + if (lastOffset == null) + return; + NMS.linkTextInteraction(player, hologram.getEntity(), npc.getEntity(), lastOffset.y); + } + + @Override + public void render0(NPC npc, Vector3d offset) { + lastOffset = new Vector3d(offset); + if (hologram.getEntity().getVehicle() == null) { + npc.getEntity().addPassenger(hologram.getEntity()); + } + } + } + + public class ItemRenderer extends SingleEntityHologramRenderer { + @Override + protected NPC createNPC(Entity base, String name, Vector3d offset) { + Matcher itemMatcher = ITEM_MATCHER.matcher(name); + Material item = SpigotUtil.isUsing1_13API() ? Material.matchMaterial(itemMatcher.group(1), false) + : Material.matchMaterial(itemMatcher.group(1)); + ItemStack itemStack = new ItemStack(item, 1); + NPC npc = registry.createNPCUsingItem(EntityType.DROPPED_ITEM, "", itemStack); + npc.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)); + npc.setItemProvider(() -> itemStack); } else { - hologram.data().set(NPC.Metadata.NAMEPLATE_VISIBLE, ChatColor.stripColor(name).length() > 0); - hologram.data().remove(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER); + npc.getOrAddTrait(ScoreboardTrait.class) + .setColor(Util.matchEnum(ChatColor.values(), itemMatcher.group(2).substring(1))); } } + return npc; + } + + @Override + protected void render0(NPC npc, Vector3d offset) { + hologram.getEntity().teleport(npc.getStoredLocation().clone().add(offset.x, offset.y, offset.z), + TeleportCause.PLUGIN); + } + } + + public abstract class SingleEntityHologramRenderer implements HologramRenderer { + protected NPC hologram; + protected String text; + + protected abstract NPC createNPC(Entity base, String text, Vector3d offset); + + @Override + public void destroy() { + if (hologram != null) { + hologram.destroy(); + hologram = null; + } } - private void spawn(NPC npc, String text, Vector3d offset) { - // hologram = renderer.spawn(npc, text, offset); + @Override + public String getPerPlayerText(NPC npc, Player viewer) { + return Placeholders.replace(text, viewer, npc); + } + + @Override + public void render(NPC npc, Vector3d offset) { + if (hologram == null) { + spawnHologram(npc, offset); + } + render0(npc, offset); + } + + protected abstract void render0(NPC npc, Vector3d offset); + + protected void spawnHologram(NPC npc, Vector3d offset) { + hologram = createNPC(npc.getEntity(), text, offset); if (!hologram.hasTrait(ClickRedirectTrait.class)) { hologram.addTrait(new ClickRedirectTrait(npc)); } - hologram.data().set(NPC.Metadata.HOLOGRAM_FOR, npc.getUniqueId().toString()); + hologram.data().set(NPC.Metadata.HOLOGRAM_RENDERER, this); if (Setting.PACKET_HOLOGRAMS.asBoolean()) { hologram.addTrait(PacketNPC.class); } @@ -579,66 +534,17 @@ public class HologramTrait extends Trait { hologram.data().set(NPC.Metadata.TRACKING_RANGE, npc.data().get(NPC.Metadata.TRACKING_RANGE)); } hologram.spawn(npc.getEntity().getLocation().add(offset.x, offset.y, offset.z)); - if (customHologramSupplier != null) { - hologram.data().set(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER, - (Function) p -> customHologramSupplier.apply(text, p)); - } else if (Placeholders.containsPlaceholders(text)) { - hologram.data().set(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER, this); - } - } - - public void spawnNPC(double height) { - String name = Placeholders.replace(text, null, npc); - hologram = createHologram(name, height); - if (customHologramSupplier != null) { - hologram.data().set(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER, - (Function) p -> customHologramSupplier.apply(text, p)); - } else if (Placeholders.containsPlaceholders(text)) { - hologram.data().set(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER, this); - } - } - } - - public static interface HologramRenderer { - void destroy(); - - void render(Entity base, Vector3d offset); - - void updateText(Entity base, String text); - } - - public class InteractionRenderer extends AbstractRenderer { - public void render(Entity base, String name, Vector3d offset) { - if (this.npc.getEntity().getVehicle() == null) { - HologramTrait.this.npc.getEntity().addPassenger(this.npc.getEntity()); - } } @Override - protected NPC spawnNPC(Entity base, String name, Vector3d offset) { - return registry.createNPC(EntityType.INTERACTION, name); - } - } - - public class ItemRenderer extends AbstractRenderer { - @Override - protected NPC spawnNPC(Entity base, String name, Vector3d offset) { - Matcher itemMatcher = ITEM_MATCHER.matcher(name); - 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); - 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))); - } + public void updateText(NPC npc, String text) { + this.text = Placeholders.replace(text, null, npc); + if (hologram == null) + return; + hologram.setName(text); + if (!Placeholders.containsPlaceholders(text)) { + hologram.data().set(NPC.Metadata.NAMEPLATE_VISIBLE, Messaging.stripColor(text).length() > 0); } - return itemNPC; } } @@ -653,21 +559,35 @@ public class HologramTrait extends Trait { return Collections.emptyList(); } - private static List LINE_ARGS = ImmutableList.of("set", "remove", "margintop", "marginbottom"); + private static Set LINE_ARGS = ImmutableSet.of("set", "remove", "margintop", "marginbottom"); } - public class TextDisplayRenderer extends AbstractRenderer { - public void render(Entity base, String name, Vector3d offset) { - if (this.npc.getEntity().getVehicle() == null) { - HologramTrait.this.npc.getEntity().addPassenger(this.npc.getEntity()); - } + public class TextDisplayVehicleRenderer extends SingleEntityHologramRenderer { + @Override + protected NPC createNPC(Entity base, String name, Vector3d offset) { + NPC npc = registry.createNPC(EntityType.TEXT_DISPLAY, name); + return npc; } @Override - protected NPC spawnNPC(Entity base, String name, Vector3d offset) { - return registry.createNPC(EntityType.TEXT_DISPLAY, name); + public void render0(NPC base, Vector3d offset) { + TextDisplay disp = (TextDisplay) hologram.getEntity(); + disp.setBillboard(Billboard.CENTER); + Transformation tf = disp.getTransformation(); + tf.getTranslation().y = (float) offset.y + 0.1f; + disp.setTransformation(tf); + if (hologram.getEntity().getVehicle() == null) { + base.getEntity().addPassenger(hologram.getEntity()); + } } } - private static Pattern ITEM_MATCHER = Pattern.compile(""); + private static final Pattern ITEM_MATCHER = Pattern.compile(""); + private static boolean SUPPORTS_DISPLAY = false; + static { + try { + SUPPORTS_DISPLAY = Class.forName("org.bukkit.entity.Display") != null; + } catch (Throwable e) { + } + } } 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 daaf74f71..47b459268 100644 --- a/main/src/main/java/net/citizensnpcs/trait/text/Text.java +++ b/main/src/main/java/net/citizensnpcs/trait/text/Text.java @@ -12,7 +12,6 @@ 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; @@ -20,11 +19,9 @@ 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; @@ -44,9 +41,9 @@ public class Text extends Trait implements Runnable, Listener { 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(); + @Persist private double range = Setting.DEFAULT_TALK_CLOSE_RANGE.asDouble(); @Persist(value = "realistic-looking") private boolean realisticLooker = Setting.DEFAULT_REALISTIC_LOOKING.asBoolean(); @@ -54,11 +51,11 @@ public class Text extends Trait implements Runnable, Listener { private boolean speechBubbles; @Persist(value = "talk-close") private boolean talkClose = Setting.DEFAULT_TALK_CLOSE.asBoolean(); - private final List text = new ArrayList<>(); + @Persist + private volatile List text = new ArrayList<>(); public Text() { super("text"); - plugin = CitizensAPI.getPlugin(); } /** @@ -87,9 +84,9 @@ public class Text extends Trait implements Runnable, Listener { * 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); + Conversation conversation = new ConversationFactory(CitizensAPI.getPlugin()).withLocalEcho(false) + .withEscapeSequence("/npc text").withEscapeSequence("exit").withModality(false) + .withFirstPrompt(new TextBasePrompt(this)).buildConversation(player); return new Editor() { @Override public void begin() { @@ -135,18 +132,6 @@ public class Text extends Trait implements Runnable, Listener { 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) @@ -158,8 +143,11 @@ public class Text extends Trait implements Runnable, Listener { } } - private void populateDefaultText() { - text.addAll(Setting.DEFAULT_TEXT.asList()); + @Override + public void onSpawn() { + if (text.isEmpty()) { + text.addAll(Setting.DEFAULT_TEXT.asList()); + } } /** @@ -179,15 +167,6 @@ public class Text extends Trait implements Runnable, Listener { } } - @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++) { diff --git a/v1_14_R1/src/main/java/net/citizensnpcs/nms/v1_14_R1/entity/RabbitController.java b/v1_14_R1/src/main/java/net/citizensnpcs/nms/v1_14_R1/entity/RabbitController.java index 03f228372..c030d2d46 100644 --- a/v1_14_R1/src/main/java/net/citizensnpcs/nms/v1_14_R1/entity/RabbitController.java +++ b/v1_14_R1/src/main/java/net/citizensnpcs/nms/v1_14_R1/entity/RabbitController.java @@ -232,7 +232,7 @@ public class RabbitController extends MobEntityController { initPathfinder(); // make sure the evil goals include the default AI goals } super.setRabbitType(type); - NMSImpl.clearGoals(npc, goalSelector, targetSelector); + NMSImpl.clearGoals(goalSelector, targetSelector); } else if (NMSImpl.getRabbitTypeField() != null) { datawatcher.set(NMSImpl.getRabbitTypeField(), type); }