Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public HologramLib(Plugin plugin) {
} catch (Exception e) {
// noinspection CallToPrintStackTrace
e.printStackTrace();
throw new IllegalStateException("Failed to load NMS HologramManager for Minecraft version " + mcVersion + ". This version may not be supported.", e);
}

this.hologramManager.withCache(hologramLookupCache);
Expand Down
40 changes: 40 additions & 0 deletions common/src/main/java/com/tcoded/hologramlib/hologram/Hologram.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.tcoded.hologramlib.hologram;

import com.tcoded.hologramlib.tracker.HologramPlayerTracker;
import com.tcoded.hologramlib.types.LocationUpdateHook;
import org.bukkit.Location;
import org.bukkit.entity.Player;

import java.util.List;

public interface Hologram<InternalIdType> {

InternalIdType getInternalId();

Location getLocation();

void setLocation(Location location);

void hookLocationUpdate(LocationUpdateHook hook);

float getTrackingDistance();

void setTrackingDistance(float trackingDistance);

HologramPlayerTracker getTracker();

boolean isVisible();

void show();

void show(List<Player> players);

void hide();

void hide(List<Player> players);

void updateMeta();

void updateLocations();

}
348 changes: 348 additions & 0 deletions common/src/main/java/com/tcoded/hologramlib/hologram/ItemHologram.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
package com.tcoded.hologramlib.hologram;

import com.google.common.collect.ImmutableList;
import com.tcoded.hologramlib.tracker.HologramPlayerTracker;
import com.tcoded.hologramlib.types.LocationUpdateHook;
import com.tcoded.hologramlib.utils.SyncCatcher;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;

import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

