Allow holograms to be in non-loaded worlds

This commit is contained in:
filoghost 2021-08-07 18:39:19 +02:00
parent 5225d34cb6
commit 9b21ffdf76
20 changed files with 177 additions and 70 deletions

View File

@ -112,12 +112,12 @@ public interface Hologram {
@NotNull HologramPosition getPosition();
/**
* Returns the world of the hologram position.
* Returns the world name of the hologram position.
*
* @return the world of the hologram position
* @return the world name of the hologram position
* @since 1
*/
@NotNull World getPositionWorld();
@NotNull String getPositionWorldName();
/**
* Returns the X coordinate of the hologram position.
@ -151,6 +151,17 @@ public interface Hologram {
*/
void setPosition(@NotNull HologramPosition position);
/**
* Moves the hologram to the given position.
*
* @param worldName the world name where the hologram should be moved
* @param x the X coordinate
* @param y the Y coordinate
* @param z the Z coordinate
* @since 1
*/
void setPosition(@NotNull String worldName, double x, double y, double z);
/**
* Moves the hologram to the given position.
*

View File

@ -9,6 +9,7 @@ import me.filoghost.holographicdisplays.api.internal.HolographicDisplaysAPIProvi
import org.bukkit.Location;
import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface HologramPosition {
@ -16,11 +17,19 @@ public interface HologramPosition {
return HolographicDisplaysAPIProvider.getImplementation().createHologramPosition(world, x, y, z);
}
static @NotNull HologramPosition create(@NotNull String worldName, double x, double y, double z) {
return HolographicDisplaysAPIProvider.getImplementation().createHologramPosition(worldName, x, y, z);
}
static @NotNull HologramPosition fromLocation(@NotNull Location location) {
return HolographicDisplaysAPIProvider.getImplementation().createHologramPosition(location);
}
@NotNull World getWorld();
@NotNull String getWorldName();
void setWorldName(@NotNull String worldName);
@Nullable World getWorldIfLoaded();
void setWorld(@NotNull World world);

View File

@ -35,6 +35,8 @@ public abstract class HolographicDisplaysAPIProvider {
public abstract HologramPosition createHologramPosition(World world, double x, double y, double z);
public abstract HologramPosition createHologramPosition(String worldName, double x, double y, double z);
public abstract HologramPosition createHologramPosition(Location location);
}

View File

@ -110,7 +110,7 @@ public class HolographicDisplays extends FCommonsPlugin {
new DatabaseLegacyUpgrade(configManager, errorCollector).tryRun();
// Load the configuration
load(true, errorCollector);
load(errorCollector);
// Add packet listener for currently online players (may happen if the plugin is disabled and re-enabled)
for (Player player : Bukkit.getOnlinePlayers()) {
@ -155,7 +155,7 @@ public class HolographicDisplays extends FCommonsPlugin {
HologramsAPIProvider.setImplementation(new V2HologramsAPIProvider(apiHologramManager, placeholderRegistry));
}
public void load(boolean deferHologramsCreation, ErrorCollector errorCollector) {
public void load(ErrorCollector errorCollector) {
internalHologramManager.clearAll();
configManager.reloadStaticReplacements(errorCollector);
@ -167,13 +167,8 @@ public class HolographicDisplays extends FCommonsPlugin {
bungeeServerTracker.restart(Settings.bungeeRefreshSeconds, TimeUnit.SECONDS);
HologramDatabase hologramDatabase = configManager.loadHologramDatabase(errorCollector);
if (deferHologramsCreation) {
// For the initial load: holograms are loaded later, when the worlds are ready
Bukkit.getScheduler().runTask(this, () -> hologramDatabase.createHolograms(internalHologramManager, errorCollector));
} else {
hologramDatabase.createHolograms(internalHologramManager, errorCollector);
}
}
@Override
public void onDisable() {

View File

@ -43,7 +43,13 @@ public class DefaultHolographicDisplaysAPIProvider extends HolographicDisplaysAP
@Override
public HologramPosition createHologramPosition(World world, double x, double y, double z) {
return new APIHologramPosition(world, x, y, z);
Preconditions.notNull(world, "world");
return new APIHologramPosition(world.getName(), x, y, z);
}
@Override
public HologramPosition createHologramPosition(String worldName, double x, double y, double z) {
return new APIHologramPosition(worldName, x, y, z);
}
@Override

View File

@ -81,7 +81,7 @@ public class V2HologramAdapter implements Hologram {
@Override
public Location getLocation() {
return new Location(v3Hologram.getPositionWorld(), v3Hologram.getPositionX(), v3Hologram.getPositionY(), v3Hologram.getPositionZ());
return new Location(v3Hologram.getPositionWorldIfLoaded(), v3Hologram.getPositionX(), v3Hologram.getPositionY(), v3Hologram.getPositionZ());
}
@Override
@ -101,7 +101,7 @@ public class V2HologramAdapter implements Hologram {
@Override
public World getWorld() {
return v3Hologram.getPositionWorld();
return v3Hologram.getPositionWorldIfLoaded();
}
@Override

View File

@ -59,7 +59,7 @@ public class ListCommand extends HologramSubCommand {
sender.sendMessage(ColorScheme.SECONDARY_DARKER + "- " + ColorScheme.SECONDARY_BOLD + hologram.getName()
+ " " + ColorScheme.SECONDARY_DARKER + "at"
+ " x: " + position.getBlockX() + ", y: " + position.getBlockY() + ", z: " + position.getBlockZ()
+ " (lines: " + hologram.getLineCount() + ", world: \"" + position.getWorld().getName() + "\")");
+ " (lines: " + hologram.getLineCount() + ", world: \"" + position.getWorldName() + "\")");
}
}

View File

@ -45,7 +45,7 @@ public class NearCommand extends HologramSubCommand {
for (InternalHologram hologram : hologramEditor.getHolograms()) {
BaseHologramPosition position = hologram.getBasePosition();
if (position.getWorld().equals(world) && position.distance(player.getLocation()) <= radius) {
if (position.isInWorld(world) && position.distance(player.getLocation()) <= radius) {
nearHolograms.add(hologram);
}
}

View File

@ -29,7 +29,7 @@ public class ReloadCommand extends HologramSubCommand {
@Override
public void execute(CommandSender sender, String[] args, SubCommandContext context) {
PrintableErrorCollector errorCollector = new PrintableErrorCollector();
holographicDisplays.load(false, errorCollector);
holographicDisplays.load(errorCollector);
if (!errorCollector.hasErrors()) {
sender.sendMessage(ColorScheme.PRIMARY + "Configuration reloaded successfully.");

View File

@ -11,8 +11,6 @@ import me.filoghost.holographicdisplays.plugin.hologram.base.BaseHologramPositio
import me.filoghost.holographicdisplays.plugin.hologram.internal.InternalHologram;
import me.filoghost.holographicdisplays.plugin.hologram.internal.InternalHologramLine;
import me.filoghost.holographicdisplays.plugin.hologram.internal.InternalHologramManager;
import org.bukkit.Bukkit;
import org.bukkit.World;
import java.util.ArrayList;
import java.util.List;
@ -38,7 +36,7 @@ public class HologramConfig {
BaseHologramPosition position = hologram.getBasePosition();
this.positionConfigSection = new ConfigSection();
positionConfigSection.setString("world", position.getWorld().getName());
positionConfigSection.setString("world", position.getWorldName());
positionConfigSection.setDouble("x", position.getX());
positionConfigSection.setDouble("y", position.getY());
positionConfigSection.setDouble("z", position.getZ());
@ -81,14 +79,7 @@ public class HologramConfig {
double x = positionConfigSection.getRequiredDouble("x");
double y = positionConfigSection.getRequiredDouble("y");
double z = positionConfigSection.getRequiredDouble("z");
World world = Bukkit.getWorld(worldName);
if (world == null) {
throw new HologramLoadException("world \"" + worldName + "\" is not currently loaded");
}
return new BaseHologramPosition(world, x, y, z);
return new BaseHologramPosition(worldName, x, y, z);
} catch (ConfigValueException e) {
throw new HologramLoadException("invalid position attribute \"" + e.getConfigPath() + "\"", e);
}

View File

@ -136,12 +136,12 @@ public class APIHologram extends BaseHologram<APIHologramLine> implements Hologr
@Override
public @NotNull HologramPosition getPosition() {
return new APIHologramPosition(getPositionWorld(), getPositionX(), getPositionY(), getPositionZ());
return new APIHologramPosition(getPositionWorldName(), getPositionX(), getPositionY(), getPositionZ());
}
@Override
public void setPosition(@NotNull HologramPosition position) {
super.setPosition(position.getWorld(), position.getX(), position.getY(), position.getZ());
super.setPosition(position.getWorldName(), position.getX(), position.getY(), position.getZ());
}
@Override

View File

@ -8,12 +8,11 @@ package me.filoghost.holographicdisplays.plugin.hologram.api;
import me.filoghost.holographicdisplays.api.hologram.HologramPosition;
import me.filoghost.holographicdisplays.plugin.hologram.base.BaseHologramPosition;
import org.bukkit.Location;
import org.bukkit.World;
public class APIHologramPosition extends BaseHologramPosition implements HologramPosition {
public APIHologramPosition(World world, double x, double y, double z) {
super(world, x, y, z);
public APIHologramPosition(String worldName, double x, double y, double z) {
super(worldName, x, y, z);
}
public APIHologramPosition(Location location) {

View File

@ -9,17 +9,20 @@ import me.filoghost.fcommons.Preconditions;
import me.filoghost.holographicdisplays.plugin.config.Settings;
import me.filoghost.holographicdisplays.plugin.hologram.tracking.LineTrackerManager;
import me.filoghost.holographicdisplays.plugin.util.CachedBoolean;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
public abstract class BaseHologram<T extends EditableHologramLine> extends BaseHologramComponent {
@ -27,16 +30,17 @@ public abstract class BaseHologram<T extends EditableHologramLine> extends BaseH
private final List<T> unmodifiableLinesView;
private final LineTrackerManager lineTrackerManager;
private World world;
private @Nullable World world;
private String worldName;
private double x, y, z;
private int chunkX, chunkZ;
private final CachedBoolean isInLoadedChunk = new CachedBoolean(() -> world.isChunkLoaded(chunkX, chunkZ));
private final CachedBoolean isInLoadedChunk = new CachedBoolean(() -> world != null && world.isChunkLoaded(chunkX, chunkZ));
public BaseHologram(BaseHologramPosition position, LineTrackerManager lineTrackerManager) {
this.lines = new ArrayList<>();
this.unmodifiableLinesView = Collections.unmodifiableList(lines);
this.lineTrackerManager = lineTrackerManager;
setPosition(position.getWorld(), position.getX(), position.getY(), position.getZ());
setPosition(position);
}
protected abstract boolean isVisibleTo(Player player);
@ -129,13 +133,17 @@ public abstract class BaseHologram<T extends EditableHologramLine> extends BaseH
}
public BaseHologramPosition getBasePosition() {
return new BaseHologramPosition(getPositionWorld(), getPositionX(), getPositionY(), getPositionZ());
return new BaseHologramPosition(getPositionWorldName(), getPositionX(), getPositionY(), getPositionZ());
}
public @NotNull World getPositionWorld() {
public @Nullable World getPositionWorldIfLoaded() {
return world;
}
public @NotNull String getPositionWorldName() {
return worldName;
}
public double getPositionX() {
return x;
}
@ -150,16 +158,22 @@ public abstract class BaseHologram<T extends EditableHologramLine> extends BaseH
public void setPosition(@NotNull BaseHologramPosition position) {
Preconditions.notNull(position, "position");
setPosition(position.getWorld(), position.getX(), position.getY(), position.getZ());
setPosition(position.getWorldName(), position.getX(), position.getY(), position.getZ());
}
public void setPosition(@NotNull Location location) {
Preconditions.notNull(location, "location");
setPosition(location.getWorld(), location.getX(), location.getY(), location.getZ());
Preconditions.notNull(location.getWorld(), "location's world");
setPosition(location.getWorld().getName(), location.getX(), location.getY(), location.getZ());
}
public void setPosition(@NotNull World world, double x, double y, double z) {
Preconditions.notNull(world, "world");
setPosition(world.getName(), x, y, z);
}
public void setPosition(@NotNull String worldName, double x, double y, double z) {
Preconditions.notNull(worldName, "worldName");
checkNotDeleted();
this.x = x;
@ -168,8 +182,9 @@ public abstract class BaseHologram<T extends EditableHologramLine> extends BaseH
int chunkX = getChunkCoordinate(x);
int chunkZ = getChunkCoordinate(z);
if (this.world != world || this.chunkX != chunkX || this.chunkZ != chunkZ) {
this.world = world;
if (!Objects.equals(this.worldName, worldName) || this.chunkX != chunkX || this.chunkZ != chunkZ) {
this.world = Bukkit.getWorld(worldName);
this.worldName = worldName;
this.chunkX = chunkX;
this.chunkZ = chunkZ;
this.isInLoadedChunk.invalidate();
@ -201,6 +216,20 @@ public abstract class BaseHologram<T extends EditableHologramLine> extends BaseH
}
}
protected void onWorldLoad(World world) {
if (BaseHologramPosition.isSameWorld(world, this.worldName)) {
this.world = world;
isInLoadedChunk.invalidate();
}
}
protected void onWorldUnload(World world) {
if (BaseHologramPosition.isSameWorld(world, this.worldName)) {
this.world = null;
isInLoadedChunk.set(false);
}
}
protected void onChunkLoad(Chunk chunk) {
if (world == chunk.getWorld() && chunkX == chunk.getX() && chunkZ == chunk.getZ()) {
isInLoadedChunk.set(true);

View File

@ -12,6 +12,7 @@ import org.bukkit.GameMode;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.Nullable;
public abstract class BaseHologramLine extends BaseHologramComponent implements EditableHologramLine {
@ -44,6 +45,10 @@ public abstract class BaseHologramLine extends BaseHologramComponent implements
setChanged();
}
public @Nullable World getWorldIfLoaded() {
return hologram.getPositionWorldIfLoaded();
}
public double getX() {
return x;
}
@ -56,10 +61,6 @@ public abstract class BaseHologramLine extends BaseHologramComponent implements
return z;
}
public World getWorld() {
return hologram.getPositionWorld();
}
public boolean isInLoadedChunk() {
return hologram.isInLoadedChunk();
}

View File

@ -6,6 +6,7 @@
package me.filoghost.holographicdisplays.plugin.hologram.base;
import org.bukkit.Chunk;
import org.bukkit.World;
import java.util.ArrayList;
import java.util.Collections;
@ -39,6 +40,18 @@ public abstract class BaseHologramManager<H extends BaseHologram<?>> {
}
}
public void onWorldLoad(World world) {
for (H hologram : holograms) {
hologram.onWorldLoad(world);
}
}
public void onWorldUnload(World world) {
for (H hologram : holograms) {
hologram.onWorldUnload(world);
}
}
public void onChunkLoad(Chunk chunk) {
for (H hologram : holograms) {
hologram.onChunkLoad(chunk);

View File

@ -6,19 +6,23 @@
package me.filoghost.holographicdisplays.plugin.hologram.base;
import me.filoghost.fcommons.Preconditions;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.util.NumberConversions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
public class BaseHologramPosition {
private World world;
private String worldName;
private double x, y, z;
public BaseHologramPosition(World world, double x, double y, double z) {
Preconditions.notNull(world, "world");
this.world = world;
public BaseHologramPosition(String worldName, double x, double y, double z) {
Preconditions.notNull(worldName, "worldName");
this.worldName = worldName;
this.x = x;
this.y = y;
this.z = z;
@ -27,19 +31,32 @@ public class BaseHologramPosition {
public BaseHologramPosition(Location location) {
Preconditions.notNull(location, "location");
Preconditions.notNull(location.getWorld(), "location's world");
this.world = location.getWorld();
this.worldName = location.getWorld().getName();
this.x = location.getX();
this.y = location.getY();
this.z = location.getZ();
}
public @NotNull World getWorld() {
return world;
public @NotNull String getWorldName() {
return worldName;
}
public void setWorldName(@NotNull String worldName) {
Preconditions.notNull(worldName, "worldName");
this.worldName = worldName;
}
public boolean isInWorld(World world) {
return world != null && isSameWorld(world, this.worldName);
}
public @Nullable World getWorldIfLoaded() {
return Bukkit.getWorld(worldName);
}
public void setWorld(@NotNull World world) {
Preconditions.notNull(world, "world");
this.world = world;
this.worldName = world.getName();
}
public double getX() {
@ -96,17 +113,22 @@ public class BaseHologramPosition {
}
public @NotNull Location toLocation() {
return new Location(world, x, y, z);
return new Location(getWorldIfLoaded(), x, y, z);
}
@Override
public String toString() {
return "HologramPosition{"
+ "world=" + world
+ "worldName=" + worldName
+ ", x=" + x
+ ", y=" + y
+ ", z=" + z
+ "}";
}
static boolean isSameWorld(World world, String worldName) {
// Use the same comparison used by Bukkit.getWorld(...)
return world.getName().toLowerCase(Locale.ENGLISH).equals(worldName.toLowerCase(Locale.ENGLISH));
}
}

View File

@ -9,7 +9,6 @@ import me.filoghost.holographicdisplays.common.nms.NMSManager;
import me.filoghost.holographicdisplays.common.nms.NMSPacketList;
import me.filoghost.holographicdisplays.plugin.hologram.base.BaseHologramLine;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.MustBeInvokedByOverriders;
@ -17,7 +16,6 @@ abstract class LocationBasedLineTracker<T extends BaseHologramLine> extends Line
private static final int ENTITY_VIEW_RANGE = 64;
private World world;
protected double locationX;
protected double locationY;
protected double locationZ;
@ -30,12 +28,10 @@ abstract class LocationBasedLineTracker<T extends BaseHologramLine> extends Line
@MustBeInvokedByOverriders
@Override
protected void detectChanges() {
World world = line.getWorld();
double locationX = line.getX();
double locationY = line.getY();
double locationZ = line.getZ();
if (this.world != world || this.locationX != locationX || this.locationY != locationY || this.locationZ != locationZ) {
this.world = world;
if (this.locationX != locationX || this.locationY != locationY || this.locationZ != locationZ) {
this.locationX = locationX;
this.locationY = locationY;
this.locationZ = locationZ;
@ -52,11 +48,14 @@ abstract class LocationBasedLineTracker<T extends BaseHologramLine> extends Line
@Override
protected final boolean shouldTrackPlayer(Player player) {
Location playerLocation = player.getLocation();
if (playerLocation.getWorld() != line.getWorldIfLoaded()) {
return false;
}
double diffX = Math.abs(playerLocation.getX() - locationX);
double diffZ = Math.abs(playerLocation.getZ() - locationZ);
return playerLocation.getWorld() == world
&& diffX <= (double) ENTITY_VIEW_RANGE
return diffX <= (double) ENTITY_VIEW_RANGE
&& diffZ <= (double) ENTITY_VIEW_RANGE
&& line.isVisibleTo(player);
}

View File

@ -14,6 +14,8 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import org.bukkit.plugin.Plugin;
public class ChunkListener implements Listener {
@ -28,6 +30,16 @@ public class ChunkListener implements Listener {
this.apiHologramManager = apiHologramManager;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onWorldLoad(WorldUnloadEvent event) {
internalHologramManager.onWorldLoad(event.getWorld());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onWorldLoad(WorldLoadEvent event) {
internalHologramManager.onWorldUnload(event.getWorld());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onChunkUnload(ChunkUnloadEvent event) {
internalHologramManager.onChunkUnload(event.getChunk());

View File

@ -10,12 +10,13 @@ import com.gmail.filoghost.holographicdisplays.api.line.TextLine;
import me.filoghost.holographicdisplays.api.hologram.ClickListener;
import me.filoghost.holographicdisplays.plugin.hologram.api.APIHologram;
import me.filoghost.holographicdisplays.plugin.hologram.api.APIHologramManager;
import me.filoghost.holographicdisplays.plugin.hologram.base.BaseHologramPosition;
import me.filoghost.holographicdisplays.plugin.hologram.api.APITextLine;
import me.filoghost.holographicdisplays.plugin.hologram.base.BaseHologramPosition;
import me.filoghost.holographicdisplays.plugin.test.Mocks;
import me.filoghost.holographicdisplays.plugin.test.TestAPIHologramManager;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
@ -26,10 +27,15 @@ class V2TouchableLineAdapterTest {
APIHologramManager apiHologramManager = new TestAPIHologramManager();
APIHologram hologram = apiHologramManager.createHologram(
new BaseHologramPosition(Mocks.WORLD, 0, 0, 0),
new BaseHologramPosition("world", 0, 0, 0),
Mocks.PLUGIN
);
@BeforeAll
static void beforeAll() {
Mocks.prepareEnvironment();
}
@Test
void setNullV2TouchHandler() {
APITextLine v3Line = hologram.appendTextLine("");

View File

@ -5,20 +5,32 @@
*/
package me.filoghost.holographicdisplays.plugin.test;
import org.bukkit.World;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
import java.util.logging.Logger;
import static org.mockito.Mockito.*;
public class Mocks {
private static final Logger SERVER_LOGGER;
public static final Server SERVER;
public static final Plugin PLUGIN;
public static final World WORLD;
static {
SERVER_LOGGER = mock(Logger.class);
SERVER = mock(Server.class);
when(SERVER.getLogger()).thenReturn(SERVER_LOGGER);
PLUGIN = mock(Plugin.class);
when(PLUGIN.getName()).thenReturn("HolographicDisplays");
WORLD = mock(World.class);
when(WORLD.getName()).thenReturn("world");
}
public static void prepareEnvironment() {
if (Bukkit.getServer() == null) {
Bukkit.setServer(Mocks.SERVER);
}
}
}