diff --git a/patches/api/0021-Entity-Data-Storage.patch b/patches/api/0021-Entity-Data-Storage.patch new file mode 100644 index 0000000..8b37c42 --- /dev/null +++ b/patches/api/0021-Entity-Data-Storage.patch @@ -0,0 +1,273 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Blast-MC +Date: Mon, 15 Jan 2024 16:38:52 -0500 +Subject: [PATCH] Entity Data Storage + + +diff --git a/src/main/java/gg/projecteden/parchment/entity/EntityData.java b/src/main/java/gg/projecteden/parchment/entity/EntityData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c4b2cd54c6c0d595e67b0fa0f05a32afa40328b3 +--- /dev/null ++++ b/src/main/java/gg/projecteden/parchment/entity/EntityData.java +@@ -0,0 +1,140 @@ ++package gg.projecteden.parchment.entity; ++ ++import org.bukkit.entity.Entity; ++ ++import java.util.*; ++import java.util.function.Supplier; ++ ++public final class EntityData { ++ private static final Map> PARKED = new HashMap<>(); ++ private static int DATA_IDX; ++ ++ private final List> data = new ArrayList<>(); ++ private final Class ownerType; ++ private Entity owner; ++ ++ EntityData(Class ownerType) { ++ this.ownerType = ownerType; ++ } ++ ++ public static , E extends Entity> EntityDataKey createKey( ++ Supplier generator, ++ Class ownerType ++ ) { ++ return new EntityDataKey<>(ownerType, generator, EntityData.DATA_IDX++); ++ } ++ ++ public static EntityData create(Entity entity) { ++ EntityData data = new EntityData(entity.getClass()); ++ ++ List slots = EntityData.PARKED.get(entity.getUniqueId()); ++ if (slots != null) { ++ for (DataSlot slot : slots) { ++ data.set(slot.idx, slot.data); ++ } ++ } ++ ++ data.setOwner(entity); ++ ++ return data; ++ } ++ ++ public , E extends Entity> T get(EntityDataKey key) { ++ while (this.data.size() <= key.getIdx()) { ++ this.data.add(null); ++ } ++ ++ T out = cast(this.data.get(key.getIdx())); ++ if (out == null) { ++ this.checkEntityType(key.ownerType); ++ ++ out = key.getGenerator().get(); ++ out.setOwner(cast(this.owner)); ++ ++ this.data.set(key.getIdx(), out); ++ } ++ ++ return out; ++ } ++ ++ public , E extends Entity> boolean clear(EntityDataKey key) { ++ if (this.data.size() <= key.getIdx()) { ++ return false; ++ } ++ ++ this.checkEntityType(key.ownerType); ++ return this.data.set(key.getIdx(), null) != null; ++ } ++ ++ public void orphan() { ++ for (EntityDataFragment frag : this.data) { ++ if (frag != null) { ++ frag.onOrphan(); ++ } ++ } ++ ++ List persist = new ArrayList<>(); ++ ++ for (int i = this.data.size() - 1; i >= 0; i--) { ++ EntityDataFragment frag = this.data.get(i); ++ if (frag != null && frag.isPersistent()) { ++ persist.add(new DataSlot(frag, i)); ++ } ++ } ++ ++ if (!persist.isEmpty()) { ++ EntityData.PARKED.put(this.owner.getUniqueId(), persist); ++ } ++ } ++ ++ void set(int slot, EntityDataFragment data) { ++ while (this.data.size() <= slot) { ++ this.data.add(null); ++ } ++ ++ this.data.set(slot, data); ++ } ++ ++ void setOwner(Entity entity) { ++ Class ownerType = entity.getClass(); ++ if (!ownerType.equals(this.ownerType)) { ++ throw new IllegalArgumentException(String.format( ++ "Wrong entity type. (entity=%s@%s, expect=%s@%s)", ++ ownerType, ownerType.getClassLoader(), ++ this.ownerType, this.ownerType.getClassLoader() ++ )); ++ } ++ ++ this.owner = entity; ++ ++ for (EntityDataFragment frag : this.data) { ++ if (frag != null) { ++ frag.setOwner(cast(entity)); ++ } ++ } ++ } ++ ++ private void checkEntityType(Class ownerType) { ++ if (!ownerType.isAssignableFrom(this.ownerType)) { ++ throw new IllegalArgumentException(String.format( ++ "Incompatible entity types. (key=%s@%s, expect=%s@%s)", ++ ownerType, ownerType.getClassLoader(), ++ this.ownerType, this.ownerType.getClassLoader() ++ )); ++ } ++ } ++ ++ private T cast(S src) { ++ return (T) src; ++ } ++ ++ private static final class DataSlot { ++ private final EntityDataFragment data; ++ private final int idx; ++ ++ private DataSlot(EntityDataFragment data, int idx) { ++ this.data = data; ++ this.idx = idx; ++ } ++ } ++} +diff --git a/src/main/java/gg/projecteden/parchment/entity/EntityDataFragment.java b/src/main/java/gg/projecteden/parchment/entity/EntityDataFragment.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c3d43c27a61155036e3f74e781da14ab0bc58d5f +--- /dev/null ++++ b/src/main/java/gg/projecteden/parchment/entity/EntityDataFragment.java +@@ -0,0 +1,34 @@ ++package gg.projecteden.parchment.entity; ++ ++import org.bukkit.entity.Entity; ++ ++public abstract class EntityDataFragment { ++ private E owner; ++ private boolean persistent = true; ++ ++ protected EntityDataFragment() { ++ } ++ ++ protected void onOwnerChange() { ++ } ++ ++ protected void onOrphan() { ++ } ++ ++ protected final E getOwner() { ++ return this.owner; ++ } ++ ++ protected final void setPersistent(boolean persistent) { ++ this.persistent = persistent; ++ } ++ ++ final boolean isPersistent() { ++ return this.persistent; ++ } ++ ++ final void setOwner(E entity) { ++ this.owner = entity; ++ this.onOwnerChange(); ++ } ++} +diff --git a/src/main/java/gg/projecteden/parchment/entity/EntityDataKey.java b/src/main/java/gg/projecteden/parchment/entity/EntityDataKey.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c52c8371b2edf4f62533ffcbc7f8d7b1dbc90777 +--- /dev/null ++++ b/src/main/java/gg/projecteden/parchment/entity/EntityDataKey.java +@@ -0,0 +1,27 @@ ++package gg.projecteden.parchment.entity; ++ ++import org.bukkit.entity.Entity; ++ ++import java.util.function.Supplier; ++ ++public final class EntityDataKey, E extends Entity> { ++ private final Supplier generator; ++ private final int idx; ++ ++ final Class ownerType; ++ ++ EntityDataKey(Class ownerType, Supplier generator, int idx) { ++ this.generator = generator; ++ this.idx = idx; ++ ++ this.ownerType = ownerType; ++ } ++ ++ Supplier getGenerator() { ++ return this.generator; ++ } ++ ++ int getIdx() { ++ return this.idx; ++ } ++} +diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java +index 65d2b0e87feec296b9f20a6de2d2266493cd1e7b..283fa192456de9e643ed6b87ace05135e9cb7713 100644 +--- a/src/main/java/org/bukkit/entity/Entity.java ++++ b/src/main/java/org/bukkit/entity/Entity.java +@@ -1107,4 +1107,7 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent + */ + @NotNull String getScoreboardEntryName(); + // Paper end - entity scoreboard name ++ ++ gg.projecteden.parchment.entity.EntityData getStoredEntityData(); ++ + } +diff --git a/src/main/java/gg/projecteden/parchment/entity/EntityDataServiceKey.java b/src/main/java/gg/projecteden/parchment/entity/EntityDataServiceKey.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c9f9c0d235f33925ee247ba4af56bf9f31bf7056 +--- /dev/null ++++ b/src/main/java/gg/projecteden/parchment/entity/EntityDataServiceKey.java +@@ -0,0 +1,30 @@ ++package gg.projecteden.parchment.entity; ++ ++public final class EntityDataServiceKey { ++ private final Class serviceType; ++ private S service; ++ ++ public EntityDataServiceKey(Class serviceType) { ++ this.serviceType = serviceType; ++ } ++ ++ public S get() { ++ if (this.service == null) { ++ throw new IllegalStateException("Service is not initialized."); ++ } ++ ++ return this.service; ++ } ++ ++ public void set(S service) { ++ if (this.service != null) { ++ throw new IllegalStateException("Service is already initialized."); ++ } ++ ++ if (!this.serviceType.isInstance(service)) { ++ throw new IllegalArgumentException("Value does not implement service contract."); ++ } ++ ++ this.service = service; ++ } ++} diff --git a/patches/api/0022-Add-Sidebar-Utility.patch b/patches/api/0022-Add-Sidebar-Utility.patch new file mode 100644 index 0000000..208c17a --- /dev/null +++ b/patches/api/0022-Add-Sidebar-Utility.patch @@ -0,0 +1,357 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Blast-MC +Date: Mon, 15 Jan 2024 20:53:14 -0500 +Subject: [PATCH] Add Sidebar Utility + + +diff --git a/src/main/java/gg/projecteden/parchment/sidebar/Sidebar.java b/src/main/java/gg/projecteden/parchment/sidebar/Sidebar.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f3f7a138d8d0a0e5f863f50fa39ffd861a8a1db9 +--- /dev/null ++++ b/src/main/java/gg/projecteden/parchment/sidebar/Sidebar.java +@@ -0,0 +1,124 @@ ++package gg.projecteden.parchment.sidebar; ++ ++import gg.projecteden.parchment.entity.EntityData; ++import gg.projecteden.parchment.entity.EntityDataFragment; ++import gg.projecteden.parchment.entity.EntityDataKey; ++import org.bukkit.entity.Player; ++ ++import java.util.Objects; ++ ++public final class Sidebar extends EntityDataFragment { ++ private static final EntityDataKey DATA_KEY = EntityData.createKey(Sidebar::new, Player.class); ++ ++ private final SidebarBuffer[] buffer = new SidebarBuffer[2]; ++ ++ private final StageImpl stage = new StageImpl(); ++ private final Runnable layoutListener; ++ ++ private SidebarLayout layout; ++ ++ private boolean visible; ++ private int back; ++ ++ public static Sidebar get(Player player) { ++ Objects.requireNonNull(player); ++ ++ return player.getStoredEntityData().get(Sidebar.DATA_KEY); ++ } ++ ++ private Sidebar() { ++ this.buffer[0] = SidebarBufferUtilSpec.IMPL_KEY.get().create("_sidebar_l"); ++ this.buffer[1] = SidebarBufferUtilSpec.IMPL_KEY.get().create("_sidebar_r"); ++ ++ this.layoutListener = () -> { ++ this.layout.update(this.stage); ++ this.flush(); ++ }; ++ ++ this.setPersistent(false); ++ } ++ ++ public void applyLayout(SidebarLayout layout) { ++ if (this.layout != null) { ++ this.layout.unsubscribe(this.layoutListener); ++ this.buffer[this.back].clear(); ++ } ++ ++ this.layout = layout; ++ ++ if (layout == null) { ++ this.hide(); ++ } else { ++ layout.setup(this.stage); ++ this.flush(); ++ ++ layout.subscribe(this.layoutListener); ++ } ++ } ++ ++ private void setTitle(String title) { ++ this.buffer[this.back].setTitle(title); ++ } ++ ++ private void setLine(int idx, String value, String display) { ++ if (value == null) { ++ this.buffer[this.back].clearLine(idx); ++ } else { ++ this.buffer[this.back].setLine(idx, value, display); ++ this.buffer[this.back ^ 1].stagedDisplays[idx] = display; ++ } ++ } ++ ++ private void flush() { ++ SidebarBuffer front = this.buffer[this.back]; ++ SidebarBuffer back = this.buffer[this.back ^ 1]; ++ boolean shouldShow = !this.visible; ++ ++ if (front.hasDiverged(back)) { ++ front.pushChanges(); ++ ++ back.sync(front); ++ this.back ^= 1; ++ ++ shouldShow = true; ++ } ++ ++ if (shouldShow) { ++ front.setActive(); ++ this.visible = true; ++ } ++ } ++ ++ private void hide() { ++ SidebarBufferUtilSpec.IMPL_KEY.get().hideSidebar(this.getOwner()); ++ this.visible = false; ++ } ++ ++ @Override ++ protected void onOwnerChange() { ++ this.buffer[0].setOwner(this.getOwner()); ++ this.buffer[1].setOwner(this.getOwner()); ++ ++ if (this.visible) { ++ this.buffer[this.back ^ 1].setActive(); ++ } ++ } ++ ++ private final class StageImpl implements SidebarStage { ++ @Override ++ public void setTitle(String title) { ++ Sidebar.this.setTitle(title); ++ } ++ ++ @Override ++ public void setLine(int line, String value) { ++ this.setLine(line, value, null); ++ } ++ ++ @Override ++ public void setLine(int line, String value, String display) { ++ Sidebar.this.setLine(line, value, display); ++ } ++ ++ } ++} +diff --git a/src/main/java/gg/projecteden/parchment/sidebar/SidebarBuffer.java b/src/main/java/gg/projecteden/parchment/sidebar/SidebarBuffer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c4a58a2b4e8bf5d3130b7da71616f06b6ed98e8f +--- /dev/null ++++ b/src/main/java/gg/projecteden/parchment/sidebar/SidebarBuffer.java +@@ -0,0 +1,95 @@ ++package gg.projecteden.parchment.sidebar; ++ ++import org.bukkit.entity.Player; ++ ++import java.util.Arrays; ++import java.util.Objects; ++ ++public abstract class SidebarBuffer { ++ ++ @SuppressWarnings("StringOperationCanBeSimplified") ++ private static final String AUTO_SPACE = new String(); ++ protected final String name; ++ protected final int size; ++ protected final String[] liveLines; ++ protected final String[] stagedLines; ++ protected final String[] stagedDisplays; ++ ++ protected String liveTitle = ""; ++ protected String stagedTitle = ""; ++ ++ protected SidebarBuffer(String name, int size) { ++ this.name = name; ++ this.size = size; ++ this.liveLines = new String[size]; ++ this.stagedLines = new String[size]; ++ this.stagedDisplays = new String[size]; ++ } ++ ++ protected abstract void setActive(); ++ ++ protected abstract void pushChanges(); ++ ++ protected abstract void setOwner(Player player); ++ ++ protected abstract boolean checkTitle(String title); ++ ++ protected abstract boolean checkLine(String line); ++ ++ void setTitle(String title) { ++ this.stagedTitle = Objects.requireNonNullElse(title, ""); ++ } ++ ++ void setLine(int line, String value, String display) { ++ Objects.requireNonNull(value); ++ if (line < 0 || line > this.size - 1) { ++ throw new IndexOutOfBoundsException(); ++ } ++ ++ this.stagedLines[line] = value; ++ this.stagedDisplays[line] = display; ++ ++ for (int i = line - 1; i >= 0; i--) { ++ if (this.stagedLines[i] == null) { ++ this.stagedLines[i] = SidebarBuffer.AUTO_SPACE; ++ } else { ++ break; ++ } ++ } ++ } ++ ++ void clearLine(int line) { ++ if (line < 0 || line > this.size - 1) { ++ throw new IndexOutOfBoundsException(); ++ } ++ ++ this.stagedLines[line] = SidebarBuffer.AUTO_SPACE; ++ ++ if (line + 1 == this.size || this.stagedLines[line + 1] == null) { ++ for (int i = line; i >= 0; i--) { ++ if (this.stagedLines[i] == SidebarBuffer.AUTO_SPACE) { ++ this.stagedLines[i] = null; ++ } else { ++ break; ++ } ++ } ++ } ++ } ++ ++ void sync(SidebarBuffer live) { ++ this.stagedTitle = live.liveTitle; ++ System.arraycopy(live.liveLines, 0, this.stagedLines, 0, this.size); ++ } ++ ++ void clear() { ++ this.stagedTitle = ""; ++ Arrays.fill(this.stagedLines, null); ++ } ++ ++ boolean hasDiverged(SidebarBuffer live) { ++ boolean out = !Objects.equals(this.stagedTitle, live.liveTitle); ++ out = out || !Arrays.equals(this.stagedLines, live.liveLines); ++ ++ return out; ++ } ++} +diff --git a/src/main/java/gg/projecteden/parchment/sidebar/SidebarBufferUtilSpec.java b/src/main/java/gg/projecteden/parchment/sidebar/SidebarBufferUtilSpec.java +new file mode 100644 +index 0000000000000000000000000000000000000000..22239d7ea5d632f306caba0d139fe1576e85a7dc +--- /dev/null ++++ b/src/main/java/gg/projecteden/parchment/sidebar/SidebarBufferUtilSpec.java +@@ -0,0 +1,12 @@ ++package gg.projecteden.parchment.sidebar; ++ ++import gg.projecteden.parchment.entity.EntityDataServiceKey; ++import org.bukkit.entity.Player; ++ ++public interface SidebarBufferUtilSpec { ++ EntityDataServiceKey IMPL_KEY = new EntityDataServiceKey<>(SidebarBufferUtilSpec.class); ++ ++ SidebarBuffer create(String bufferName); ++ ++ void hideSidebar(Player player); ++} +diff --git a/src/main/java/gg/projecteden/parchment/sidebar/SidebarLayout.java b/src/main/java/gg/projecteden/parchment/sidebar/SidebarLayout.java +new file mode 100644 +index 0000000000000000000000000000000000000000..238d2c1338aee95b24fd31c9643e0f966fe0b79f +--- /dev/null ++++ b/src/main/java/gg/projecteden/parchment/sidebar/SidebarLayout.java +@@ -0,0 +1,58 @@ ++package gg.projecteden.parchment.sidebar; ++ ++import java.util.HashSet; ++import java.util.Set; ++ ++/** ++ * A sidebar layout. Subclasses can describe custom layouts by using the ++ * {@link #setup(SidebarStage)} and {@link #update(SidebarStage)} hooks. ++ */ ++public abstract class SidebarLayout { ++ private final Set subscribers = new HashSet<>(); ++ ++ /** ++ * Pushes an update to all subscribers. ++ */ ++ public final void refresh() { ++ synchronized (this.subscribers) { ++ for (Runnable s : this.subscribers) { ++ s.run(); ++ } ++ } ++ } ++ ++ /** ++ * Runs when the layout is first applied. ++ * ++ *