public class ItemHologram<InternalIdType> implements Hologram<InternalIdType> {

private final InternalIdType internalId;
private final HologramPlayerTracker tracker;

private ImmutableList<ItemHologramLine> lines;
private ReentrantLock linesLock;

private boolean visible;
private ReentrantLock visibleLock;

private float trackingDistance;

private Location location;
private boolean locationSynced;
private Location lastSyncedLocation;
private LocationUpdateHook locationUpdateHook;

public ItemHologram(InternalIdType internalId) {
this.internalId = internalId;
this.tracker = new HologramPlayerTracker(this);

this.lines = ImmutableList.of();
this.linesLock = new ReentrantLock();

this.visible = false;
this.visibleLock = new ReentrantLock();

this.trackingDistance = 40.0f;

this.location = null;
this.locationSynced = false;
this.lastSyncedLocation = null;
this.locationUpdateHook = null;
}

@Override
public void show() {
SyncCatcher.ensureAsync();

this.visibleLock.lock();
try {
if (this.isVisible()) {
throw new IllegalStateException("Hologram already visible");
}

if (this.location == null) {
throw new IllegalStateException("Hologram location is null");
}

if (!this.locationSynced) {
this.syncLocation();
}

this.setVisible(true);

List<Player> viewers = this.tracker.getAllViewingPlayers();
this.show(viewers);
} finally {
this.visibleLock.unlock();
}
}

@ApiStatus.Internal
public void show(List<Player> players) {
SyncCatcher.ensureAsync();

this.sendSpawnPackets(players);
this.sendMetaPackets(players);
}

@Override
public void hide() {
SyncCatcher.ensureAsync();

this.visibleLock.lock();
try {
if (!this.isVisible()) {
throw new IllegalStateException("Hologram already hidden");
}

this.setVisible(false);

List<Player> viewers = this.tracker.getAllViewingPlayers();
this.hide(viewers);
} finally {
this.visibleLock.unlock();
}
}

@ApiStatus.Internal
public void hide(List<Player> players) {
this.sendKillPackets(players);
}

@Override
public void updateMeta() {
SyncCatcher.ensureAsync();

List<Player> viewers = this.tracker.getAllViewingPlayers();
sendMetaPackets(viewers);
}

@Override
public InternalIdType getInternalId() {
return this.internalId;
}

@Override
public Location getLocation() {
return location.clone();
}

@Override
public void setLocation(Location location) {
boolean holoVisible;

this.visibleLock.lock();
try {
holoVisible = this.isVisible();
} finally {
this.visibleLock.unlock();
}

if (holoVisible) SyncCatcher.ensureAsync();

this.location = location.clone();

if (holoVisible && this.syncLocation()) {
this.sendTeleportPackets(this.tracker.getAllViewingPlayers());
}
}

@Override
public void hookLocationUpdate(LocationUpdateHook hook) {
this.locationUpdateHook = hook;
}

@Override
public float getTrackingDistance() {
return trackingDistance;
}

@Override
public void setTrackingDistance(float trackingDistance) {
this.trackingDistance = trackingDistance;
}

@Override
public HologramPlayerTracker getTracker() {
return this.tracker;
}

@Override
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isVisible() {
this.visibleLock.lock();
try {
return this.visible;
} finally {
this.visibleLock.unlock();
}
}

private void setVisible(boolean state) {
this.visibleLock.lock();
try {
this.visible = state;
} finally {
this.visibleLock.unlock();
}
}

public List<ItemHologramLine> getLines() {
try {
this.linesLock.lock();
return this.lines;
} finally {
this.linesLock.unlock();
}
}

public ItemHologramLine getLine(int index) {
try {
this.linesLock.lock();

if (index < 0 || index >= this.lines.size()) {
throw new IndexOutOfBoundsException("Index out of bounds");
}

return this.lines.get(index);
} finally {
this.linesLock.unlock();
}
}

public void addLine(ItemHologramLine line) {
boolean holoVisible;

this.visibleLock.lock();
try {
holoVisible = this.isVisible();
} finally {
this.visibleLock.unlock();
}

if (holoVisible) SyncCatcher.ensureAsync();

ImmutableList.Builder<ItemHologramLine> linesBuilder = ImmutableList.builder();
linesBuilder.addAll(this.lines);
linesBuilder.add(line);

try {
this.linesLock.lock();

this.lines = linesBuilder.build();

if (holoVisible) {
this.syncLineLocations();
this.sendTeleportPackets(this.tracker.getAllViewingPlayers());
}
} finally {
this.linesLock.unlock();
}
}

public void removeLine(int index) {
boolean holoVisible;

this.visibleLock.lock();
try {
holoVisible = this.isVisible();
} finally {
this.visibleLock.unlock();
}

if (holoVisible) SyncCatcher.ensureAsync();

try {
this.linesLock.lock();

if (index < 0 || index >= this.lines.size()) {
throw new IndexOutOfBoundsException("Index out of bounds");
}

ImmutableList.Builder<ItemHologramLine> linesBuilder = ImmutableList.builder();
for (int i = 0; i < this.lines.size(); i++) {
if (i != index) {
linesBuilder.add(this.lines.get(i));
}
}

this.lines = linesBuilder.build();

if (holoVisible) {
this.sendTeleportPackets(this.tracker.getAllViewingPlayers());
this.syncLineLocations();
}
} finally {
this.linesLock.unlock();
}

if (holoVisible) this.sendTeleportPackets(this.tracker.getAllViewingPlayers());
}

public void updateLocations() {
this.syncLineLocations();
}

private boolean syncLocation() {
if (this.location == null) {
throw new IllegalStateException("Hologram location is null");
}

Location from = this.lastSyncedLocation;
Location to = this.location;

boolean updated = false;
if (!Objects.equals(from, to)) {
this.syncLineLocations();
if (this.locationUpdateHook != null) {
this.locationUpdateHook.handle(this, from, to);
}
updated = true;
}

this.lastSyncedLocation = this.location;
this.locationSynced = true;
return updated;
}

private void syncLineLocations() {
try {
this.linesLock.lock();

ImmutableList<ItemHologramLine> linesRef = this.lines;

Location baseLocation = this.location.clone();
for (int i = linesRef.size() - 1; i >= 0; i--) {
ItemHologramLine line = linesRef.get(i);
Location itemLocation = baseLocation.clone();
double heightOffset = line.getHeight().orElse(0.0);
itemLocation.add(0, heightOffset, 0);
line.setLocation(itemLocation);
}
} finally {
this.linesLock.unlock();
}
}

private void sendSpawnPackets(List<Player> players) {
for (ItemHologramLine line : this.lines) {
line.sendSpawnPacket(players);
}
}

private void sendMetaPackets(List<Player> players) {
for (ItemHologramLine line : this.lines) {
line.sendMetaPacket(players);
}
}

private void sendTeleportPackets(List<Player> players) {
for (ItemHologramLine line : this.lines) {
line.sendTeleportPackets(players);
}
}

private void sendKillPackets(List<Player> players) {
for (ItemHologramLine line : this.lines) {
line.sendKillPacket(players);
}
}

}
Loading