diff --git a/api/src/main/java/de/oliver/fancyholograms/api/Hologram.java b/api/src/main/java/de/oliver/fancyholograms/api/Hologram.java index 364b0dbc..4636bd5e 100644 --- a/api/src/main/java/de/oliver/fancyholograms/api/Hologram.java +++ b/api/src/main/java/de/oliver/fancyholograms/api/Hologram.java @@ -144,7 +144,7 @@ protected boolean shouldHologramBeShown(@NotNull final Player player) { return false; } - if (!getData().getDisplayData().isVisibleByDefault() && !player.hasPermission("fancyholograms.viewhologram." + data.getName())) { + if (!this.getData().getDisplayData().getVisibility().canSee(player, this)) { return false; } diff --git a/api/src/main/java/de/oliver/fancyholograms/api/data/DisplayHologramData.java b/api/src/main/java/de/oliver/fancyholograms/api/data/DisplayHologramData.java index 14186f05..c186122f 100644 --- a/api/src/main/java/de/oliver/fancyholograms/api/data/DisplayHologramData.java +++ b/api/src/main/java/de/oliver/fancyholograms/api/data/DisplayHologramData.java @@ -1,6 +1,7 @@ package de.oliver.fancyholograms.api.data; import de.oliver.fancyholograms.api.FancyHologramsPlugin; +import de.oliver.fancyholograms.api.data.property.visibility.Visibility; import de.oliver.fancylib.FancyLib; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -11,6 +12,7 @@ import org.joml.Vector3f; import java.util.Locale; +import java.util.Optional; public class DisplayHologramData implements Data { @@ -21,6 +23,7 @@ public class DisplayHologramData implements Data { public static final float DEFAULT_SHADOW_STRENGTH = 1.0f; public static final int DEFAULT_VISIBILITY_DISTANCE = -1; public static final boolean DEFAULT_IS_VISIBLE = true; + public static final Visibility DEFAULT_VISIBILITY = Visibility.ALL; private Location location; private Display.Billboard billboard = DEFAULT_BILLBOARD; @@ -30,12 +33,12 @@ public class DisplayHologramData implements Data { private float shadowRadius = DEFAULT_SHADOW_RADIUS; private float shadowStrength = DEFAULT_SHADOW_STRENGTH; private int visibilityDistance = DEFAULT_VISIBILITY_DISTANCE; - private boolean visibleByDefault = DEFAULT_IS_VISIBLE; + private Visibility visibility = DEFAULT_VISIBILITY; private String linkedNpcName; public DisplayHologramData(Location location, Display.Billboard billboard, Vector3f scale, Vector3f translation, Display.Brightness brightness, float shadowRadius, float shadowStrength, - int visibilityDistance, String linkedNpcName, boolean visibleByDefault) { + int visibilityDistance, String linkedNpcName, Visibility visibility) { this.location = location; this.billboard = billboard; this.scale = scale; @@ -45,7 +48,7 @@ public DisplayHologramData(Location location, Display.Billboard billboard, Vecto this.shadowStrength = shadowStrength; this.visibilityDistance = visibilityDistance; this.linkedNpcName = linkedNpcName; - this.visibleByDefault = visibleByDefault; + this.visibility = visibility; } public DisplayHologramData() { @@ -62,7 +65,7 @@ public static DisplayHologramData getDefault(Location location) { DEFAULT_SHADOW_STRENGTH, DEFAULT_VISIBILITY_DISTANCE, null, - DEFAULT_IS_VISIBLE + DEFAULT_VISIBILITY ); } @@ -81,7 +84,7 @@ public void write(ConfigurationSection section, String name) { section.set("shadow_radius", shadowRadius); section.set("shadow_strength", shadowStrength); section.set("visibility_distance", visibilityDistance); - section.set("visible_by_default", visibleByDefault); + section.set("visibility", visibility.toString()); if (billboard == Display.Billboard.CENTER) { @@ -126,7 +129,24 @@ public void read(ConfigurationSection section, String name) { shadowStrength = (float) section.getDouble("shadow_strength", DEFAULT_SHADOW_STRENGTH); visibilityDistance = section.getInt("visibility_distance", DEFAULT_VISIBILITY_DISTANCE); linkedNpcName = section.getString("linkedNpc"); - visibleByDefault = section.getBoolean("visible_by_default", DEFAULT_IS_VISIBLE); + + visibility = Optional.ofNullable(section.getString("visibility")) + .flatMap(Visibility::byString) + .orElseGet(() -> { + final var visibleByDefault = section.getBoolean("visible_by_default", DEFAULT_IS_VISIBLE); + if (section.contains("visible_by_default")) { + section.set("visible_by_default", null); + } + if (visibleByDefault) { + return Visibility.ALL; + } else { + return Visibility.PERMISSION_REQUIRED; + } + }); + + if (section.contains("visible_by_default")) { + section.set("visible_by_default", null); + } String billboardStr = section.getString("billboard", DisplayHologramData.DEFAULT_BILLBOARD.name()); billboard = switch (billboardStr.toLowerCase()) { @@ -213,12 +233,40 @@ public DisplayHologramData setVisibilityDistance(int visibilityDistance) { return this; } + /** + * Returns to the default state of visibility. + * This method is deprecated, to control the visibility of the hologram, use {@link #getVisibility()}. + * + * @return {@code true} if hologram can see all players, else {@code false}. + */ + @Deprecated(forRemoval = true) public boolean isVisibleByDefault() { - return visibleByDefault; + return this.getVisibility() == Visibility.ALL; } + /** + * Set the default state of visibility. + * This method is deprecated, to control the visibility of the hologram, use {@link #setVisibility(Visibility)}. + */ + @Deprecated(forRemoval = true) public void setVisibleByDefault(boolean visibleByDefault) { - this.visibleByDefault = visibleByDefault; + this.setVisibility(visibleByDefault ? Visibility.ALL : Visibility.PERMISSION_REQUIRED); + } + + /** + * Get the type of visibility for the hologram. + * + * @return type of visibility. + */ + public Visibility getVisibility() { + return this.visibility; + } + + /** + * Set the type of visibility for the hologram. + */ + public void setVisibility(Visibility visibility) { + this.visibility = visibility; } public String getLinkedNpcName() { @@ -242,7 +290,7 @@ public Data copy() { shadowStrength, visibilityDistance, linkedNpcName, - visibleByDefault + visibility ); } } diff --git a/api/src/main/java/de/oliver/fancyholograms/api/data/property/visibility/Visibility.java b/api/src/main/java/de/oliver/fancyholograms/api/data/property/visibility/Visibility.java new file mode 100644 index 00000000..5f990977 --- /dev/null +++ b/api/src/main/java/de/oliver/fancyholograms/api/data/property/visibility/Visibility.java @@ -0,0 +1,49 @@ +package de.oliver.fancyholograms.api.data.property.visibility; + +import de.oliver.fancyholograms.api.Hologram; +import org.bukkit.entity.Player; + +import java.util.Arrays; +import java.util.Optional; + +public enum Visibility { + /** + * Everybody can see a hologram. + */ + ALL((player, hologram) -> true), + /** + * Manual control. + */ + MANUAL((player, hologram) -> hologram.isShown(player)), + /** + * The player needs permission to see a specific hologram. + */ + PERMISSION_REQUIRED( + (player, hologram) -> player.hasPermission("fancyholograms.viewhologram." + hologram.getData().getName()) + ); + + + private final VisibilityPredicate predicate; + + + Visibility(VisibilityPredicate predicate) { + this.predicate = predicate; + } + + + public boolean canSee(Player player, Hologram hologram) { + return this.predicate.canSee(player, hologram); + } + + + public static Optional byString(String value) { + return Arrays.stream(Visibility.values()) + .filter(visibility -> visibility.toString().equalsIgnoreCase(value)) + .findFirst(); + } + + public interface VisibilityPredicate { + + boolean canSee(Player player, Hologram hologram); + } +} diff --git a/src/main/java/de/oliver/fancyholograms/commands/HologramCMD.java b/src/main/java/de/oliver/fancyholograms/commands/HologramCMD.java index 396ca7b2..4f21fb79 100644 --- a/src/main/java/de/oliver/fancyholograms/commands/HologramCMD.java +++ b/src/main/java/de/oliver/fancyholograms/commands/HologramCMD.java @@ -149,7 +149,7 @@ public boolean execute(@NotNull CommandSender sender, @NotNull String label, @No final var usingNpcs = FancyHolograms.isUsingFancyNpcs(); - List suggestions = new ArrayList<>(Arrays.asList("position", "moveHere", "moveTo", "rotate", "rotatepitch", "billboard", "scale", "visibilityDistance", "visibleByDefault", "shadowRadius", "shadowStrength", usingNpcs ? "linkWithNpc" : "", usingNpcs ? "unlinkWithNpc" : "")); + List suggestions = new ArrayList<>(Arrays.asList("position", "moveHere", "moveTo", "rotate", "rotatepitch", "billboard", "scale", "visibilityDistance", "visibility", "shadowRadius", "shadowStrength", usingNpcs ? "linkWithNpc" : "", usingNpcs ? "unlinkWithNpc" : "")); suggestions.addAll(type.getCommands()); return suggestions.stream().filter(input -> input.toLowerCase().startsWith(args[2].toLowerCase(Locale.ROOT))).toList(); @@ -209,7 +209,8 @@ public boolean execute(@NotNull CommandSender sender, @NotNull String label, @No yield FancyNpcsPlugin.get().getNpcManager().getAllNpcs().stream().map(npc -> npc.getData().getName()); } case "block" -> Arrays.stream(Material.values()).filter(Material::isBlock).map(Enum::name); - case "seethrough", "visiblebydefault" -> Stream.of("true", "false"); + case "seethrough" -> Stream.of("true", "false"); + case "visibility" -> new VisibilityCMD().tabcompletion(sender, hologram, args).stream(); default -> null; }; @@ -297,7 +298,7 @@ private boolean edit(@NotNull final CommandSender player, @NotNull final Hologra case "scale" -> new ScaleCMD().run(player, hologram, args); case "updatetextinterval" -> new UpdateTextIntervalCMD().run(player, hologram, args); case "visibilitydistance" -> new VisibilityDistanceCMD().run(player, hologram, args); - case "visiblebydefault" -> new VisibleByDefaultCMD().run(player, hologram, args); + case "visibility" -> new VisibilityCMD().run(player, hologram, args); case "linkwithnpc" -> new LinkWithNpcCMD().run(player, hologram, args); case "shadowradius" -> new ShadowRadiusCMD().run(player, hologram, args); case "shadowstrength" -> new ShadowStrengthCMD().run(player, hologram, args); diff --git a/src/main/java/de/oliver/fancyholograms/commands/hologram/VisibleByDefaultCMD.java b/src/main/java/de/oliver/fancyholograms/commands/hologram/VisibilityCMD.java similarity index 55% rename from src/main/java/de/oliver/fancyholograms/commands/hologram/VisibleByDefaultCMD.java rename to src/main/java/de/oliver/fancyholograms/commands/hologram/VisibilityCMD.java index a10bf2a7..0ad35c2b 100644 --- a/src/main/java/de/oliver/fancyholograms/commands/hologram/VisibleByDefaultCMD.java +++ b/src/main/java/de/oliver/fancyholograms/commands/hologram/VisibilityCMD.java @@ -2,44 +2,50 @@ import de.oliver.fancyholograms.FancyHolograms; import de.oliver.fancyholograms.api.Hologram; +import de.oliver.fancyholograms.api.data.property.visibility.Visibility; import de.oliver.fancyholograms.commands.Subcommand; import de.oliver.fancylib.MessageHelper; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; import java.util.List; +import java.util.Objects; -public class VisibleByDefaultCMD implements Subcommand { +public class VisibilityCMD implements Subcommand { @Override public List tabcompletion(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { - return null; + return Arrays.stream( + Visibility.values() + ).map(Objects::toString).toList(); } @Override public boolean run(@NotNull CommandSender player, @Nullable Hologram hologram, @NotNull String[] args) { - var visibleByDefault = Boolean.parseBoolean(args[3]); - if (hologram == null) { + final var optionalVisibility = Visibility.byString(args[3]); + if (hologram == null || optionalVisibility.isEmpty()) { return false; } + final var visibility = optionalVisibility.get(); final var copied = hologram.getData().copy(); - copied.getDisplayData().setVisibleByDefault(visibleByDefault); + copied.getDisplayData().setVisibility(visibility); - if (hologram.getData().getDisplayData().isVisibleByDefault() == copied.getDisplayData().isVisibleByDefault()) { - MessageHelper.warning(player, "This hologram already has visibility by default set to " + visibleByDefault); + if (hologram.getData().getDisplayData().getVisibility() == copied.getDisplayData().getVisibility()) { + MessageHelper.warning(player, "This hologram already has visibility set to " + visibility); return false; } - hologram.getData().getDisplayData().setVisibleByDefault(copied.getDisplayData().isVisibleByDefault()); + hologram.getData().getDisplayData().setVisibility(copied.getDisplayData().getVisibility()); if (FancyHolograms.get().getHologramConfiguration().isSaveOnChangedEnabled()) { FancyHolograms.get().getHologramStorage().save(hologram); } - MessageHelper.success(player, "Changed visibility by default to " + visibleByDefault); + MessageHelper.success(player, "Changed visibility to " + visibility); return true; } } diff --git a/src/main/java/de/oliver/fancyholograms/storage/FlatFileHologramStorage.java b/src/main/java/de/oliver/fancyholograms/storage/FlatFileHologramStorage.java index 754a64d8..d4ef2c04 100644 --- a/src/main/java/de/oliver/fancyholograms/storage/FlatFileHologramStorage.java +++ b/src/main/java/de/oliver/fancyholograms/storage/FlatFileHologramStorage.java @@ -5,6 +5,7 @@ import de.oliver.fancyholograms.api.HologramStorage; import de.oliver.fancyholograms.api.HologramType; import de.oliver.fancyholograms.api.data.*; +import de.oliver.fancyholograms.api.data.property.visibility.Visibility; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; import org.bukkit.Bukkit; @@ -22,6 +23,7 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; + public class FlatFileHologramStorage implements HologramStorage { private static final ReadWriteLock lock = new ReentrantReadWriteLock(); @@ -232,7 +234,21 @@ public static HologramData readHologram(String name, ConfigurationSection config final var billboardName = config.getString("billboard", DisplayHologramData.DEFAULT_BILLBOARD.name()); final var textAlignmentName = config.getString("text_alignment", TextHologramData.DEFAULT_TEXT_ALIGNMENT.name()); final var linkedNpc = config.getString("linkedNpc"); - final var visibleByDefault = config.getBoolean("visible_by_default", DisplayHologramData.DEFAULT_IS_VISIBLE); + + + final var visibility = Optional.ofNullable(config.getString("visibility")) + .flatMap(Visibility::byString) + .orElseGet(() -> { + final var visibleByDefault = config.getBoolean("visible_by_default", DisplayHologramData.DEFAULT_IS_VISIBLE); + if (config.contains("visible_by_default")) { + config.set("visible_by_default", null); + } + if (visibleByDefault) { + return Visibility.ALL; + } else { + return Visibility.PERMISSION_REQUIRED; + } + }); final var billboard = switch (billboardName.toLowerCase(Locale.ROOT)) { case "fixed" -> Display.Billboard.FIXED; @@ -259,7 +275,7 @@ public static HologramData readHologram(String name, ConfigurationSection config } - DisplayHologramData displayData = new DisplayHologramData(location, billboard, new Vector3f((float) scaleX, (float) scaleY, (float) scaleZ), DisplayHologramData.DEFAULT_TRANSLATION, null, (float) shadowRadius, (float) shadowStrength, visibilityDistance, linkedNpc, visibleByDefault); + DisplayHologramData displayData = new DisplayHologramData(location, billboard, new Vector3f((float) scaleX, (float) scaleY, (float) scaleZ), DisplayHologramData.DEFAULT_TRANSLATION, null, (float) shadowRadius, (float) shadowStrength, visibilityDistance, linkedNpc, visibility); TextHologramData textData = new TextHologramData(text, background, textAlignment, textHasShadow, isSeeThrough, textUpdateInterval);