Skip to content

Commit

Permalink
Implement Hologram
Browse files Browse the repository at this point in the history
  • Loading branch information
iamnoksio authored Aug 15, 2024
1 parent 421ee61 commit f03f19f
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 0 deletions.
17 changes: 17 additions & 0 deletions nPaper-API/src/main/java/io/noks/Hologram.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
public interface Hologram {
void delete();

String getMessage();

void setMessage(String paramString);

Location getLocation();

void setMessageHandler(MessageHandler paramMessageHandler);

Hologram addLineBelow(String text);

public static interface MessageHandler {
String getMessage(Player param1Player, String param1String);
}
}
7 changes: 7 additions & 0 deletions nPaper-API/src/main/java/org/bukkit/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -904,4 +904,11 @@ public double[] getTPS()
}

Spigot spigot();

/**
* Spawn a new hologram.
*
* @return Created Hologram
*/
public Hologram newHologram(Location location, String text);
}
237 changes: 237 additions & 0 deletions nPaper-Server/src/main/java/io/noks/cb/CraftHologram.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
package io.noks.cb;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;

import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.entity.Player;

import com.avaje.ebean.validation.NotNull;

import io.noks.Hologram;
import net.minecraft.server.Chunk;
import net.minecraft.server.DataWatcher;
import net.minecraft.server.Entity;
import net.minecraft.server.EntityPlayer;
import net.minecraft.server.MathHelper;
import net.minecraft.server.PacketPlayOutAttachEntity;
import net.minecraft.server.PacketPlayOutEntityDestroy;
import net.minecraft.server.PacketPlayOutEntityTeleport;
import net.minecraft.server.PacketPlayOutSpawnEntity;
import net.minecraft.server.PacketPlayOutSpawnEntityLiving;
import net.minecraft.server.WorldServer;