The provided sidebar stage may be used to create the first ++ * frame of sidebar data, which will be pushed to the client upon ++ * exit of this method. ++ * ++ * @param stage The sidebar stage. Using it outside this method will ++ * result in undefined behavior. ++ */ ++ protected void setup(SidebarStage stage) { ++ } ++ ++ /** ++ * Runs when a refresh is requested. ++ * ++ *

The provided sidebar stage may be used to update the existing ++ * sidebar data, which will be pushed to the client upon exit of ++ * this method. Sidebar data from previous hook calls will stay the ++ * same unless explicitly changed here. ++ * ++ * @param stage The sidebar stage. Using it outside this method will ++ * result in undefined behavior. ++ */ ++ protected void update(SidebarStage stage) { ++ } ++ ++ final void subscribe(Runnable listener) { ++ this.subscribers.add(listener); ++ } ++ ++ final void unsubscribe(Runnable listener) { ++ this.subscribers.remove(listener); ++ } ++} +diff --git a/src/main/java/gg/projecteden/parchment/sidebar/SidebarStage.java b/src/main/java/gg/projecteden/parchment/sidebar/SidebarStage.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9e604d3b8183abe342ef5c055069f1c4b16df55c +--- /dev/null ++++ b/src/main/java/gg/projecteden/parchment/sidebar/SidebarStage.java +@@ -0,0 +1,32 @@ ++package gg.projecteden.parchment.sidebar; ++ ++/** ++ * An abstracted sidebar stage. ++ */ ++public interface SidebarStage { ++ /** ++ * Stages a new title. ++ * ++ * @param title The new title. ++ */ ++ void setTitle(String title); ++ ++ /** ++ * Stages a new line at the provided index. ++ * ++ * @param line The line index. ++ * @param value The new line. ++ */ ++ void setLine(int line, String value); ++ ++ /** ++ * Stages a new line at the provided index ++ * Uses the display as the right aligned text ++ * ++ * @param line The line index ++ * @param value The new line ++ * @param display The right aligned text ++ */ ++ void setLine(int line, String value, String display); ++ ++} diff --git a/patches/server/0027-Entity-Data-Storage.patch b/patches/server/0027-Entity-Data-Storage.patch new file mode 100644 index 0000000..7dd0f10 --- /dev/null +++ b/patches/server/0027-Entity-Data-Storage.patch @@ -0,0 +1,92 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Blast-MC +Date: Mon, 15 Jan 2024 16:39:10 -0500 +Subject: [PATCH] Entity Data Storage + + +diff --git a/src/main/java/gg/projecteden/parchment/entity/EntityDataServices.java b/src/main/java/gg/projecteden/parchment/entity/EntityDataServices.java +new file mode 100644 +index 0000000000000000000000000000000000000000..30f8fd154136d05267e8737ff04a0be45b23a35d +--- /dev/null ++++ b/src/main/java/gg/projecteden/parchment/entity/EntityDataServices.java +@@ -0,0 +1,16 @@ ++package gg.projecteden.parchment.entity; ++ ++public class EntityDataServices { ++ ++ private static boolean initialized; ++ ++ public static void init() { ++ if (initialized) { ++ throw new RuntimeException("EntityData Services already initialized"); ++ } ++ initialized = true; ++ ++ // Initialize Services Here ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 58536aabf607015939a1326f80207c0a06eed8ff..b27e958e8da8abbf0e36b432bf3a29da270ecb8e 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -268,6 +268,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + return false; + } + ++ gg.projecteden.parchment.entity.EntityDataServices.init(); ++ + // CraftBukkit start + // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // Spigot - moved up + this.server.loadPlugins(); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 9abe817ae202edaa2d88cd59ae5c7db0b1c634be..861dff20a09d94cb90e1ff95ea51f50efa7b501d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -158,6 +158,31 @@ import org.bukkit.plugin.PluginManager; + + public abstract class Entity implements Nameable, EntityAccess, CommandSource, ScoreHolder { + ++ @javax.annotation.Nullable ++ private gg.projecteden.parchment.entity.EntityData storedEntityData; ++ ++ /** ++ * Retrieves the stored EntityData for this entity ++ * @return The currently stored EntityData ++ */ ++ public gg.projecteden.parchment.entity.EntityData getStoredEntityData() { ++ if (this.storedEntityData == null) { ++ this.storedEntityData = gg.projecteden.parchment.entity.EntityData.create(this.getBukkitEntity()); ++ } ++ return this.storedEntityData; ++ } ++ ++ /** ++ * Clears the currently stored EntityData for this entity ++ * @return the previously stored EntityData ++ */ ++ public @javax.annotation.Nullable gg.projecteden.parchment.entity.EntityData clearStoredEntityData() { ++ gg.projecteden.parchment.entity.EntityData data = this.storedEntityData; ++ this.storedEntityData = null; ++ ++ return data; ++ } ++ + // CraftBukkit start + private static final int CURRENT_LEVEL = 2; + public boolean preserveMotion = true; // Paper - keep initial motion on first setPositionRotation +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index aa4dbf91cf6da329fdcacbde98bb870eb48e8f5c..392d5b896254e5dd57de33e16fa196950e6f7c61 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -222,6 +222,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + }; + // Paper end - Folia schedulers + ++ public gg.projecteden.parchment.entity.EntityData getStoredEntityData() { ++ return this.entity.getStoredEntityData(); ++ } ++ + public CraftEntity(final CraftServer server, final Entity entity) { + this.server = server; + this.entity = entity; diff --git a/patches/server/0028-Add-Sidebar-Utility.patch b/patches/server/0028-Add-Sidebar-Utility.patch new file mode 100644 index 0000000..ed95ebc --- /dev/null +++ b/patches/server/0028-Add-Sidebar-Utility.patch @@ -0,0 +1,272 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Blast-MC +Date: Mon, 15 Jan 2024 20:53:54 -0500 +Subject: [PATCH] Add Sidebar Utility + + +diff --git a/src/main/java/gg/projecteden/parchment/entity/EntityDataServices.java b/src/main/java/gg/projecteden/parchment/entity/EntityDataServices.java +index 30f8fd154136d05267e8737ff04a0be45b23a35d..7091340870607605521239893b8ab763f49d7999 100644 +--- a/src/main/java/gg/projecteden/parchment/entity/EntityDataServices.java ++++ b/src/main/java/gg/projecteden/parchment/entity/EntityDataServices.java +@@ -1,5 +1,8 @@ + package gg.projecteden.parchment.entity; + ++import gg.projecteden.parchment.sidebar.SidebarBufferUtil; ++import gg.projecteden.parchment.sidebar.SidebarBufferUtilSpec; ++ + public class EntityDataServices { + + private static boolean initialized; +@@ -11,6 +14,7 @@ public class EntityDataServices { + initialized = true; + + // Initialize Services Here ++ SidebarBufferUtilSpec.IMPL_KEY.set(new SidebarBufferUtil()); + } + + } +diff --git a/src/main/java/gg/projecteden/parchment/sidebar/SidebarBufferImpl.java b/src/main/java/gg/projecteden/parchment/sidebar/SidebarBufferImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5a1e28da1f3dcae1abeb016708dc6fdf883cd213 +--- /dev/null ++++ b/src/main/java/gg/projecteden/parchment/sidebar/SidebarBufferImpl.java +@@ -0,0 +1,178 @@ ++package gg.projecteden.parchment.sidebar; ++ ++import gg.projecteden.parchment.util.StringUtils; ++import net.minecraft.network.Connection; ++import net.minecraft.network.chat.Component; ++import net.minecraft.network.chat.numbers.BlankFormat; ++import net.minecraft.network.chat.numbers.FixedFormat; ++import net.minecraft.network.chat.numbers.NumberFormat; ++import net.minecraft.network.protocol.game.*; ++import net.minecraft.server.ServerScoreboard; ++import net.minecraft.server.dedicated.DedicatedServer; ++import net.minecraft.world.scores.DisplaySlot; ++import net.minecraft.world.scores.Objective; ++import net.minecraft.world.scores.PlayerTeam; ++import net.minecraft.world.scores.Team; ++import net.minecraft.world.scores.criteria.ObjectiveCriteria; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.entity.Player; ++ ++import java.util.Objects; ++ ++public class SidebarBufferImpl extends SidebarBuffer { ++ ++ private static final int NAME_LIMIT = 38; ++ private static final int AFFIX_LIMIT = 16; ++ private static final int SIZE = 15; ++ ++ static final int SIDEBAR_SLOT = 1; ++ private final Objective objective = new Objective(DedicatedServer.getServer().getScoreboard(), this.name, ++ ObjectiveCriteria.DUMMY, Component.literal(this.stagedTitle), ObjectiveCriteria.RenderType.INTEGER, ++ false, null); ++ ++ private Connection connection; ++ ++ SidebarBufferImpl(String name) { ++ super(name, SidebarBufferImpl.SIZE); ++ } ++ ++ protected void setActive() { ++ objective.setDisplayName(Component.literal(StringUtils.colorize(this.stagedTitle))); ++ ClientboundSetObjectivePacket packet = new ClientboundSetObjectivePacket(this.objective, ClientboundSetObjectivePacket.METHOD_CHANGE); ++ ClientboundSetDisplayObjectivePacket packet2 = new ClientboundSetDisplayObjectivePacket(DisplaySlot.SIDEBAR, this.objective); ++ ++ this.connection.send(packet); ++ this.connection.send(packet2); ++ } ++ ++ protected void pushChanges() { ++ if (!Objects.equals(this.liveTitle, this.stagedTitle)) { ++ this.liveTitle = this.stagedTitle; ++ ++ ClientboundSetObjectivePacket packet = new ClientboundSetObjectivePacket(this.objective, ClientboundSetObjectivePacket.METHOD_CHANGE); ++ ++ this.connection.send(packet); ++ } ++ ++ int liveEnd = this.size; ++ for (int i = 0; i < this.size; i++) { ++ if (this.liveLines[i] == null) { ++ liveEnd = i; ++ break; ++ } ++ } ++ ++ int stagedEnd = this.size; ++ for (int i = 0; i < this.size; i++) { ++ if (this.stagedLines[i] == null) { ++ stagedEnd = i; ++ break; ++ } ++ } ++ ++ int maxEnd = Math.max(liveEnd, stagedEnd); ++ int liveIdx = liveEnd - maxEnd; ++ int stagedIdx = stagedEnd - maxEnd; ++ ++ for (int i = 0; i < this.size; i++) { ++ String live = liveIdx >= 0 ? this.liveLines[liveIdx] : null; ++ String staged = stagedIdx >= 0 ? this.stagedLines[stagedIdx] : null; ++ ++ if (!Objects.equals(live, staged)) { ++ if (live != null) { ++ this.sendDelete(live, liveEnd - liveIdx); ++ } ++ ++ if (staged != null) { ++ this.sendCreate(staged, stagedEnd - stagedIdx, this.stagedDisplays[stagedIdx]); ++ } ++ } ++ ++ liveIdx++; ++ stagedIdx++; ++ } ++ ++ System.arraycopy(this.stagedLines, 0, this.liveLines, 0, this.size); ++ } ++ ++ @Override ++ protected void setOwner(Player player) { ++ this.connection = ((CraftPlayer) player).getHandle().connection.connection; ++ ++ ClientboundSetObjectivePacket packet = new ClientboundSetObjectivePacket(this.objective, ClientboundSetObjectivePacket.METHOD_ADD); ++ this.connection.send(packet); ++ ++ for (int i = 0; i < this.size; i++) { ++ String live = this.liveLines[i]; ++ ++ if (live != null) { ++ this.sendCreate(live, i, stagedDisplays[i]); ++ } ++ } ++ } ++ ++ @Override ++ protected boolean checkTitle(String line) { ++ return line.length() <= 32; ++ } ++ ++ @Override ++ protected boolean checkLine(String line) { ++ return line.length() <= SidebarBufferImpl.NAME_LIMIT + SidebarBufferImpl.AFFIX_LIMIT * 2; ++ } ++ ++ private void sendCreate(String value, int line, String display) { ++ String id = "\u00a7" + (char) ('\u0080' + line); ++ value = StringUtils.colorize(value); ++ ++ if (value.length() > SidebarBufferImpl.NAME_LIMIT) { ++ String prefix = value.substring(0, SidebarBufferImpl.AFFIX_LIMIT); ++ String suffix = ""; ++ ++ value = value.substring(SidebarBufferImpl.AFFIX_LIMIT); ++ ++ if (value.length() > SidebarBufferImpl.NAME_LIMIT) { ++ suffix = value.substring(SidebarBufferImpl.NAME_LIMIT); ++ value = value.substring(0, SidebarBufferImpl.NAME_LIMIT); ++ } ++ ++ PlayerTeam team = new PlayerTeam(DedicatedServer.getServer().getScoreboard(), this.getTeamName(line)); ++ team.setPlayerPrefix(Component.literal(prefix)); ++ team.setPlayerSuffix(Component.literal(suffix)); ++ team.setNameTagVisibility(Team.Visibility.NEVER); ++ team.setCollisionRule(Team.CollisionRule.NEVER); ++ team.getPlayers().add(id + value); ++ ClientboundSetPlayerTeamPacket packet = ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true); ++ ++ this.connection.send(packet); ++ } ++ ++ NumberFormat numberFormat = (display == null || display.isEmpty() || display.isBlank()) ? BlankFormat.INSTANCE : new FixedFormat(Component.literal(StringUtils.colorize(display))); ++ ClientboundSetScorePacket packet = new ClientboundSetScorePacket(id + value, this.name, line, null, numberFormat); ++ ++ this.connection.send(packet); ++ } ++ ++ private void sendDelete(String value, int line) { ++ String id = "\u00a7" + (char) ('\u0080' + line); ++ ++ value = StringUtils.colorize(value); ++ ++ if (value.length() > SidebarBufferImpl.NAME_LIMIT) { ++ value = value.substring( ++ SidebarBufferImpl.AFFIX_LIMIT, ++ Math.min(value.length(), SidebarBufferImpl.AFFIX_LIMIT + SidebarBufferImpl.NAME_LIMIT) ++ ); ++ ++ ClientboundSetPlayerTeamPacket packet = ClientboundSetPlayerTeamPacket.createRemovePacket(DedicatedServer.getServer().getScoreboard().getPlayerTeam(this.getTeamName(line))); ++ this.connection.send(packet); ++ } ++ ++ ClientboundResetScorePacket packet = new ClientboundResetScorePacket(id + value, this.name); ++ this.connection.send(packet); ++ } ++ ++ private String getTeamName(int line) { ++ return this.name + "/" + Integer.toUnsignedString(line, 32); ++ } ++} +diff --git a/src/main/java/gg/projecteden/parchment/sidebar/SidebarBufferUtil.java b/src/main/java/gg/projecteden/parchment/sidebar/SidebarBufferUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..599e84adf8e480508b9c2ce87baca07e163d345e +--- /dev/null ++++ b/src/main/java/gg/projecteden/parchment/sidebar/SidebarBufferUtil.java +@@ -0,0 +1,19 @@ ++package gg.projecteden.parchment.sidebar; ++ ++import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket; ++import net.minecraft.world.scores.DisplaySlot; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.entity.Player; ++ ++public class SidebarBufferUtil implements SidebarBufferUtilSpec { ++ @Override ++ public SidebarBuffer create(String name) { ++ return new SidebarBufferImpl(name); ++ } ++ ++ @Override ++ public void hideSidebar(Player player) { ++ ClientboundSetDisplayObjectivePacket packet = new ClientboundSetDisplayObjectivePacket(DisplaySlot.SIDEBAR, null); ++ ((CraftPlayer) player).getHandle().connection.send(packet); ++ } ++} +diff --git a/src/main/java/gg/projecteden/parchment/util/StringUtils.java b/src/main/java/gg/projecteden/parchment/util/StringUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0f8700f43e5503d5b002368fa36d8e6a7577658f +--- /dev/null ++++ b/src/main/java/gg/projecteden/parchment/util/StringUtils.java +@@ -0,0 +1,30 @@ ++package gg.projecteden.parchment.util; ++ ++import net.md_5.bungee.api.ChatColor; ++ ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++ ++public class StringUtils { ++ ++ private static final String colorChar = "ยง"; ++ private static final String altColorChar = "&"; ++ private static final String colorCharsRegex = "[" + colorChar + altColorChar + "]"; ++ private static final Pattern hexPattern = Pattern.compile(colorCharsRegex + "#[a-fA-F\\d]{6}"); ++ ++ public static String colorize(String input) { ++ if (input == null) ++ return null; ++ ++ while (true) { ++ Matcher matcher = hexPattern.matcher(input); ++ if (!matcher.find()) break; ++ ++ String color = matcher.group(); ++ input = input.replace(color, ChatColor.of(color.replaceFirst(colorCharsRegex, "")).toString()); ++ } ++ ++ return ChatColor.translateAlternateColorCodes(altColorChar.charAt(0), input); ++ } ++ ++}