public class CraftHologram implements Hologram {
private static final List<CraftHologram> holograms = new ArrayList<CraftHologram>();
private String text;
private Location location;
private final int entityId;
private @NotNull final UUID uuid;
private Set<EntityPlayer> viewers = Collections.newSetFromMap(new WeakHashMap<>());
private final int viewDistance = 32;
private Hologram.MessageHandler messageHandler;
private int playerSearchTick = 0;

public CraftHologram(Location location, String text) {
this.text = text;
this.location = location;
this.entityId = Entity.entityCount;
Entity.entityCount += 2;
this.uuid = UUID.randomUUID();
holograms.add(this);
}

public static void tickAll() {
final Iterator<CraftHologram> iter = holograms.iterator();
while (iter.hasNext()) {
CraftHologram hologram = iter.next();
if (hologram.location == null) {
iter.remove();
continue;
}
hologram.tick();
}
}

private void tick() {
if (--this.playerSearchTick < 0) {
this.playerSearchTick = 10;
final double viewDistanceSq = this.viewDistance * this.viewDistance;
final Iterator<EntityPlayer> iter = this.viewers.iterator();
while (iter.hasNext()) {
EntityPlayer player = iter.next();
if (shouldRemovePlayer(player, this.location, viewDistanceSq)) {
iter.remove();
sendDestroyPacket(player);
}
}
WorldServer world = ((CraftWorld) this.location.getWorld()).getHandle();
int chunkX = this.location.getBlockX() >> 4;
int chunkZ = this.location.getBlockZ() >> 4;
for (int x = chunkX - 1; x <= chunkX + 1; x++) {
for (int z = chunkZ - 1; z <= chunkZ + 1; z++) {
Chunk chunk = world.getChunkIfLoaded(x, z);
if (chunk == null) continue;

for (EntityPlayer player : chunk.playersInChunk) {
if (!this.viewers.contains(player) && shouldAddPlayer(player, this.location, viewDistanceSq)) {
this.viewers.add(player);
sendSpawnPackets(player);
}
}
}
}
}
}

private boolean shouldRemovePlayer(EntityPlayer player, Location hologramLocation, double viewDistanceSq) {
return player.dead || player.world.getWorld() != hologramLocation.getWorld() || player.getBukkitEntity().getLocation().distanceSquared(hologramLocation) > viewDistanceSq;
}

private boolean shouldAddPlayer(EntityPlayer player, Location hologramLocation, double viewDistanceSq) {
return player.getBukkitEntity().getLocation().distanceSquared(hologramLocation) < viewDistanceSq;
}

public void delete() {
this.location = null;
this.text = null;
for (EntityPlayer player : this.viewers) {
sendDestroyPacket(player);
}
this.viewers = null;
}

public String getMessage() {
return this.text;
}

public void setMessage(String msg) {
this.text = msg;
for (EntityPlayer player : this.viewers) {
sendMessagePacket(player);
}
}

public Location getLocation() {
return this.location;
}

public void setMessageHandler(Hologram.MessageHandler messageHandler) {
this.messageHandler = messageHandler;
}

private void sendSpawnPackets(EntityPlayer player) {
if (player.playerConnection.networkManager.getVersion() < 47) {
final PacketPlayOutSpawnEntity skullPacket = new PacketPlayOutSpawnEntity();
skullPacket.a = this.entityId + 1;
skullPacket.b = MathHelper.floor(this.location.getX() * 32.0D);
skullPacket.c = MathHelper.floor((this.location.getY() + 54.55D) * 32.0D);
skullPacket.d = MathHelper.floor(this.location.getZ() * 32.0D);
skullPacket.j = 64;
final PacketPlayOutAttachEntity attachEntityPacket = new PacketPlayOutAttachEntity();
attachEntityPacket.leash = 0;
attachEntityPacket.entityId = this.entityId;
attachEntityPacket.vehicleEntityId = this.entityId + 1;
final PacketPlayOutSpawnEntityLiving horsePacket = new PacketPlayOutSpawnEntityLiving();
horsePacket.a = this.entityId;
horsePacket.b = 100;
horsePacket.c = MathHelper.floor(location.getX() * 32.0D);
horsePacket.d = MathHelper.floor(location.getY() * 32.0D);
horsePacket.e = MathHelper.floor(location.getZ() * 32.0D);
horsePacket.l = new DataWatcher(null);

horsePacket.l.a(10, getMessageForPlayer(player));
horsePacket.l.a(11, (byte) 1);
horsePacket.l.a(12, -1700000);

player.playerConnection.sendPacket(horsePacket);
player.playerConnection.sendPacket(skullPacket);
player.playerConnection.sendPacket(attachEntityPacket);
return;
}
final PacketPlayOutEntityTeleport packetPlayOutEntityTeleport = new PacketPlayOutEntityTeleport();
packetPlayOutEntityTeleport.a = this.entityId;
packetPlayOutEntityTeleport.b = MathHelper.floor(this.location.getX() * 32.0D);
packetPlayOutEntityTeleport.c = MathHelper.floor((this.location.getY() - 0.25) * 32.0D);
packetPlayOutEntityTeleport.d = MathHelper.floor(this.location.getZ() * 32.0D);
packetPlayOutEntityTeleport.onGround = true;
final PacketPlayOutSpawnEntityLiving armorStandPacket = new PacketPlayOutSpawnEntityLiving();
armorStandPacket.a = this.entityId;
armorStandPacket.b = 30;
armorStandPacket.c = packetPlayOutEntityTeleport.b;
armorStandPacket.d = packetPlayOutEntityTeleport.c;
armorStandPacket.e = packetPlayOutEntityTeleport.d;
armorStandPacket.l = new DataWatcher(null);

armorStandPacket.l.a(0, (byte) 0x20);
armorStandPacket.l.a(2, getMessageForPlayer(player));
armorStandPacket.l.a(3, (byte) 1);
armorStandPacket.l.a(10, (byte) 0x16);

player.playerConnection.sendPacket(armorStandPacket);
player.playerConnection.sendPacket(packetPlayOutEntityTeleport);
}

// TODO: ONLY REFRESH WHEN UNTRACKING AND TRACKING BACK WHEN USING THE NON RETRACK METHOD
private void sendMessagePacket(EntityPlayer player) {
this.retrack(player);
/*
final DataWatcher dataWatcher = new DataWatcher(null);
dataWatcher.a((player.playerConnection.networkManager.getVersion() < 47 ? 10 : 2), getMessageForPlayer(player));
final PacketPlayOutEntityMetadata metadataPacket = new PacketPlayOutEntityMetadata();
metadataPacket.a = this.entityId;
metadataPacket.b = dataWatcher.b();
player.playerConnection.sendPacket(metadataPacket);*/

}

/*private void sendMessagePacket(EntityPlayer player) {
PacketPlayOutEntityMetadata metadataPacket = new PacketPlayOutEntityMetadata();
metadataPacket.a = Entity.entityCount;
metadataPacket.b = new DataWatcher(null).b(); // Initialize an empty DataWatcher and get list of WatchableObjects
metadataPacket.b.add(new WatchableObject(10, 0, getMessageForPlayer(player))); // Add custom name for the hologram
metadataPacket.b.add(new WatchableObject(11, 1, (byte) 1)); // Add flag for custom name
player.playerConnection.sendPacket(metadataPacket);
}*/

private void sendDestroyPacket(EntityPlayer player) {
player.playerConnection.sendPacket(new PacketPlayOutEntityDestroy(new int[] { this.entityId, this.entityId + 1 }));
}

private String getMessageForPlayer(EntityPlayer player) {
String message = this.text;
if (message.contains("{player}"))
message = message.replace("{player}", player.getName());
if (message.contains("{displayname}"))
message = message.replace("{displayname}", player.getBukkitEntity().getDisplayName());
if (this.messageHandler != null)
try {
message = this.messageHandler.getMessage((Player) player.getBukkitEntity(), message);
} catch (Exception ex) {
Bukkit.getLogger().severe("Exception in CraftHologram.messageHandler:");
ex.printStackTrace();
}
if (message.length() > 64)
message = message.substring(0, 64);
return message;
}

@Override
public Hologram addLineBelow(String text) {
final Hologram holow = new CraftHologram(location.clone().subtract(0, 0.25, 0), text);
return holow;
}

// TEMP FIX
private void retrack(EntityPlayer player) {
this.sendDestroyPacket(player);
this.sendSpawnPackets(player);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,7 @@ public void v() {
SpigotTimings.tickablesTimer.stopTiming(); // Spigot

this.methodProfiler.b();
CraftHologram.tickAll();
}

private boolean needDayUpdate() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1813,4 +1813,8 @@ public Spigot spigot()
{
return spigot;
}

public Hologram newHologram(Location location, String text) {
return new CraftHologram(location.clone(), text);
}
}

0 comments on commit f03f19f

Please sign in to comment.