diff --git a/src/main/java/org/bukkit/event/Event.java b/src/main/java/org/bukkit/event/Event.java
index 3d73d80..3c3607d 100644
--- a/src/main/java/org/bukkit/event/Event.java
+++ b/src/main/java/org/bukkit/event/Event.java
@@ -337,6 +337,20 @@ public enum Type {
* @see org.bukkit.event.player.PlayerItemDamageEvent
*/
PLAYER_ITEM_DAMAGE(Category.PLAYER),
+ // Tsunami start - backport plugin messaging
+ /**
+ * Called when a player registers for a plugin channel
+ *
+ * @see org.bukkit.event.player.PlayerRegisterChannelEvent
+ */
+ PLAYER_REGISTER_CHANNEL(Category.PLAYER),
+ /**
+ * Called when a player unregisters for a plugin channel
+ *
+ * @see org.bukkit.event.player.PlayerUnregisterChannelEvent
+ */
+ PLAYER_UNREGISTER_CHANNEL(Category.PLAYER),
+ // Tsunami end
/**
* BLOCK EVENTS
diff --git a/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java b/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java
new file mode 100644
index 0000000..1036e5e
--- /dev/null
+++ b/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java
@@ -0,0 +1,20 @@
+package org.bukkit.event.player;
+
+import org.bukkit.entity.Player;
+
+/**
+ * This event is called after a player registers or unregisters a new plugin
+ * channel.
+ */
+public abstract class PlayerChannelEvent extends PlayerEvent {
+ private final String channel;
+
+ public PlayerChannelEvent(Type type, Player player, String channel) {
+ super(type, player);
+ this.channel = channel;
+ }
+
+ public final String getChannel() {
+ return channel;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/bukkit/event/player/PlayerListener.java b/src/main/java/org/bukkit/event/player/PlayerListener.java
index b7fce6c..ee90895 100644
--- a/src/main/java/org/bukkit/event/player/PlayerListener.java
+++ b/src/main/java/org/bukkit/event/player/PlayerListener.java
@@ -205,4 +205,20 @@ public void onPlayerFish(PlayerFishEvent event) {}
* @param event Relevant event details
*/
public void onPlayerItemDamage(PlayerItemDamageEvent event) {}
+
+ // Tsunami start - backport plugin messaging
+ /**
+ * Called when a player registers for a plugin channel
+ *
+ * @param event Relevant event details
+ */
+ public void onPlayerRegisterChannel(PlayerRegisterChannelEvent event) {}
+
+ /**
+ * Called when a player unregisters for a plugin channel
+ *
+ * @param event Relevant event details
+ */
+ public void onPlayerUnregisterChannel(PlayerUnregisterChannelEvent event) {}
+ // Tsunami end
}
diff --git a/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java b/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java
new file mode 100644
index 0000000..26a8275
--- /dev/null
+++ b/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java
@@ -0,0 +1,13 @@
+package org.bukkit.event.player;
+
+import org.bukkit.entity.Player;
+
+/**
+ * This is called immediately after a player registers for a plugin channel.
+ */
+public class PlayerRegisterChannelEvent extends PlayerChannelEvent {
+
+ public PlayerRegisterChannelEvent(Player player, String channel) {
+ super(Type.PLAYER_REGISTER_CHANNEL, player, channel);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java b/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java
new file mode 100644
index 0000000..5ec9029
--- /dev/null
+++ b/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java
@@ -0,0 +1,13 @@
+package org.bukkit.event.player;
+
+import org.bukkit.entity.Player;
+
+/**
+ * This is called immediately after a player unregisters for a plugin channel.
+ */
+public class PlayerUnregisterChannelEvent extends PlayerChannelEvent {
+
+ public PlayerUnregisterChannelEvent(Player player, String channel) {
+ super(Type.PLAYER_UNREGISTER_CHANNEL, player, channel);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
index 6ff0e70..896bd1c 100644
--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
@@ -620,11 +620,31 @@ public void execute(Listener listener, Event event)
}
};
case PLAYER_CHANGED_WORLD:
- return new EventExecutor() {
- public void execute(Listener listener, Event event) {
+ return new EventExecutor()
+ {
+ public void execute(Listener listener, Event event)
+ {
((PlayerListener) listener).onPlayerChangedWorld((PlayerChangedWorldEvent) event);
}
};
+ // Tsunami start - backport plugin messaging
+ case PLAYER_REGISTER_CHANNEL:
+ return new EventExecutor()
+ {
+ public void execute(Listener listener, Event event)
+ {
+ ((PlayerListener) listener).onPlayerRegisterChannel((PlayerRegisterChannelEvent) event);
+ }
+ };
+ case PLAYER_UNREGISTER_CHANNEL:
+ return new EventExecutor()
+ {
+ public void execute(Listener listener, Event event)
+ {
+ ((PlayerListener) listener).onPlayerUnregisterChannel((PlayerUnregisterChannelEvent) event);
+ }
+ };
+ // Tsunami end
// Block Events
case BLOCK_PHYSICS:
diff --git a/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java b/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java
new file mode 100644
index 0000000..2ee5403
--- /dev/null
+++ b/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java
@@ -0,0 +1,11 @@
+package org.bukkit.plugin.messaging;
+
+/**
+ * Thrown if a Plugin Channel is too long.
+ */
+public class ChannelNameTooLongException extends RuntimeException {
+
+ public ChannelNameTooLongException(String channel) {
+ super("Attempted to send a Plugin Message to a channel that was too large. The maximum length a channel may be is " + Messenger.MAX_CHANNEL_SIZE + " chars (attempted " + channel.length() + " - '" + channel + ".");
+ }
+}
diff --git a/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java b/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java
new file mode 100644
index 0000000..56f2e2a
--- /dev/null
+++ b/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java
@@ -0,0 +1,11 @@
+package org.bukkit.plugin.messaging;
+
+/**
+ * Thrown if a Plugin attempts to send a message on an unregistered channel.
+ */
+public class ChannelNotRegisteredException extends RuntimeException {
+
+ public ChannelNotRegisteredException(String channel) {
+ super("Attempted to send a plugin message through an unregistered channel ('" + channel + "'.");
+ }
+}
diff --git a/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java b/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java
new file mode 100644
index 0000000..93765fa
--- /dev/null
+++ b/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java
@@ -0,0 +1,19 @@
+package org.bukkit.plugin.messaging;
+
+/**
+ * Thrown if a Plugin Message is sent that is too large to be sent.
+ */
+public class MessageTooLargeException extends RuntimeException {
+
+ public MessageTooLargeException(byte[] message) {
+ this(message.length);
+ }
+
+ public MessageTooLargeException(int length) {
+ this("Attempted to send a plugin message that was too large. The maximum length a plugin message may be is " + Messenger.MAX_MESSAGE_SIZE + " bytes (tried to send one that is " + length + " bytes long).");
+ }
+
+ public MessageTooLargeException(String msg) {
+ super(msg);
+ }
+}
diff --git a/src/main/java/org/bukkit/plugin/messaging/Messenger.java b/src/main/java/org/bukkit/plugin/messaging/Messenger.java
new file mode 100644
index 0000000..8d4480b
--- /dev/null
+++ b/src/main/java/org/bukkit/plugin/messaging/Messenger.java
@@ -0,0 +1,201 @@
+package org.bukkit.plugin.messaging;
+
+import java.util.Set;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+
+/**
+ * A class responsible for managing the registrations of plugin channels and their
+ * listeners.
+ */
+public interface Messenger {
+ /**
+ * Represents the largest size that an individual Plugin Message may be.
+ */
+ public static final int MAX_MESSAGE_SIZE = 32766;
+
+ /**
+ * Represents the largest size that a Plugin Channel may be.
+ */
+ public static final int MAX_CHANNEL_SIZE = 64;
+
+ /**
+ * Checks if the specified channel is a reserved name.
+ *
+ * @param channel Channel name to check.
+ * @return True if the channel is reserved, otherwise false.
+ * @throws IllegalArgumentException Thrown if channel is null.
+ */
+ public boolean isReservedChannel(String channel);
+
+ /**
+ * Registers the specific plugin to the requested outgoing plugin channel, allowing it
+ * to send messages through that channel to any clients.
+ *
+ * @param plugin Plugin that wishes to send messages through the channel.
+ * @param channel Channel to register.
+ * @throws IllegalArgumentException Thrown if plugin or channel is null.
+ */
+ public void registerOutgoingPluginChannel(Plugin plugin, String channel);
+
+ /**
+ * Unregisters the specific plugin from the requested outgoing plugin channel, no longer
+ * allowing it to send messages through that channel to any clients.
+ *
+ * @param plugin Plugin that no longer wishes to send messages through the channel.
+ * @param channel Channel to unregister.
+ * @throws IllegalArgumentException Thrown if plugin or channel is null.
+ */
+ public void unregisterOutgoingPluginChannel(Plugin plugin, String channel);
+
+ /**
+ * Unregisters the specific plugin from all outgoing plugin channels, no longer allowing
+ * it to send any plugin messages.
+ *
+ * @param plugin Plugin that no longer wishes to send plugin messages.
+ * @throws IllegalArgumentException Thrown if plugin is null.
+ */
+ public void unregisterOutgoingPluginChannel(Plugin plugin);
+
+ /**
+ * Registers the specific plugin for listening on the requested incoming plugin channel,
+ * allowing it to act upon any plugin messages.
+ *
+ * @param plugin Plugin that wishes to register to this channel.
+ * @param channel Channel to register.
+ * @param listener Listener to receive messages on.
+ * @returns The resulting registration that was made as a result of this method.
+ * @throws IllegalArgumentException Thrown if plugin, channel or listener is null, or the listener is already registered for this channel.
+ */
+ public PluginMessageListenerRegistration registerIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener);
+
+ /**
+ * Unregisters the specific plugin's listener from listening on the requested incoming plugin channel,
+ * no longer allowing it to act upon any plugin messages.
+ *
+ * @param plugin Plugin that wishes to unregister from this channel.
+ * @param channel Channel to unregister.
+ * @param listener Listener to stop receiving messages on.
+ * @throws IllegalArgumentException Thrown if plugin, channel or listener is null.
+ */
+ public void unregisterIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener);
+
+ /**
+ * Unregisters the specific plugin from listening on the requested incoming plugin channel,
+ * no longer allowing it to act upon any plugin messages.
+ *
+ * @param plugin Plugin that wishes to unregister from this channel.
+ * @param channel Channel to unregister.
+ * @throws IllegalArgumentException Thrown if plugin or channel is null.
+ */
+ public void unregisterIncomingPluginChannel(Plugin plugin, String channel);
+
+ /**
+ * Unregisters the specific plugin from listening on all plugin channels through all listeners.
+ *
+ * @param plugin Plugin that wishes to unregister from this channel.
+ * @throws IllegalArgumentException Thrown if plugin is null.
+ */
+ public void unregisterIncomingPluginChannel(Plugin plugin);
+
+ /**
+ * Gets a set containing all the outgoing plugin channels.
+ *
+ * @return List of all registered outgoing plugin channels.
+ */
+ public Set getOutgoingChannels();
+
+ /**
+ * Gets a set containing all the outgoing plugin channels that the specified plugin is registered to.
+ *
+ * @param plugin Plugin to retrieve channels for.
+ * @return List of all registered outgoing plugin channels that a plugin is registered to.
+ * @throws IllegalArgumentException Thrown if plugin is null.
+ */
+ public Set getOutgoingChannels(Plugin plugin);
+
+ /**
+ * Gets a set containing all the incoming plugin channels.
+ *
+ * @return List of all registered incoming plugin channels.
+ */
+ public Set getIncomingChannels();
+
+ /**
+ * Gets a set containing all the incoming plugin channels that the specified plugin is registered for.
+ *
+ * @param plugin Plugin to retrieve channels for.
+ * @return List of all registered incoming plugin channels that the plugin is registered for.
+ * @throws IllegalArgumentException Thrown if plugin is null.
+ */
+ public Set getIncomingChannels(Plugin plugin);
+
+ /**
+ * Gets a set containing all the incoming plugin channel registrations that the specified plugin has.
+ *
+ * @param plugin Plugin to retrieve registrations for.
+ * @return List of all registrations that the plugin has.
+ * @throws IllegalArgumentException Thrown if plugin is null.
+ */
+ public Set getIncomingChannelRegistrations(Plugin plugin);
+
+ /**
+ * Gets a set containing all the incoming plugin channel registrations that are on the requested channel.
+ *
+ * @param channel Channel to retrieve registrations for.
+ * @return List of all registrations that are on the channel.
+ * @throws IllegalArgumentException Thrown if channel is null.
+ */
+ public Set getIncomingChannelRegistrations(String channel);
+
+ /**
+ * Gets a set containing all the incoming plugin channel registrations that the specified plugin has
+ * on the requested channel.
+ *
+ * @param plugin Plugin to retrieve registrations for.
+ * @param channel Channel to filter registrations by.
+ * @return List of all registrations that the plugin has.
+ * @throws IllegalArgumentException Thrown if plugin or channel is null.
+ */
+ public Set getIncomingChannelRegistrations(Plugin plugin, String channel);
+
+ /**
+ * Checks if the specified plugin message listener registration is valid.
+ *
+ * A registration is considered valid if it has not be unregistered and that the plugin
+ * is still enabled.
+ *
+ * @param registration Registration to check.
+ * @return True if the registration is valid, otherwise false.
+ */
+ public boolean isRegistrationValid(PluginMessageListenerRegistration registration);
+
+ /**
+ * Checks if the specified plugin has registered to receive incoming messages through the requested
+ * channel.
+ *
+ * @param plugin Plugin to check registration for.
+ * @param channel Channel to test for.
+ * @return True if the channel is registered, else false.
+ */
+ public boolean isIncomingChannelRegistered(Plugin plugin, String channel);
+
+ /**
+ * Checks if the specified plugin has registered to send outgoing messages through the requested
+ * channel.
+ *
+ * @param plugin Plugin to check registration for.
+ * @param channel Channel to test for.
+ * @return True if the channel is registered, else false.
+ */
+ public boolean isOutgoingChannelRegistered(Plugin plugin, String channel);
+
+ /**
+ * Dispatches the specified incoming message to any registered listeners.
+ *
+ * @param source Source of the message.
+ * @param channel Channel that the message was sent by.
+ * @param message Raw payload of the message.
+ */
+ public void dispatchIncomingMessage(Player source, String channel, byte[] message);
+}
diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java
new file mode 100644
index 0000000..0e197a6
--- /dev/null
+++ b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java
@@ -0,0 +1,19 @@
+package org.bukkit.plugin.messaging;
+
+import org.bukkit.entity.Player;
+
+/**
+ * A listener for a specific Plugin Channel, which will receive notifications of messages sent
+ * from a client.
+ */
+public interface PluginMessageListener {
+ /**
+ * A method that will be thrown when a {@link PluginMessageSource} sends a plugin
+ * message on a registered channel.
+ *
+ * @param channel Channel that the message was sent through.
+ * @param player Source of the message.
+ * @param message The raw message that was sent.
+ */
+ public void onPluginMessageReceived(String channel, Player player, byte[] message);
+}
diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java
new file mode 100644
index 0000000..850ba5e
--- /dev/null
+++ b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java
@@ -0,0 +1,103 @@
+package org.bukkit.plugin.messaging;
+
+import org.bukkit.plugin.Plugin;
+
+/**
+ * Contains information about a {@link Plugin}s registration to a plugin channel.
+ */
+public final class PluginMessageListenerRegistration {
+ private final Messenger messenger;
+ private final Plugin plugin;
+ private final String channel;
+ private final PluginMessageListener listener;
+
+ public PluginMessageListenerRegistration(Messenger messenger, Plugin plugin, String channel, PluginMessageListener listener) {
+ if (messenger == null) {
+ throw new IllegalArgumentException("Messenger cannot be null!");
+ }
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null!");
+ }
+ if (channel == null) {
+ throw new IllegalArgumentException("Channel cannot be null!");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener cannot be null!");
+ }
+
+ this.messenger = messenger;
+ this.plugin = plugin;
+ this.channel = channel;
+ this.listener = listener;
+ }
+
+ /**
+ * Gets the plugin channel that this registration is about.
+ *
+ * @return Plugin channel.
+ */
+ public String getChannel() {
+ return channel;
+ }
+
+ /**
+ * Gets the registered listener described by this registration.
+ *
+ * @return Registered listener.
+ */
+ public PluginMessageListener getListener() {
+ return listener;
+ }
+
+ /**
+ * Gets the plugin that this registration is for.
+ *
+ * @return Registered plugin.
+ */
+ public Plugin getPlugin() {
+ return plugin;
+ }
+
+ /**
+ * Checks if this registration is still valid.
+ *
+ * @return True if this registration is still valid, otherwise false.
+ */
+ public boolean isValid() {
+ return messenger.isRegistrationValid(this);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final PluginMessageListenerRegistration other = (PluginMessageListenerRegistration) obj;
+ if (this.messenger != other.messenger && (this.messenger == null || !this.messenger.equals(other.messenger))) {
+ return false;
+ }
+ if (this.plugin != other.plugin && (this.plugin == null || !this.plugin.equals(other.plugin))) {
+ return false;
+ }
+ if ((this.channel == null) ? (other.channel != null) : !this.channel.equals(other.channel)) {
+ return false;
+ }
+ if (this.listener != other.listener && (this.listener == null || !this.listener.equals(other.listener))) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 53 * hash + (this.messenger != null ? this.messenger.hashCode() : 0);
+ hash = 53 * hash + (this.plugin != null ? this.plugin.hashCode() : 0);
+ hash = 53 * hash + (this.channel != null ? this.channel.hashCode() : 0);
+ hash = 53 * hash + (this.listener != null ? this.listener.hashCode() : 0);
+ return hash;
+ }
+}
diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java b/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java
new file mode 100644
index 0000000..6383166
--- /dev/null
+++ b/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java
@@ -0,0 +1,32 @@
+package org.bukkit.plugin.messaging;
+
+import java.util.Set;
+import org.bukkit.plugin.Plugin;
+
+/**
+ * Represents a possible recipient for a Plugin Message.
+ */
+public interface PluginMessageRecipient {
+ /**
+ * Sends this recipient a Plugin Message on the specified outgoing channel.
+ *
+ * The message may not be larger than {@link Messenger#MAX_MESSAGE_SIZE} bytes, and the plugin must be registered to send
+ * messages on the specified channel.
+ *
+ * @param source The plugin that sent this message.
+ * @param channel The channel to send this message on.
+ * @param message The raw message to send.
+ * @throws IllegalArgumentException Thrown if the source plugin is disabled.
+ * @throws IllegalArgumentException Thrown if source, channel or message is null.
+ * @throws MessageTooLargeException Thrown if the message is too big.
+ * @throws ChannelNotRegisteredException Thrown if the channel is not registered for this plugin.
+ */
+ public void sendPluginMessage(Plugin source, String channel, byte[] message);
+
+ /**
+ * Gets a set containing all the Plugin Channels that this client is listening on.
+ *
+ * @return Set containing all the channels that this client may accept.
+ */
+ public Set getListeningPluginChannels();
+}
diff --git a/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java b/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java
new file mode 100644
index 0000000..2f6fafa
--- /dev/null
+++ b/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java
@@ -0,0 +1,11 @@
+package org.bukkit.plugin.messaging;
+
+/**
+ * Thrown if a plugin attempts to register for a reserved channel (such as "REGISTER")
+ */
+public class ReservedChannelException extends RuntimeException {
+
+ public ReservedChannelException(String name) {
+ super("Attempted to register for a reserved channel name ('" + name + "')");
+ }
+}
diff --git a/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java b/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java
new file mode 100644
index 0000000..e90f2e1
--- /dev/null
+++ b/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java
@@ -0,0 +1,476 @@
+package org.bukkit.plugin.messaging;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+
+/**
+ * Standard implementation to {@link Messenger}
+ */
+public class StandardMessenger implements Messenger {
+ private final Map> incomingByChannel = new HashMap>();
+ private final Map> incomingByPlugin = new HashMap>();
+ private final Map> outgoingByChannel = new HashMap>();
+ private final Map> outgoingByPlugin = new HashMap>();
+ private final Object incomingLock = new Object();
+ private final Object outgoingLock = new Object();
+
+ private void addToOutgoing(Plugin plugin, String channel) {
+ synchronized (outgoingLock) {
+ Set plugins = outgoingByChannel.get(channel);
+ Set channels = outgoingByPlugin.get(plugin);
+
+ if (plugins == null) {
+ plugins = new HashSet();
+ outgoingByChannel.put(channel, plugins);
+ }
+
+ if (channels == null) {
+ channels = new HashSet();
+ outgoingByPlugin.put(plugin, channels);
+ }
+
+ plugins.add(plugin);
+ channels.add(channel);
+ }
+ }
+
+ private void removeFromOutgoing(Plugin plugin, String channel) {
+ synchronized (outgoingLock) {
+ Set plugins = outgoingByChannel.get(channel);
+ Set channels = outgoingByPlugin.get(plugin);
+
+ if (plugins != null) {
+ plugins.remove(plugin);
+
+ if (plugins.isEmpty()) {
+ outgoingByChannel.remove(channel);
+ }
+ }
+
+ if (channels != null) {
+ channels.remove(channel);
+
+ if (channels.isEmpty()) {
+ outgoingByChannel.remove(channel);
+ }
+ }
+ }
+ }
+
+ private void removeFromOutgoing(Plugin plugin) {
+ synchronized (outgoingLock) {
+ Set channels = outgoingByPlugin.get(plugin);
+
+ if (channels != null) {
+ String[] toRemove = channels.toArray(new String[0]);
+
+ outgoingByPlugin.remove(plugin);
+
+ for (String channel : toRemove) {
+ removeFromOutgoing(plugin, channel);
+ }
+ }
+ }
+ }
+
+ private void addToIncoming(PluginMessageListenerRegistration registration) {
+ synchronized (incomingLock) {
+ Set registrations = incomingByChannel.get(registration.getChannel());
+
+ if (registrations == null) {
+ registrations = new HashSet();
+ incomingByChannel.put(registration.getChannel(), registrations);
+ } else {
+ if (registrations.contains(registration)) {
+ throw new IllegalArgumentException("This registration already exists");
+ }
+ }
+
+ registrations.add(registration);
+
+ registrations = incomingByPlugin.get(registration.getPlugin());
+
+ if (registrations == null) {
+ registrations = new HashSet();
+ incomingByPlugin.put(registration.getPlugin(), registrations);
+ } else {
+ if (registrations.contains(registration)) {
+ throw new IllegalArgumentException("This registration already exists");
+ }
+ }
+
+ registrations.add(registration);
+ }
+ }
+
+ private void removeFromIncoming(PluginMessageListenerRegistration registration) {
+ synchronized (incomingLock) {
+ Set registrations = incomingByChannel.get(registration.getChannel());
+
+ if (registrations != null) {
+ registrations.remove(registration);
+
+ if (registrations.isEmpty()) {
+ incomingByChannel.remove(registration.getChannel());
+ }
+ }
+
+ registrations = incomingByPlugin.get(registration.getPlugin());
+
+ if (registrations != null) {
+ registrations.remove(registration);
+
+ if (registrations.isEmpty()) {
+ incomingByPlugin.remove(registration.getPlugin());
+ }
+ }
+ }
+ }
+
+ private void removeFromIncoming(Plugin plugin, String channel) {
+ synchronized (incomingLock) {
+ Set registrations = incomingByPlugin.get(plugin);
+
+ if (registrations != null) {
+ PluginMessageListenerRegistration[] toRemove = registrations.toArray(new PluginMessageListenerRegistration[0]);
+
+ for (PluginMessageListenerRegistration registration : toRemove) {
+ if (registration.getChannel().equals(channel)) {
+ removeFromIncoming(registration);
+ }
+ }
+ }
+ }
+ }
+
+ private void removeFromIncoming(Plugin plugin) {
+ synchronized (incomingLock) {
+ Set registrations = incomingByPlugin.get(plugin);
+
+ if (registrations != null) {
+ PluginMessageListenerRegistration[] toRemove = registrations.toArray(new PluginMessageListenerRegistration[0]);
+
+ incomingByPlugin.remove(plugin);
+
+ for (PluginMessageListenerRegistration registration : toRemove) {
+ removeFromIncoming(registration);
+ }
+ }
+ }
+ }
+
+ public boolean isReservedChannel(String channel) {
+ validateChannel(channel);
+
+ return channel.equals("REGISTER") || channel.equals("UNREGISTER");
+ }
+
+ public void registerOutgoingPluginChannel(Plugin plugin, String channel) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+ validateChannel(channel);
+ if (isReservedChannel(channel)) {
+ throw new ReservedChannelException(channel);
+ }
+
+ addToOutgoing(plugin, channel);
+ }
+
+ public void unregisterOutgoingPluginChannel(Plugin plugin, String channel) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+ validateChannel(channel);
+
+ removeFromOutgoing(plugin, channel);
+ }
+
+ public void unregisterOutgoingPluginChannel(Plugin plugin) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+
+ removeFromOutgoing(plugin);
+ }
+
+ public PluginMessageListenerRegistration registerIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+ validateChannel(channel);
+ if (isReservedChannel(channel)) {
+ throw new ReservedChannelException(channel);
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener cannot be null");
+ }
+
+ PluginMessageListenerRegistration result = new PluginMessageListenerRegistration(this, plugin, channel, listener);
+
+ addToIncoming(result);
+
+ return result;
+ }
+
+ public void unregisterIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener cannot be null");
+ }
+ validateChannel(channel);
+
+ removeFromIncoming(new PluginMessageListenerRegistration(this, plugin, channel, listener));
+ }
+
+ public void unregisterIncomingPluginChannel(Plugin plugin, String channel) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+ validateChannel(channel);
+
+ removeFromIncoming(plugin, channel);
+ }
+
+ public void unregisterIncomingPluginChannel(Plugin plugin) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+
+ removeFromIncoming(plugin);
+ }
+
+ public Set getOutgoingChannels() {
+ synchronized (outgoingLock) {
+ Set keys = outgoingByChannel.keySet();
+ return ImmutableSet.copyOf(keys);
+ }
+ }
+
+ public Set getOutgoingChannels(Plugin plugin) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+
+ synchronized (outgoingLock) {
+ Set channels = outgoingByPlugin.get(plugin);
+
+ if (channels != null) {
+ return ImmutableSet.copyOf(channels);
+ } else {
+ return ImmutableSet.of();
+ }
+ }
+ }
+
+ public Set getIncomingChannels() {
+ synchronized (incomingLock) {
+ Set keys = incomingByChannel.keySet();
+ return ImmutableSet.copyOf(keys);
+ }
+ }
+
+ public Set getIncomingChannels(Plugin plugin) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+
+ synchronized (incomingLock) {
+ Set registrations = incomingByPlugin.get(plugin);
+
+ if (registrations != null) {
+ Builder builder = ImmutableSet.builder();
+
+ for (PluginMessageListenerRegistration registration : registrations) {
+ builder.add(registration.getChannel());
+ }
+
+ return builder.build();
+ } else {
+ return ImmutableSet.of();
+ }
+ }
+ }
+
+ public Set getIncomingChannelRegistrations(Plugin plugin) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+
+ synchronized (incomingLock) {
+ Set registrations = incomingByPlugin.get(plugin);
+
+ if (registrations != null) {
+ return ImmutableSet.copyOf(registrations);
+ } else {
+ return ImmutableSet.of();
+ }
+ }
+ }
+
+ public Set getIncomingChannelRegistrations(String channel) {
+ validateChannel(channel);
+
+ synchronized (incomingLock) {
+ Set registrations = incomingByChannel.get(channel);
+
+ if (registrations != null) {
+ return ImmutableSet.copyOf(registrations);
+ } else {
+ return ImmutableSet.of();
+ }
+ }
+ }
+
+ public Set getIncomingChannelRegistrations(Plugin plugin, String channel) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+ validateChannel(channel);
+
+ synchronized (incomingLock) {
+ Set registrations = incomingByPlugin.get(plugin);
+
+ if (registrations != null) {
+ Builder builder = ImmutableSet.builder();
+
+ for (PluginMessageListenerRegistration registration : registrations) {
+ if (registration.getChannel().equals(channel)) {
+ builder.add(registration);
+ }
+ }
+
+ return builder.build();
+ } else {
+ return ImmutableSet.of();
+ }
+ }
+ }
+
+ public boolean isRegistrationValid(PluginMessageListenerRegistration registration) {
+ if (registration == null) {
+ throw new IllegalArgumentException("Registration cannot be null");
+ }
+
+ synchronized (incomingLock) {
+ Set registrations = incomingByPlugin.get(registration.getPlugin());
+
+ if (registrations != null) {
+ return registrations.contains(registration);
+ }
+
+ return false;
+ }
+ }
+
+ public boolean isIncomingChannelRegistered(Plugin plugin, String channel) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+ validateChannel(channel);
+
+ synchronized (incomingLock) {
+ Set registrations = incomingByPlugin.get(plugin);
+
+ if (registrations != null) {
+ for (PluginMessageListenerRegistration registration : registrations) {
+ if (registration.getChannel().equals(channel)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+ }
+
+ public boolean isOutgoingChannelRegistered(Plugin plugin, String channel) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+ validateChannel(channel);
+
+ synchronized (outgoingLock) {
+ Set channels = outgoingByPlugin.get(plugin);
+
+ if (channels != null) {
+ return channels.contains(channel);
+ }
+
+ return false;
+ }
+ }
+
+ public void dispatchIncomingMessage(Player source, String channel, byte[] message) {
+ if (source == null) {
+ throw new IllegalArgumentException("Player source cannot be null");
+ }
+ if (message == null) {
+ throw new IllegalArgumentException("Message cannot be null");
+ }
+ validateChannel(channel);
+
+ Set registrations = getIncomingChannelRegistrations(channel);
+
+ for (PluginMessageListenerRegistration registration : registrations) {
+ registration.getListener().onPluginMessageReceived(channel, source, message);
+ }
+ }
+
+ /**
+ * Validates a Plugin Channel name.
+ *
+ * @param channel Channel name to validate.
+ */
+ public static void validateChannel(String channel) {
+ if (channel == null) {
+ throw new IllegalArgumentException("Channel cannot be null");
+ }
+ if (channel.length() > Messenger.MAX_CHANNEL_SIZE) {
+ throw new ChannelNameTooLongException(channel);
+ }
+ }
+
+ /**
+ * Validates the input of a Plugin Message, ensuring the arguments are all valid.
+ *
+ * @param messenger Messenger to use for validation.
+ * @param source Source plugin of the Message.
+ * @param channel Plugin Channel to send the message by.
+ * @param message Raw message payload to send.
+ * @throws IllegalArgumentException Thrown if the source plugin is disabled.
+ * @throws IllegalArgumentException Thrown if source, channel or message is null.
+ * @throws MessageTooLargeException Thrown if the message is too big.
+ * @throws ChannelNameTooLongException Thrown if the channel name is too long.
+ * @throws ChannelNotRegisteredException Thrown if the channel is not registered for this plugin.
+ */
+ public static void validatePluginMessage(Messenger messenger, Plugin source, String channel, byte[] message) {
+ if (messenger == null) {
+ throw new IllegalArgumentException("Messenger cannot be null");
+ }
+ if (source == null) {
+ throw new IllegalArgumentException("Plugin source cannot be null");
+ }
+ if (!source.isEnabled()) {
+ throw new IllegalArgumentException("Plugin must be enabled to send messages");
+ }
+ if (message == null) {
+ throw new IllegalArgumentException("Message cannot be null");
+ }
+ if (!messenger.isOutgoingChannelRegistered(source, channel)) {
+ throw new ChannelNotRegisteredException(channel);
+ }
+ if (message.length > Messenger.MAX_MESSAGE_SIZE) {
+ throw new MessageTooLargeException(message);
+ }
+ validateChannel(channel);
+ }
+}
From 01ae0c3dd84640d8941e978e2d6d24c74b78c937 Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Sun, 8 Feb 2026 18:53:42 +0100
Subject: [PATCH 09/23] Switch back to Configurate
---
pom.xml | 24 ++-
.../org/betamc/tsunami/TsunamiConfig.java | 149 ++++++------------
2 files changed, 55 insertions(+), 118 deletions(-)
diff --git a/pom.xml b/pom.xml
index 6304993..3aac7fd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,13 +13,6 @@
UTF-8
-
-
- Alpine Cloud
- https://lib.alpn.cloud/alpine-public/
-
-
-
net.sf.jopt-simple
@@ -66,9 +59,9 @@
1.7
- dev.tomwmth.configlib
- configlib-yaml
- 4.6.0
+ org.spongepowered
+ configurate-yaml
+ 4.2.0com.google.guava
@@ -108,12 +101,12 @@
- clean install
+ clean packageorg.apache.maven.pluginsmaven-source-plugin
- 3.3.1
+ 3.4.0attach-sources
@@ -126,7 +119,7 @@
org.apache.maven.pluginsmaven-jar-plugin
- 3.2.0
+ 3.5.0
@@ -164,7 +157,7 @@
org.apache.maven.pluginsmaven-shade-plugin
- 3.2.4
+ 3.6.1package
@@ -182,6 +175,7 @@
*:*META-INF/*.RSA
+ META-INF/*.DSAMETA-INF/*.SF
@@ -193,7 +187,7 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 3.8.1
+ 3.15.01.81.8
diff --git a/src/main/java/org/betamc/tsunami/TsunamiConfig.java b/src/main/java/org/betamc/tsunami/TsunamiConfig.java
index fc09648..16343b9 100644
--- a/src/main/java/org/betamc/tsunami/TsunamiConfig.java
+++ b/src/main/java/org/betamc/tsunami/TsunamiConfig.java
@@ -1,36 +1,42 @@
package org.betamc.tsunami;
-import de.exlll.configlib.Comment;
-import de.exlll.configlib.Configuration;
-import de.exlll.configlib.NameFormatters;
-import de.exlll.configlib.YamlConfigurations;
+import org.spongepowered.configurate.ConfigurateException;
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
+import org.spongepowered.configurate.yaml.NodeStyle;
+import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
-@Configuration
+@ConfigSerializable
public class TsunamiConfig {
private static TsunamiConfig instance;
- private Console console = new Console();
- private Logging logging = new Logging();
- private Networking networking = new Networking();
- private Profiles profiles = new Profiles();
- private Rcon rcon = new Rcon();
- private ServerListPing serverListPing = new ServerListPing();
- private Anticheat anticheat = new Anticheat();
- private World world = new World();
+ private Console console;
+ private Logging logging;
+ private Networking networking;
+ private Profiles profiles;
+ private Rcon rcon;
+ private ServerListPing serverListPing;
+ private Anticheat anticheat;
+ private World world;
public static TsunamiConfig getInstance() {
- if (instance == null) {
- instance = YamlConfigurations.update(Paths.get("tsunami.yml"), TsunamiConfig.class, builder -> builder
- .charset(StandardCharsets.UTF_8)
- .setNameFormatter(NameFormatters.LOWER_KEBAB_CASE)
- .inputNulls(false)
- .outputNulls(false));
- }
- return instance;
+ if (instance != null) return instance;
+
+ YamlConfigurationLoader loader = YamlConfigurationLoader.builder()
+ .path(Paths.get("tsunami.yml"))
+ .indent(2)
+ .nodeStyle(NodeStyle.BLOCK)
+ .build();
+
+ try {
+ instance = loader.load().get(TsunamiConfig.class);
+ loader.save(loader.createNode().set(instance));
+ return instance;
+ } catch (ConfigurateException e) {
+ throw new RuntimeException(e);
+ }
}
private TsunamiConfig() {
@@ -68,16 +74,10 @@ public World world() {
return world;
}
- @Configuration
+ @ConfigSerializable
public static class Console {
- @Comment({
- "The prompt which will show in console.",
- "Color codes (§[0-9a-f]) can be used here."
- })
private String prompt = "> ";
- @Comment("If warning messages should be highlighted.")
private boolean highlightWarnings = true;
- @Comment("If error messages should be highlighted.")
private boolean highlightErrors = true;
public String prompt() {
@@ -93,9 +93,8 @@ public boolean highlightErrors() {
}
}
- @Configuration
+ @ConfigSerializable
public static class Logging {
- @Comment("If attempts to issue unknown commands should be logged.")
private boolean logUnknownCommands = false;
public boolean logUnknownCommands() {
@@ -103,12 +102,8 @@ public boolean logUnknownCommands() {
}
}
- @Configuration
+ @ConfigSerializable
public static class Networking {
- @Comment({
- "The deflate compression level used to compress chunk packets.",
- "Acceptable values are [-1..9]."
- })
private int chunkPacketCompressionLevel = 6;
public int chunkPacketCompressionLevel() {
@@ -116,25 +111,13 @@ public int chunkPacketCompressionLevel() {
}
}
- @Configuration
+ @ConfigSerializable
public static class Profiles {
- @Comment({
- "The HTTP method used to fetch player profiles.",
- "Acceptable values are POST and GET."
- })
private FetchMethod fetchMethod = FetchMethod.POST;
- @Comment("The URL which should be used for POST requests.")
private String postUrl = "https://api.minecraftservices.com/minecraft/profile/lookup/bulk/byname";
- @Comment("The URL which should be used for GET requests. {username} will be replaced by the actual name.")
private String getUrl = "https://api.minecraftservices.com/minecraft/profile/lookup/name/{username}";
- @Comment("If the name of a player with an online profile is required to exactly match the name returned by the API.")
private boolean verifyUsernameCasing = false;
- @Comment({
- "Specifies in which cases offline profiles should be created for players.",
- "Acceptable values are NEVER, WHEN_CRACKED and ALWAYS."
- })
private CreateOfflineProfiles createOfflineProfiles = CreateOfflineProfiles.NEVER;
- @Comment("After how many days online profiles should be refetched.")
private int refetchAfterDays = 30;
public FetchMethod fetchMethod() {
@@ -173,16 +156,10 @@ public enum CreateOfflineProfiles {
}
}
- @Configuration
+ @ConfigSerializable
public static class Rcon {
- @Comment({
- "If the remote console protocol should be enabled.",
- "Please note that RCON is not encrypted and should not be used in a production environment."
- })
private boolean enabled = false;
- @Comment("The port used to listen for RCON connections.")
private int port = 25575;
- @Comment("The password required to establish an RCON connection.")
private String password = "";
public boolean enabled() {
@@ -198,16 +175,10 @@ public String password() {
}
}
- @Configuration
+ @ConfigSerializable
public static class ServerListPing {
- @Comment("If the 1.7+ query protocol should be enabled.")
private boolean enabled = false;
- @Comment({
- "The MOTD which should be included in the query response.",
- "Color codes (§[0-9a-f]) can be used here."
- })
private String motd = "A Minecraft Server";
- @Comment("If the names of connected players should be included in the query response.")
private boolean showPlayerNames = true;
public boolean enabled() {
@@ -223,11 +194,11 @@ public boolean showPlayerNames() {
}
}
- @Configuration
+ @ConfigSerializable
public static class Anticheat {
- private FlagQuickMovement flagQuickMovement = new FlagQuickMovement();
- private FlagWrongMovement flagWrongMovement = new FlagWrongMovement();
- private FlagFlight flagFlight = new FlagFlight();
+ private FlagQuickMovement flagQuickMovement;
+ private FlagWrongMovement flagWrongMovement;
+ private FlagFlight flagFlight;
public FlagQuickMovement flagQuickMovement() {
return flagQuickMovement;
@@ -241,19 +212,10 @@ public FlagFlight flagFlight() {
return flagFlight;
}
- @Configuration
+ @ConfigSerializable
public static class FlagQuickMovement {
- @Comment({
- "If too quick movement should be flagged.",
- "Players with the permission 'tsunami.anticheat.quick-movement.bypass' are exempt from being flagged."
- })
private boolean enabled = true;
- @Comment("The distance threshold for quick movement to be flagged.")
private double threshold = 100.0;
- @Comment({
- "If flagged players should be teleported back to their previous location.",
- "If this is disabled, players will be kicked instead."
- })
private boolean teleportBack = false;
public boolean enabled() {
@@ -269,16 +231,10 @@ public boolean teleportBack() {
}
}
- @Configuration
+ @ConfigSerializable
public static class FlagWrongMovement {
- @Comment({
- "If wrong movement should be flagged.",
- "Players with the permission 'tsunami.anticheat.wrong-movement.bypass' are exempt from being flagged."
- })
private boolean enabled = true;
- @Comment("The distance threshold for wrong movement to be flagged.")
private double threshold = 0.0625;
- @Comment("If flagged players should be teleported back to their previous location.")
private boolean teleportBack = true;
public boolean enabled() {
@@ -294,12 +250,8 @@ public boolean teleportBack() {
}
}
- @Configuration
+ @ConfigSerializable
public static class FlagFlight {
- @Comment({
- "After how many ticks players should be kicked for flying.",
- "Players with the permission 'tsunami.anticheat.flight.bypass' are exempt from being flagged."
- })
private int kickAfter = 80;
public int kickAfter() {
@@ -308,17 +260,13 @@ public int kickAfter() {
}
}
- @Configuration
+ @ConfigSerializable
public static class World {
- private AsyncChunkLoading asyncChunkLoading = new AsyncChunkLoading();
- @Comment("The interval in ticks in which world data should be auto-saved.")
+ private AsyncChunkLoading asyncChunkLoading;
private int autoSaveInterval = 6000;
- @Comment("The maximum amount of chunks to auto-save per tick.")
private int maxAutoSaveChunksPerTick = 24;
- private MobCaps mobCaps = new MobCaps();
- @Comment("If mob caps should be enforced on a per-player basis instead of globally.")
+ private MobCaps mobCaps;
private boolean perPlayerMobSpawning = false;
- @Comment("If dropped items should merge if they are of the same type.")
private boolean mergeDroppedItems = false;
public AsyncChunkLoading asyncChunkLoading() {
@@ -345,11 +293,9 @@ public boolean mergeDroppedItems() {
return mergeDroppedItems;
}
- @Configuration
+ @ConfigSerializable
public static class AsyncChunkLoading {
- @Comment("If chunks should be loaded from disk asynchronously.")
private boolean enabled = false;
- @Comment("The amount of threads to use for loading chunks.")
private int threads = 3;
public boolean enabled() {
@@ -361,13 +307,10 @@ public int threads() {
}
}
- @Configuration
+ @ConfigSerializable
public static class MobCaps {
- @Comment("The mob cap for hostile mobs.")
private int monsters = 70;
- @Comment("The mob cap for passive mobs.")
private int animals = 15;
- @Comment("The mob cap for water mobs (squids).")
private int waterMobs = 5;
public int monsters() {
From 6da1e77b296cccab35eb343cd8c149137aa6a0c6 Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Mon, 9 Feb 2026 16:58:29 +0100
Subject: [PATCH 10/23] Improve ProjectileHitEvent
---
.../net/minecraft/server/EntityArrow.java | 85 ++++---
.../java/net/minecraft/server/EntityEgg.java | 215 ++++++++++--------
.../net/minecraft/server/EntityFireball.java | 30 ++-
.../net/minecraft/server/EntitySnowball.java | 70 +++---
.../event/entity/ProjectileHitEvent.java | 76 ++++++-
5 files changed, 302 insertions(+), 174 deletions(-)
diff --git a/src/main/java/net/minecraft/server/EntityArrow.java b/src/main/java/net/minecraft/server/EntityArrow.java
index 9c6a90f..eeb9f1c 100644
--- a/src/main/java/net/minecraft/server/EntityArrow.java
+++ b/src/main/java/net/minecraft/server/EntityArrow.java
@@ -1,5 +1,7 @@
package net.minecraft.server;
+import org.bukkit.block.BlockFace;
+import org.bukkit.craftbukkit.block.CraftBlock;
import org.bukkit.craftbukkit.entity.CraftLivingEntity;
import org.bukkit.entity.Projectile;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
@@ -161,48 +163,57 @@ public void m_() {
float f2;
if (movingobjectposition != null) {
- // CraftBukkit start
- ProjectileHitEvent phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity());
- this.world.getServer().getPluginManager().callEvent(phe);
- // CraftBukkit end
if (movingobjectposition.entity != null) {
- // CraftBukkit start
- boolean stick;
- if (entity instanceof EntityLiving) {
- org.bukkit.Server server = this.world.getServer();
-
- // TODO decide if we should create DamageCause.ARROW, DamageCause.PROJECTILE
- // or leave as DamageCause.ENTITY_ATTACK
- org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity();
- Projectile projectile = (Projectile) this.getBukkitEntity();
- // TODO deal with arrows being fired from a non-entity
-
- EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(projectile, damagee, EntityDamageEvent.DamageCause.PROJECTILE, 4);
- server.getPluginManager().callEvent(event);
- this.shooter = (projectile.getShooter() == null) ? null : ((CraftLivingEntity) projectile.getShooter()).getHandle();
-
- if (event.isCancelled()) {
- stick = !projectile.doesBounce();
+ // Tsunami start - improve ProjectileHitEvent
+ ProjectileHitEvent phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), movingobjectposition.entity.getBukkitEntity());
+ this.world.getServer().getPluginManager().callEvent(phe);
+ if (!phe.isCancelled()) {
+ // Tsunami end
+
+ // CraftBukkit start
+ boolean stick;
+ if (entity instanceof EntityLiving) {
+ org.bukkit.Server server = this.world.getServer();
+
+ // TODO decide if we should create DamageCause.ARROW, DamageCause.PROJECTILE
+ // or leave as DamageCause.ENTITY_ATTACK
+ org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity();
+ Projectile projectile = (Projectile) this.getBukkitEntity();
+ // TODO deal with arrows being fired from a non-entity
+
+ EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(projectile, damagee, EntityDamageEvent.DamageCause.PROJECTILE, 4);
+ server.getPluginManager().callEvent(event);
+ this.shooter = (projectile.getShooter() == null) ? null : ((CraftLivingEntity) projectile.getShooter()).getHandle();
+
+ if (event.isCancelled()) {
+ stick = !projectile.doesBounce();
+ } else {
+ // this function returns if the arrow should stick in or not, i.e. !bounce
+ stick = movingobjectposition.entity.damageEntity(this, event.getDamage());
+ }
} else {
- // this function returns if the arrow should stick in or not, i.e. !bounce
- stick = movingobjectposition.entity.damageEntity(this, event.getDamage());
+ stick = movingobjectposition.entity.damageEntity(this.shooter, 4);
+ }
+ if (stick) {
+ // CraftBukkit end
+ this.world.makeSound(this, "random.drr", 1.0F, 1.2F / (this.random.nextFloat() * 0.2F + 0.9F));
+ this.die();
+ } else {
+ this.motX *= -0.10000000149011612D;
+ this.motY *= -0.10000000149011612D;
+ this.motZ *= -0.10000000149011612D;
+ this.yaw += 180.0F;
+ this.lastYaw += 180.0F;
+ this.k = 0;
}
- } else {
- stick = movingobjectposition.entity.damageEntity(this.shooter, 4);
- }
- if (stick) {
- // CraftBukkit end
- this.world.makeSound(this, "random.drr", 1.0F, 1.2F / (this.random.nextFloat() * 0.2F + 0.9F));
- this.die();
- } else {
- this.motX *= -0.10000000149011612D;
- this.motY *= -0.10000000149011612D;
- this.motZ *= -0.10000000149011612D;
- this.yaw += 180.0F;
- this.lastYaw += 180.0F;
- this.k = 0;
}
} else {
+ // Tsunami start - improve ProjectileHitEvent
+ org.bukkit.block.Block block = this.world.getWorld().getBlockAt(movingobjectposition.b, movingobjectposition.c, movingobjectposition.d);
+ BlockFace face = CraftBlock.notchToBlockFace(movingobjectposition.face);
+ ProjectileHitEvent phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), block, face);
+ this.world.getServer().getPluginManager().callEvent(phe);
+ // Tsunami end
this.d = movingobjectposition.b;
this.e = movingobjectposition.c;
this.f = movingobjectposition.d;
diff --git a/src/main/java/net/minecraft/server/EntityEgg.java b/src/main/java/net/minecraft/server/EntityEgg.java
index d80da64..27b130b 100644
--- a/src/main/java/net/minecraft/server/EntityEgg.java
+++ b/src/main/java/net/minecraft/server/EntityEgg.java
@@ -1,5 +1,7 @@
package net.minecraft.server;
+import org.bukkit.block.BlockFace;
+import org.bukkit.craftbukkit.block.CraftBlock;
import org.bukkit.entity.CreatureType;
import org.bukkit.entity.Projectile;
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
@@ -148,123 +150,138 @@ public void m_() {
}
if (movingobjectposition != null) {
- // CraftBukkit start
- ProjectileHitEvent phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity());
- this.world.getServer().getPluginManager().callEvent(phe);
+ ProjectileHitEvent phe;
+ // CraftBukkit start
if (movingobjectposition.entity != null) {
- boolean stick;
- if (movingobjectposition.entity instanceof EntityLiving) {
- org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity();
- Projectile projectile = (Projectile) this.getBukkitEntity();
-
- // TODO @see EntityArrow#162
- EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(projectile, damagee, EntityDamageEvent.DamageCause.PROJECTILE, 0);
- this.world.getServer().getPluginManager().callEvent(event);
-
- if (event.isCancelled()) {
- stick = !projectile.doesBounce();
+ // Tsunami start - improve ProjectileHitEvent
+ phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), movingobjectposition.entity.getBukkitEntity());
+ this.world.getServer().getPluginManager().callEvent(phe);
+ if (!phe.isCancelled()) {
+ // Tsunami end
+
+ boolean stick;
+ if (movingobjectposition.entity instanceof EntityLiving) {
+ org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity();
+ Projectile projectile = (Projectile) this.getBukkitEntity();
+
+ // TODO @see EntityArrow#162
+ EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(projectile, damagee, EntityDamageEvent.DamageCause.PROJECTILE, 0);
+ this.world.getServer().getPluginManager().callEvent(event);
+
+ if (event.isCancelled()) {
+ stick = !projectile.doesBounce();
+ } else {
+ // this function returns if the egg should stick in or not, i.e. !bounce
+ stick = movingobjectposition.entity.damageEntity(this, event.getDamage());
+ }
} else {
- // this function returns if the egg should stick in or not, i.e. !bounce
- stick = movingobjectposition.entity.damageEntity(this, event.getDamage());
+ stick = movingobjectposition.entity.damageEntity(this.thrower, 0);
}
- } else {
- stick = movingobjectposition.entity.damageEntity(this.thrower, 0);
- }
- if (stick) {
- ; // Original code does nothing *yet*
+ if (stick) {
+ ; // Original code does nothing *yet*
+ }
}
+ } else {
+ // Tsunami start - improve ProjectileHitEvent
+ org.bukkit.block.Block block = this.world.getWorld().getBlockAt(movingobjectposition.b, movingobjectposition.c, movingobjectposition.d);
+ BlockFace face = CraftBlock.notchToBlockFace(movingobjectposition.face);
+ phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), block, face);
+ this.world.getServer().getPluginManager().callEvent(phe);
+ // Tsunami end
}
- boolean hatching = !this.world.isStatic && this.random.nextInt(8) == 0;
- int numHatching = (this.random.nextInt(32) == 0) ? 4 : 1;
- if (!hatching) {
- numHatching = 0;
- }
+ if (!phe.isCancelled() || phe.getHitEntity() == null) { // Tsunami
+ boolean hatching = !this.world.isStatic && this.random.nextInt(8) == 0;
+ int numHatching = (this.random.nextInt(32) == 0) ? 4 : 1;
+ if (!hatching) {
+ numHatching = 0;
+ }
- CreatureType hatchingType = CreatureType.CHICKEN;
+ CreatureType hatchingType = CreatureType.CHICKEN;
- if (this.thrower instanceof EntityPlayer) {
- org.bukkit.entity.Player player = (this.thrower == null) ? null : (org.bukkit.entity.Player) this.thrower.getBukkitEntity();
+ if (this.thrower instanceof EntityPlayer) {
+ org.bukkit.entity.Player player = (this.thrower == null) ? null : (org.bukkit.entity.Player) this.thrower.getBukkitEntity();
- PlayerEggThrowEvent event = new PlayerEggThrowEvent(player, (org.bukkit.entity.Egg) this.getBukkitEntity(), hatching, (byte) numHatching, hatchingType);
- this.world.getServer().getPluginManager().callEvent(event);
+ PlayerEggThrowEvent event = new PlayerEggThrowEvent(player, (org.bukkit.entity.Egg) this.getBukkitEntity(), hatching, (byte) numHatching, hatchingType);
+ this.world.getServer().getPluginManager().callEvent(event);
- hatching = event.isHatching();
- numHatching = event.getNumHatches();
- hatchingType = event.getHatchType();
- }
+ hatching = event.isHatching();
+ numHatching = event.getNumHatches();
+ hatchingType = event.getHatchType();
+ }
- if (hatching) {
- for (int k = 0; k < numHatching; k++) {
- Entity entity = null;
- switch (hatchingType) {
- case CHICKEN:
- entity = new EntityChicken(this.world);
- break;
- case COW:
- entity = new EntityCow(this.world);
- break;
- case CREEPER:
- entity = new EntityCreeper(this.world);
- break;
- case GHAST:
- entity = new EntityGhast(this.world);
- break;
- case GIANT:
- entity = new EntityGiantZombie(this.world);
- break;
- case PIG:
- entity = new EntityPig(this.world);
- break;
- case PIG_ZOMBIE:
- entity = new EntityPigZombie(this.world);
- break;
- case SHEEP:
- entity = new EntitySheep(this.world);
- break;
- case SKELETON:
- entity = new EntitySkeleton(this.world);
- break;
- case SPIDER:
- entity = new EntitySpider(this.world);
- break;
- case ZOMBIE:
- entity = new EntityZombie(this.world);
- break;
- case SQUID:
- entity = new EntitySquid(this.world);
- break;
- case SLIME:
- entity = new EntitySlime(this.world);
- break;
- case WOLF:
- entity = new EntityWolf(this.world);
- break;
- case MONSTER:
- entity = new EntityMonster(this.world);
- break;
- default:
- entity = new EntityChicken(this.world);
- break;
- }
+ if (hatching) {
+ for (int k = 0; k < numHatching; k++) {
+ Entity entity = null;
+ switch (hatchingType) {
+ case CHICKEN:
+ entity = new EntityChicken(this.world);
+ break;
+ case COW:
+ entity = new EntityCow(this.world);
+ break;
+ case CREEPER:
+ entity = new EntityCreeper(this.world);
+ break;
+ case GHAST:
+ entity = new EntityGhast(this.world);
+ break;
+ case GIANT:
+ entity = new EntityGiantZombie(this.world);
+ break;
+ case PIG:
+ entity = new EntityPig(this.world);
+ break;
+ case PIG_ZOMBIE:
+ entity = new EntityPigZombie(this.world);
+ break;
+ case SHEEP:
+ entity = new EntitySheep(this.world);
+ break;
+ case SKELETON:
+ entity = new EntitySkeleton(this.world);
+ break;
+ case SPIDER:
+ entity = new EntitySpider(this.world);
+ break;
+ case ZOMBIE:
+ entity = new EntityZombie(this.world);
+ break;
+ case SQUID:
+ entity = new EntitySquid(this.world);
+ break;
+ case SLIME:
+ entity = new EntitySlime(this.world);
+ break;
+ case WOLF:
+ entity = new EntityWolf(this.world);
+ break;
+ case MONSTER:
+ entity = new EntityMonster(this.world);
+ break;
+ default:
+ entity = new EntityChicken(this.world);
+ break;
+ }
- // The world we're spawning in accepts this creature
- boolean isAnimal = entity instanceof EntityAnimal || entity instanceof EntityWaterAnimal;
- if ((isAnimal && this.world.allowAnimals) || (!isAnimal && this.world.allowMonsters)) {
- entity.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, 0.0F);
- this.world.addEntity(entity, SpawnReason.EGG);
+ // The world we're spawning in accepts this creature
+ boolean isAnimal = entity instanceof EntityAnimal || entity instanceof EntityWaterAnimal;
+ if ((isAnimal && this.world.allowAnimals) || (!isAnimal && this.world.allowMonsters)) {
+ entity.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, 0.0F);
+ this.world.addEntity(entity, SpawnReason.EGG);
+ }
+ // CraftBukkit end
}
- // CraftBukkit end
}
- }
- for (int l = 0; l < 8; ++l) {
- this.world.a("snowballpoof", this.locX, this.locY, this.locZ, 0.0D, 0.0D, 0.0D);
- }
+ for (int l = 0; l < 8; ++l) {
+ this.world.a("snowballpoof", this.locX, this.locY, this.locZ, 0.0D, 0.0D, 0.0D);
+ }
- this.die();
+ this.die();
+ }
}
this.locX += this.motX;
diff --git a/src/main/java/net/minecraft/server/EntityFireball.java b/src/main/java/net/minecraft/server/EntityFireball.java
index 4e36ab6..aa69f7f 100644
--- a/src/main/java/net/minecraft/server/EntityFireball.java
+++ b/src/main/java/net/minecraft/server/EntityFireball.java
@@ -1,5 +1,7 @@
package net.minecraft.server;
+import org.bukkit.block.BlockFace;
+import org.bukkit.craftbukkit.block.CraftBlock;
import org.bukkit.craftbukkit.entity.CraftEntity;
import org.bukkit.craftbukkit.entity.CraftLivingEntity;
import org.bukkit.entity.Explosive;
@@ -126,13 +128,16 @@ public void m_() {
}
if (movingobjectposition != null) {
+ ProjectileHitEvent phe;
+
// CraftBukkit start
- ProjectileHitEvent phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity());
- this.world.getServer().getPluginManager().callEvent(phe);
- // CraftBukkit end
- if (!this.world.isStatic) {
- // CraftBukkit start
- if (movingobjectposition.entity != null) {
+ if (movingobjectposition.entity != null) {
+ // Tsunami start - improve ProjectileHitEvent
+ phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), movingobjectposition.entity.getBukkitEntity());
+ this.world.getServer().getPluginManager().callEvent(phe);
+ if (!phe.isCancelled()) {
+ // Tsunami end
+
boolean stick;
if (movingobjectposition.entity instanceof EntityLiving) {
org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity();
@@ -157,7 +162,16 @@ public void m_() {
;
}
}
+ } else {
+ // Tsunami start - improve ProjectileHitEvent
+ org.bukkit.block.Block block = this.world.getWorld().getBlockAt(movingobjectposition.b, movingobjectposition.c, movingobjectposition.d);
+ BlockFace face = CraftBlock.notchToBlockFace(movingobjectposition.face);
+ phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), block, face);
+ this.world.getServer().getPluginManager().callEvent(phe);
+ // Tsunami end
+ }
+ if (!phe.isCancelled() || phe.getHitEntity() == null) { // Tsunami
ExplosionPrimeEvent event = new ExplosionPrimeEvent((Explosive) CraftEntity.getEntity(this.world.getServer(), this));
this.world.getServer().getPluginManager().callEvent(event);
@@ -166,9 +180,9 @@ public void m_() {
this.world.createExplosion(this, this.locX, this.locY, this.locZ, event.getRadius(), event.getFire());
}
// CraftBukkit end
- }
- this.die();
+ this.die();
+ }
}
this.locX += this.motX;
diff --git a/src/main/java/net/minecraft/server/EntitySnowball.java b/src/main/java/net/minecraft/server/EntitySnowball.java
index fb3da4e..719e0c0 100644
--- a/src/main/java/net/minecraft/server/EntitySnowball.java
+++ b/src/main/java/net/minecraft/server/EntitySnowball.java
@@ -1,5 +1,7 @@
package net.minecraft.server;
+import org.bukkit.block.BlockFace;
+import org.bukkit.craftbukkit.block.CraftBlock;
import org.bukkit.craftbukkit.entity.CraftLivingEntity;
import org.bukkit.entity.Projectile;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
@@ -146,41 +148,55 @@ public void m_() {
}
if (movingobjectposition != null) {
- // CraftBukkit start
- ProjectileHitEvent phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity());
- this.world.getServer().getPluginManager().callEvent(phe);
+ ProjectileHitEvent phe;
+ // CraftBukkit start
if (movingobjectposition.entity != null) {
- boolean stick;
- if (movingobjectposition.entity instanceof EntityLiving) {
- org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity();
- Projectile projectile = (Projectile) this.getBukkitEntity();
-
- // TODO @see EntityArrow#162
- EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(projectile, damagee, EntityDamageEvent.DamageCause.PROJECTILE, 0);
- this.world.getServer().getPluginManager().callEvent(event);
- this.shooter = (projectile.getShooter() == null) ? null : ((CraftLivingEntity) projectile.getShooter()).getHandle();
-
- if (event.isCancelled()) {
- stick = !projectile.doesBounce();
+ // Tsunami start - improve ProjectileHitEvent
+ phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), movingobjectposition.entity.getBukkitEntity());
+ this.world.getServer().getPluginManager().callEvent(phe);
+ if (!phe.isCancelled()) {
+ // Tsunami end
+
+ boolean stick;
+ if (movingobjectposition.entity instanceof EntityLiving) {
+ org.bukkit.entity.Entity damagee = movingobjectposition.entity.getBukkitEntity();
+ Projectile projectile = (Projectile) this.getBukkitEntity();
+
+ // TODO @see EntityArrow#162
+ EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(projectile, damagee, EntityDamageEvent.DamageCause.PROJECTILE, 0);
+ this.world.getServer().getPluginManager().callEvent(event);
+ this.shooter = (projectile.getShooter() == null) ? null : ((CraftLivingEntity) projectile.getShooter()).getHandle();
+
+ if (event.isCancelled()) {
+ stick = !projectile.doesBounce();
+ } else {
+ // this function returns if the snowball should stick in or not, i.e. !bounce
+ stick = movingobjectposition.entity.damageEntity(this, event.getDamage());
+ }
} else {
- // this function returns if the snowball should stick in or not, i.e. !bounce
- stick = movingobjectposition.entity.damageEntity(this, event.getDamage());
+ stick = movingobjectposition.entity.damageEntity(this.shooter, 0);
+ }
+ if (stick) {
+ ;
}
- } else {
- stick = movingobjectposition.entity.damageEntity(this.shooter, 0);
- }
- if (stick) {
- ;
}
+ // CraftBukkit end
+ } else {
+ // Tsunami start - improve ProjectileHitEvent
+ org.bukkit.block.Block block = this.world.getWorld().getBlockAt(movingobjectposition.b, movingobjectposition.c, movingobjectposition.d);
+ BlockFace face = CraftBlock.notchToBlockFace(movingobjectposition.face);
+ phe = new ProjectileHitEvent((Projectile) this.getBukkitEntity(), block, face);
+ this.world.getServer().getPluginManager().callEvent(phe);
+ // Tsunami end
}
- // CraftBukkit end
- for (int k = 0; k < 8; ++k) {
- this.world.a("snowballpoof", this.locX, this.locY, this.locZ, 0.0D, 0.0D, 0.0D);
+ if (!phe.isCancelled() || phe.getHitEntity() == null) { // Tsunami
+ for (int k = 0; k < 8; ++k) {
+ this.world.a("snowballpoof", this.locX, this.locY, this.locZ, 0.0D, 0.0D, 0.0D);
+ }
+ this.die();
}
-
- this.die();
}
this.locX += this.motX;
diff --git a/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java b/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java
index b1e21f9..57267fe 100644
--- a/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java
+++ b/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java
@@ -1,14 +1,84 @@
package org.bukkit.event.entity;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
+import org.bukkit.entity.Entity;
import org.bukkit.entity.Projectile;
+import org.bukkit.event.Cancellable;
/**
- * Called when a projectile hits an object
+ * Called when a projectile hits a block or an entity
*/
-public class ProjectileHitEvent extends EntityEvent {
+public class ProjectileHitEvent extends EntityEvent implements Cancellable { // Tsunami - implements Cancellable
- public ProjectileHitEvent(Projectile projectile) {
+ // Tsunami start - improve ProjectileHitEvent
+ private final Projectile projectile;
+ private final Entity hitEntity;
+ private final Block hitBlock;
+ private final BlockFace hitFace;
+ private boolean cancelled = false;
+
+ public ProjectileHitEvent(Projectile projectile, Entity hitEntity) {
+ super(Type.PROJECTILE_HIT, projectile);
+ this.projectile = projectile;
+ this.hitEntity = hitEntity;
+ this.hitBlock = null;
+ this.hitFace = null;
+ }
+
+ public ProjectileHitEvent(Projectile projectile, Block hitBlock, BlockFace hitFace) {
super(Type.PROJECTILE_HIT, projectile);
+ this.projectile = projectile;
+ this.hitBlock = hitBlock;
+ this.hitFace = hitFace;
+ this.hitEntity = null;
+ }
+
+ /**
+ * Gets the projectile involved in this event
+ *
+ * @return the projectile
+ */
+ public Projectile getProjectile() {
+ return this.projectile;
+ }
+
+ /**
+ * Gets the entity that was hit, if it was an entity that was hit
+ *
+ * @return hit entity or else {@code null}
+ */
+ public Entity getHitEntity() {
+ return this.hitEntity;
+ }
+
+ /**
+ * Gets the block that was hit, if it was a block that was hit
+ *
+ * @return hit block or else {@code null}
+ */
+ public Block getHitBlock() {
+ return this.hitBlock;
+ }
+
+ /**
+ * Gets the block face that was hit, if it was a block that was hit
+ *
+ * @return hit face or else {@code null}
+ */
+ public BlockFace getHitBlockFace() {
+ return this.hitFace;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return this.cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel) {
+ this.cancelled = cancel;
}
+ // Tsunami end
}
From 9b5c59a1112f1fb61c243aed208a30fec41918bc Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Sat, 14 Feb 2026 20:49:38 +0100
Subject: [PATCH 11/23] Fix packets potentially being discarded
---
src/main/java/net/minecraft/server/NetworkManager.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java
index bcbe924..12da5d5 100644
--- a/src/main/java/net/minecraft/server/NetworkManager.java
+++ b/src/main/java/net/minecraft/server/NetworkManager.java
@@ -292,7 +292,7 @@ public void b() {
// }
Packet packet;
- while ((packet = this.m.poll()) != null && i-- >= 0) { // Tsunami - poll
+ while (i-- > 0 && (packet = this.m.poll()) != null) { // Tsunami - poll
//Poseidon Start - Packet Receive Event
if (firePacketEvents && this.p instanceof NetServerHandler) {
PlayerReceivePacketEvent event = new PlayerReceivePacketEvent(((NetServerHandler) this.p).player.name, packet);
From 3215e42b1a3a815742db3b9132f37ee5f8370e39 Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Wed, 18 Feb 2026 20:47:51 +0100
Subject: [PATCH 12/23] Add Server.isPrimaryThread()
---
.../net/minecraft/server/MinecraftServer.java | 15 ++++++++++++++-
src/main/java/org/bukkit/Bukkit.java | 7 +++++++
src/main/java/org/bukkit/Server.java | 11 +++++++++++
.../java/org/bukkit/craftbukkit/CraftServer.java | 7 ++++++-
4 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index e233224..21ab9da 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -47,6 +47,7 @@ public class MinecraftServer implements Runnable, ICommandListener {
public static Logger log = Logger.getLogger("Minecraft");
private static final long NANOS_PER_TICK = 50_000_000; // Tsunami
public static HashMap trackerList = new HashMap();
+ private Thread primaryThread; // Tsunami
public NetworkListenThread networkListenThread;
public PropertyManager propertyManager;
// public WorldServer[] worldServer; // CraftBukkit - removed!
@@ -90,6 +91,12 @@ public MinecraftServer(OptionSet options) { // CraftBukkit - adds argument Optio
this.options = options;
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
// CraftBukkit end
+
+ // Tsunami - keep reference to primary thread
+ Thread primaryThread = new ThreadServerApplication("Server thread", this);
+ this.primaryThread = primaryThread;
+ primaryThread.start();
+ // Tsunami end
}
private boolean init() throws UnknownHostException { // CraftBukkit - added throws UnknownHostException
@@ -752,12 +759,18 @@ public static void main(final OptionSet options) { // CraftBukkit - replaces mai
// CraftBukkit - remove gui
- (new ThreadServerApplication("Server thread", minecraftserver)).start();
+ //(new ThreadServerApplication("Server thread", minecraftserver)).start(); // Tsunami - moved to MinecraftServer constructor
} catch (Exception exception) {
log.log(Level.SEVERE, "Failed to start the minecraft server", exception);
}
}
+ // Tsunami start
+ public boolean isPrimaryThread() {
+ return Thread.currentThread() == this.primaryThread;
+ }
+ // Tsunami end
+
public File a(String s) {
return new File(s);
}
diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
index ca98660..4412e15 100644
--- a/src/main/java/org/bukkit/Bukkit.java
+++ b/src/main/java/org/bukkit/Bukkit.java
@@ -268,4 +268,11 @@ public static Set getWhitelistedPlayers() {
public static void reloadWhitelist() {
server.reloadWhitelist();
}
+
+ // Tsunami start
+ public static boolean isPrimaryThread() {
+ return server.isPrimaryThread();
+ }
+ // Tsunami end
+
}
diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
index 963dc0e..c1c3e05 100644
--- a/src/main/java/org/bukkit/Server.java
+++ b/src/main/java/org/bukkit/Server.java
@@ -479,4 +479,15 @@ public interface Server extends PluginMessageRecipient { // Tsunami - extends Pl
*/
public Set getBannedPlayers();
+ // Tsunami start
+ /**
+ * Checks the current thread against the expected primary thread for the
+ * server.
+ *
+ * @return true if the current thread matches the expected primary thread,
+ * false otherwise
+ */
+ public boolean isPrimaryThread();
+ // Tsunami end
+
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index c6e9dd5..cc47a88 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -74,7 +74,6 @@ public final class CraftServer implements Server {
private final Configuration configuration;
private final Yaml yaml = new Yaml(new SafeConstructor());
private boolean shuttingdown = false;
- private final List hiddenCommands = new ArrayList<>(); //Project Poseidon - Create variable
public CraftServer(MinecraftServer console, ServerConfigurationManager server) {
this.console = console;
@@ -888,6 +887,12 @@ public boolean isShuttingdown() {
return shuttingdown;
}
+ // Tsunami start
+ public boolean isPrimaryThread() {
+ return console.isPrimaryThread();
+ }
+ // Tsunami end
+
public void setShuttingdown(boolean shuttingdown) {
this.shuttingdown = shuttingdown;
}
From 13054faa8b119a7e8413fe684d42783f666c15bb Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Thu, 19 Feb 2026 15:35:01 +0100
Subject: [PATCH 13/23] Abstract connection ticking
---
.../java/net/minecraft/server/NetHandler.java | 10 +++
.../net/minecraft/server/NetLoginHandler.java | 13 +++-
.../minecraft/server/NetServerHandler.java | 12 +++
.../minecraft/server/NetworkListenThread.java | 76 ++++++-------------
4 files changed, 59 insertions(+), 52 deletions(-)
diff --git a/src/main/java/net/minecraft/server/NetHandler.java b/src/main/java/net/minecraft/server/NetHandler.java
index 18b9cf2..20460f8 100644
--- a/src/main/java/net/minecraft/server/NetHandler.java
+++ b/src/main/java/net/minecraft/server/NetHandler.java
@@ -6,6 +6,16 @@ public NetHandler() {}
public abstract boolean c();
+ // Tsunami start - rewrite networking code
+ public abstract void a();
+
+ public abstract boolean disconnected();
+
+ public abstract void disconnect(String s);
+
+ public abstract NetworkManager getNetManager();
+ // Tsunami end
+
public void a(Packet51MapChunk packet51mapchunk) {}
public void a(Packet packet) {}
diff --git a/src/main/java/net/minecraft/server/NetLoginHandler.java b/src/main/java/net/minecraft/server/NetLoginHandler.java
index 890264a..cd82128 100644
--- a/src/main/java/net/minecraft/server/NetLoginHandler.java
+++ b/src/main/java/net/minecraft/server/NetLoginHandler.java
@@ -74,6 +74,12 @@ public void a() {
}
}
+ // Tsunami start
+ public boolean disconnected() {
+ return this.c;
+ }
+ // Tsunami end
+
public void disconnect(String s) {
try {
a.info("Disconnecting " + this.b() + ": " + s);
@@ -86,6 +92,12 @@ public void disconnect(String s) {
}
}
+ // Tsunami start
+ public NetworkManager getNetManager() {
+ return this.networkManager;
+ }
+ // Tsunami end
+
public void a(Packet2Handshake packet2handshake) {
if (this.server.onlineMode) {
this.serverId = Long.toHexString(d.nextLong());
@@ -246,7 +258,6 @@ private void doPongResponse(long time) throws IOException {
this.networkManager.d();
this.networkManager.socket.close();
- this.server.networkListenThread.b(this);
this.c = true;
}
// Tsunami end
diff --git a/src/main/java/net/minecraft/server/NetServerHandler.java b/src/main/java/net/minecraft/server/NetServerHandler.java
index 384b02f..cf330cc 100644
--- a/src/main/java/net/minecraft/server/NetServerHandler.java
+++ b/src/main/java/net/minecraft/server/NetServerHandler.java
@@ -148,6 +148,12 @@ public void a() {
// Tsunami end
}
+ // Tsunami start
+ public boolean disconnected() {
+ return this.disconnected;
+ }
+ // Tsunami end
+
public void disconnect(String s) {
if (disconnected) return; // Poseidon: Kick/Disconnect spam fix
@@ -181,6 +187,12 @@ public void disconnect(String s) {
this.disconnected = true;
}
+ // Tsunami start
+ public NetworkManager getNetManager() {
+ return this.networkManager;
+ }
+ // Tsunami end
+
public void a(Packet27 packet27) {
// poseidon
PacketReceivedEvent event = new PacketReceivedEvent(server.getPlayer(player), packet27);
diff --git a/src/main/java/net/minecraft/server/NetworkListenThread.java b/src/main/java/net/minecraft/server/NetworkListenThread.java
index 3e16232..7507414 100644
--- a/src/main/java/net/minecraft/server/NetworkListenThread.java
+++ b/src/main/java/net/minecraft/server/NetworkListenThread.java
@@ -3,9 +3,8 @@
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -16,8 +15,11 @@ public class NetworkListenThread {
private Thread e;
public volatile boolean b = false;
private int f = 0;
- private List g = Collections.synchronizedList(new ArrayList()); // Tsunami - synchronized list
- private ArrayList h = new ArrayList();
+ // Tsunami start - rewrite networking code
+ //private List g = new ArrayList();
+ //private ArrayList h = new ArrayList();
+ private final List connections = new CopyOnWriteArrayList<>();
+ // Tsunami end
public MinecraftServer c;
public NetworkListenThread(MinecraftServer minecraftserver, InetAddress inetaddress, int i) throws IOException {
@@ -29,68 +31,40 @@ public NetworkListenThread(MinecraftServer minecraftserver, InetAddress inetaddr
this.e.start();
}
- // Tsunami start
- public void b(NetLoginHandler netloginhandler) {
- this.g.remove(netloginhandler);
- }
- // Tsunami end
-
public void a(NetServerHandler netserverhandler) {
- this.h.add(netserverhandler);
+ addConnection(netserverhandler); // Tsunami
}
private void a(NetLoginHandler netloginhandler) {
- if (netloginhandler == null) {
- throw new IllegalArgumentException("Got null pendingconnection!");
+ addConnection(netloginhandler); // Tsunami
+ }
+
+ // Tsunami start - rewrite networking code
+ public void addConnection(NetHandler netHandler) {
+ if (netHandler == null) {
+ throw new IllegalArgumentException("Got null connection!");
} else {
- this.g.add(netloginhandler);
+ this.connections.add(netHandler);
}
}
+ // Tsunami end
public void a() {
- int i;
-
- synchronized (this.g) { // Tsunami - wrap in synchronized block
- for (i = 0; i < this.g.size(); ++i) {
- NetLoginHandler netloginhandler = (NetLoginHandler) this.g.get(i);
-
- try {
- netloginhandler.a();
- } catch (Exception exception) {
- if (netloginhandler == null) {
- a.log(Level.WARNING, "Looks like someone tried to crash the server, stopped their attempt.");
- this.g.remove(i);
- return;
- } else {
- netloginhandler.disconnect("Internal server error");
- a.log(Level.WARNING, "Failed to handle packet: " + exception, exception);
- }
- }
-
- if (netloginhandler.c) {
- this.g.remove(i--);
- }
-
- netloginhandler.networkManager.a();
- }
- }
-
- for (i = 0; i < this.h.size(); ++i) {
- NetServerHandler netserverhandler = (NetServerHandler) this.h.get(i);
-
+ // Tsunami start - rewrite networking code
+ for (NetHandler netHandler : this.connections) {
try {
- netserverhandler.a();
- } catch (Exception exception1) {
- a.log(Level.WARNING, "Failed to handle packet: " + exception1, exception1);
- netserverhandler.disconnect("Internal server error");
+ netHandler.a();
+ } catch (Exception e) {
+ a.log(Level.WARNING, "Failed to tick connection: " + e, e);
}
- if (netserverhandler.disconnected) {
- this.h.remove(i--);
+ if (netHandler.disconnected()) {
+ this.connections.remove(netHandler);
}
- netserverhandler.networkManager.a();
+ netHandler.getNetManager().a();
}
+ // Tsunami end
}
static ServerSocket a(NetworkListenThread networklistenthread) {
From bd01fe6edd22af0019a8e7052b37338f317680d1 Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Thu, 19 Feb 2026 22:03:11 +0100
Subject: [PATCH 14/23] Allow for certain packets to be handled asynchronously
---
.../net/minecraft/server/MinecraftServer.java | 20 +++++
.../net/minecraft/server/NetLoginHandler.java | 12 ++-
.../minecraft/server/NetServerHandler.java | 31 +++++++
.../minecraft/server/NetworkListenThread.java | 3 +-
.../net/minecraft/server/NetworkManager.java | 82 ++++++++++---------
.../betamc/tsunami/network/NetworkUtil.java | 25 ++++++
.../network/PacketScheduledException.java | 6 ++
7 files changed, 139 insertions(+), 40 deletions(-)
create mode 100644 src/main/java/org/betamc/tsunami/network/NetworkUtil.java
create mode 100644 src/main/java/org/betamc/tsunami/network/PacketScheduledException.java
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 21ab9da..c0c60df 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -37,6 +37,7 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.LockSupport;
import java.util.logging.Level;
@@ -62,6 +63,7 @@ public class MinecraftServer implements Runnable, ICommandListener {
private List r = new ArrayList();
private List s = Collections.synchronizedList(new ArrayList());
private final Queue remoteCommands = new LinkedBlockingQueue<>(); // Tsunami
+ private final Queue taskQueue = new ConcurrentLinkedQueue<>(); // Tsunami
// public EntityTracker[] tracker = new EntityTracker[2]; // CraftBukkit - removed!
public boolean onlineMode;
public boolean spawnAnimals;
@@ -605,6 +607,18 @@ private void h() {
Vec3D.a();
++this.ticks;
+ // Tsunami start
+ Runnable task;
+ int count = this.taskQueue.size();
+ while (count-- > 0 && (task = this.taskQueue.poll()) != null) {
+ try {
+ task.run();
+ } catch (Throwable t) {
+ log.log(Level.SEVERE, "Error executing queued task", t);
+ }
+ }
+ // Tsunami end
+
((CraftScheduler) this.server.getScheduler()).mainThreadHeartbeat(this.ticks); // CraftBukkit
//Project Poseidon Start - Tick Update
@@ -695,6 +709,12 @@ private void h() {
}
}
+ // Tsunami start
+ public void scheduleTask(Runnable task) {
+ this.taskQueue.add(task);
+ }
+ // Tsunami end
+
public void issueCommand(String s, ICommandListener icommandlistener) {
this.s.add(new ServerCommand(s, icommandlistener));
}
diff --git a/src/main/java/net/minecraft/server/NetLoginHandler.java b/src/main/java/net/minecraft/server/NetLoginHandler.java
index cd82128..282fc34 100644
--- a/src/main/java/net/minecraft/server/NetLoginHandler.java
+++ b/src/main/java/net/minecraft/server/NetLoginHandler.java
@@ -6,6 +6,7 @@
import com.legacyminecraft.poseidon.PoseidonConfig;
import com.projectposeidon.johnymuffin.LoginProcessHandler;
import org.betamc.tsunami.Tsunami;
+import org.betamc.tsunami.network.NetworkUtil;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.craftbukkit.CraftServer;
@@ -99,11 +100,16 @@ public NetworkManager getNetManager() {
// Tsunami end
public void a(Packet2Handshake packet2handshake) {
+ // Tsunami start
+ NetworkManager netManager = this.networkManager;
+ if (netManager == null) return;
+ // Tsunami end
+
if (this.server.onlineMode) {
this.serverId = Long.toHexString(d.nextLong());
- this.networkManager.queue(new Packet2Handshake(this.serverId));
+ netManager.queue(new Packet2Handshake(this.serverId));
} else {
- this.networkManager.queue(new Packet2Handshake("-"));
+ netManager.queue(new Packet2Handshake("-"));
}
}
@@ -112,6 +118,8 @@ public void a(Packet0KeepAlive packet0KeepAlive) {
}
public void a(Packet1Login packet1login) {
+ NetworkUtil.ensureOnMainThread(packet1login, this, this.server); // Tsunami
+
if (receivedLoginPacket) {
this.disconnect("Multiple login packets received.");
return;
diff --git a/src/main/java/net/minecraft/server/NetServerHandler.java b/src/main/java/net/minecraft/server/NetServerHandler.java
index cf330cc..f81a6f0 100644
--- a/src/main/java/net/minecraft/server/NetServerHandler.java
+++ b/src/main/java/net/minecraft/server/NetServerHandler.java
@@ -5,6 +5,7 @@
import com.projectposeidon.ConnectionType;
import com.legacyminecraft.poseidon.PoseidonConfig;
import org.betamc.tsunami.Tsunami;
+import org.betamc.tsunami.network.NetworkUtil;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
@@ -194,6 +195,8 @@ public NetworkManager getNetManager() {
// Tsunami end
public void a(Packet27 packet27) {
+ NetworkUtil.ensureOnMainThread(packet27, this, this.minecraftServer); // Tsunami
+
// poseidon
PacketReceivedEvent event = new PacketReceivedEvent(server.getPlayer(player), packet27);
server.getPluginManager().callEvent(event);
@@ -204,6 +207,8 @@ public void a(Packet27 packet27) {
}
public void a(Packet10Flying packet10flying) {
+ NetworkUtil.ensureOnMainThread(packet10flying, this, this.minecraftServer); // Tsunami
+
// poseidon
PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet10flying);
server.getPluginManager().callEvent(pevent);
@@ -533,6 +538,8 @@ public void teleport(Location dest) {
}
public void a(Packet14BlockDig packet14blockdig) {
+ NetworkUtil.ensureOnMainThread(packet14blockdig, this, this.minecraftServer); // Tsunami
+
// poseidon
PacketReceivedEvent event = new PacketReceivedEvent(server.getPlayer(player), packet14blockdig);
server.getPluginManager().callEvent(event);
@@ -623,6 +630,8 @@ public void a(Packet14BlockDig packet14blockdig) {
}
public void a(Packet15Place packet15place) {
+ NetworkUtil.ensureOnMainThread(packet15place, this, this.minecraftServer); // Tsunami
+
// poseidon
PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet15place);
server.getPluginManager().callEvent(pevent);
@@ -808,6 +817,8 @@ public void sendPacket(Packet packet) {
}
public void a(Packet16BlockItemSwitch packet16blockitemswitch) {
+ NetworkUtil.ensureOnMainThread(packet16blockitemswitch, this, this.minecraftServer); // Tsunami
+
// poseidon
PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet16blockitemswitch);
server.getPluginManager().callEvent(pevent);
@@ -830,6 +841,8 @@ public void a(Packet16BlockItemSwitch packet16blockitemswitch) {
}
public void a(Packet3Chat packet3chat) {
+ NetworkUtil.ensureOnMainThread(packet3chat, this, this.minecraftServer); // Tsunami
+
// poseidon
PacketReceivedEvent event = new PacketReceivedEvent(server.getPlayer(player), packet3chat);
server.getPluginManager().callEvent(event);
@@ -955,6 +968,8 @@ private void handleCommand(String s) {
}
public void a(Packet18ArmAnimation packet18armanimation) {
+ NetworkUtil.ensureOnMainThread(packet18armanimation, this, this.minecraftServer); // Tsunami
+
// poseidon
PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet18armanimation);
server.getPluginManager().callEvent(pevent);
@@ -999,6 +1014,8 @@ public void a(Packet18ArmAnimation packet18armanimation) {
}
public void a(Packet19EntityAction packet19entityaction) {
+ NetworkUtil.ensureOnMainThread(packet19entityaction, this, this.minecraftServer); // Tsunami
+
// poseidon
PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet19entityaction);
server.getPluginManager().callEvent(pevent);
@@ -1055,6 +1072,8 @@ public String getName() {
}
public void a(Packet7UseEntity packet7useentity) {
+ NetworkUtil.ensureOnMainThread(packet7useentity, this, this.minecraftServer); // Tsunami
+
// poseidon
PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet7useentity);
server.getPluginManager().callEvent(pevent);
@@ -1102,6 +1121,8 @@ public void a(Packet7UseEntity packet7useentity) {
}
public void a(Packet9Respawn packet9respawn) {
+ NetworkUtil.ensureOnMainThread(packet9respawn, this, this.minecraftServer); // Tsunami
+
// poseidon
PacketReceivedEvent event = new PacketReceivedEvent(server.getPlayer(player), packet9respawn);
server.getPluginManager().callEvent(event);
@@ -1116,12 +1137,16 @@ public void a(Packet9Respawn packet9respawn) {
}
public void a(Packet101CloseWindow packet101closewindow) {
+ NetworkUtil.ensureOnMainThread(packet101closewindow, this, this.minecraftServer); // Tsunami
+
if (this.player.dead) return; // CraftBukkit
this.player.A();
}
public void a(Packet102WindowClick packet102windowclick) {
+ NetworkUtil.ensureOnMainThread(packet102windowclick, this, this.minecraftServer); // Tsunami
+
// poseidon
PacketReceivedEvent event = new PacketReceivedEvent(server.getPlayer(player), packet102windowclick);
server.getPluginManager().callEvent(event);
@@ -1161,6 +1186,8 @@ public void a(Packet106Transaction packet106transaction) {
this.ping = (this.ping * 3 + time) / 4;
this.pingTimestamp = -1;
return;
+ } else {
+ NetworkUtil.ensureOnMainThread(packet106transaction, this, this.minecraftServer);
}
// Tsunami end
@@ -1180,6 +1207,8 @@ public void a(Packet106Transaction packet106transaction) {
}
public void a(Packet130UpdateSign packet130updatesign) {
+ NetworkUtil.ensureOnMainThread(packet130updatesign, this, this.minecraftServer); // Tsunami
+
// poseidon
PacketReceivedEvent pevent = new PacketReceivedEvent(server.getPlayer(player), packet130updatesign);
server.getPluginManager().callEvent(pevent);
@@ -1253,6 +1282,8 @@ public void a(Packet130UpdateSign packet130updatesign) {
// Tsunami start - backport plugin messaging
public void a(Packet250PluginMessage packet250pluginmessage) {
+ NetworkUtil.ensureOnMainThread(packet250pluginmessage, this, this.minecraftServer);
+
if (packet250pluginmessage.channel.equals("REGISTER")) {
String channels = new String(packet250pluginmessage.message, StandardCharsets.UTF_8);
for (String channel : channels.split("\0")) {
diff --git a/src/main/java/net/minecraft/server/NetworkListenThread.java b/src/main/java/net/minecraft/server/NetworkListenThread.java
index 7507414..1fbd8f7 100644
--- a/src/main/java/net/minecraft/server/NetworkListenThread.java
+++ b/src/main/java/net/minecraft/server/NetworkListenThread.java
@@ -55,7 +55,8 @@ public void a() {
try {
netHandler.a();
} catch (Exception e) {
- a.log(Level.WARNING, "Failed to tick connection: " + e, e);
+ a.log(Level.WARNING, "Failed to tick connection", e);
+ netHandler.disconnect("Internal server error");
}
if (netHandler.disconnected()) {
diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java
index 12da5d5..bb879d1 100644
--- a/src/main/java/net/minecraft/server/NetworkManager.java
+++ b/src/main/java/net/minecraft/server/NetworkManager.java
@@ -3,6 +3,7 @@
import com.legacyminecraft.poseidon.PoseidonConfig;
import com.legacyminecraft.poseidon.event.PlayerReceivePacketEvent;
import org.betamc.tsunami.Tsunami;
+import org.betamc.tsunami.network.PacketScheduledException;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
@@ -14,6 +15,8 @@
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
public class NetworkManager {
@@ -26,7 +29,9 @@ public class NetworkManager {
public DataInputStream input; // Tsunami - private -> public
public DataOutputStream output; // Tsunami - private -> public
private boolean l = true;
- private Queue m = new ConcurrentLinkedQueue<>(); // Tsunami - ArrayList -> ConcurrentLinkedQueue
+ //private List m = Collections.synchronizedList(new ArrayList()); // Tsunami
+ private final AtomicLong readPackets = new AtomicLong(0L); // Tsunami
+ private long lastReadPackets = 0L; // Tsunami
private Queue highPriorityQueue = new ConcurrentLinkedQueue<>(); // Tsunami - ArrayList -> ConcurrentLinkedQueue
private Queue lowPriorityQueue = new ConcurrentLinkedQueue<>(); // Tsunami - ArrayList -> ConcurrentLinkedQueue
private NetHandler p;
@@ -202,8 +207,9 @@ private boolean g() {
int i = packet.b();
aint[i] += packet.a() + 1;
- this.m.add(packet);
flag = true;
+
+ handleReadPacket(packet); // Tsunami
} else {
this.a("disconnect.endOfStream", new Object[0]);
}
@@ -218,6 +224,29 @@ private boolean g() {
}
}
+ // Tsunami start - rewrite networking code
+ private void handleReadPacket(Packet packet) {
+ this.readPackets.incrementAndGet();
+
+ NetHandler netHandler = this.p;
+ if (this.firePacketEvents && netHandler instanceof NetServerHandler) {
+ PlayerReceivePacketEvent event = new PlayerReceivePacketEvent(((NetServerHandler) netHandler).player.name, packet);
+ Bukkit.getPluginManager().callEvent(event);
+ if (event.isCancelled()) return;
+ packet = event.getPacket();
+ }
+
+ if (packet == null) return;
+ try {
+ packet.a(netHandler);
+ } catch (PacketScheduledException e) {
+ } catch (Exception e) {
+ MinecraftServer.log.log(Level.WARNING, "Failed to handle packet", e);
+ netHandler.disconnect("Internal server error");
+ }
+ }
+ // Tsunami end
+
private void a(Exception exception) {
exception.printStackTrace();
this.a("disconnect.genericReason", new Object[]{"Internal exception: " + exception.toString()});
@@ -255,12 +284,17 @@ public void a(String s, Object... aobject) {
}
public void b() {
- boolean fast = PoseidonConfig.getInstance().getBoolean("settings.faster-packets.enabled", true);
- if (this.x.get() > (fast ? 2097152 : 1048576)) {
+ // Tsunami start
+ long readPackets = this.readPackets.get();
+ long newPackets = readPackets - this.lastReadPackets;
+ this.lastReadPackets = readPackets;
+ // Tsunami end
+
+ if (this.x.get() > 2097152) { // Tsunami
this.a("disconnect.overflow", new Object[0]);
}
- if (this.m.isEmpty()) {
+ if (newPackets == 0) { // Tsunami
if (this.w++ == 1200) {
this.a("disconnect.timeout", new Object[0]);
}
@@ -268,11 +302,9 @@ public void b() {
this.w = 0;
}
- int i = (fast ? 1000 : 100);
-
- //Poseidon - Packet spam detection
+ // Poseidon start - Packet spam detection
if (spamDetection) {
- if (this.m.size() > threshold) {
+ if (newPackets > threshold) { // Tsunami
String playerUsername = "Unknown";
if (this.p instanceof NetServerHandler) {
playerUsername = ((NetServerHandler) this.p).player.name;
@@ -280,39 +312,15 @@ public void b() {
} else {
this.a("disconnect.spam", new Object[0]);
}
- System.out.println("[Poseidon] Player " + playerUsername + " has been kicked for packet spamming. The queue size was " + this.m.size() + " and the threshold was " + threshold + ".");
+ System.out.println("[Poseidon] Player " + playerUsername + " has been kicked for packet spamming. The queue size was " + newPackets + " and the threshold was " + threshold + ".");
}
}
+ // Poseidon end
-// if(this.m.size() > 1000) {
-// String playerUsername = "Unknown";
-// if (this.p instanceof NetServerHandler) {
-// System.out.println("The packet queue size is " + this.m.size() + " for player " + ((NetServerHandler) this.p).player.name + ".");
-// }
-// }
-
- Packet packet;
- while (i-- > 0 && (packet = this.m.poll()) != null) { // Tsunami - poll
- //Poseidon Start - Packet Receive Event
- if (firePacketEvents && this.p instanceof NetServerHandler) {
- PlayerReceivePacketEvent event = new PlayerReceivePacketEvent(((NetServerHandler) this.p).player.name, packet);
- Bukkit.getPluginManager().callEvent(event);
- packet = event.getPacket();
- if (!event.isCancelled()) {
- packet.a(this.p);
- }
-
- } else {
- packet.a(this.p);
- }
-
- //Poseidon End
-
-// packet.a(this.p);
- }
+ // Tsunami - moved packet handling to handleReadPacket()
this.a();
- if (this.t && this.m.isEmpty()) {
+ if (this.t && newPackets == 0) { // Tsunami
this.p.a(this.u, this.v);
}
}
diff --git a/src/main/java/org/betamc/tsunami/network/NetworkUtil.java b/src/main/java/org/betamc/tsunami/network/NetworkUtil.java
new file mode 100644
index 0000000..5e3c7ab
--- /dev/null
+++ b/src/main/java/org/betamc/tsunami/network/NetworkUtil.java
@@ -0,0 +1,25 @@
+package org.betamc.tsunami.network;
+
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.NetHandler;
+import net.minecraft.server.Packet;
+
+import java.util.logging.Level;
+
+public class NetworkUtil {
+
+ public static void ensureOnMainThread(Packet packet, NetHandler netHandler, MinecraftServer server) {
+ if (!server.isPrimaryThread()) {
+ server.scheduleTask(() -> {
+ try {
+ packet.a(netHandler);
+ } catch (Exception e) {
+ MinecraftServer.log.log(Level.WARNING, "Failed to handle packet", e);
+ netHandler.disconnect("Internal server error");
+ }
+ });
+ throw PacketScheduledException.INSTANCE;
+ }
+ }
+
+}
diff --git a/src/main/java/org/betamc/tsunami/network/PacketScheduledException.java b/src/main/java/org/betamc/tsunami/network/PacketScheduledException.java
new file mode 100644
index 0000000..cf7a2e9
--- /dev/null
+++ b/src/main/java/org/betamc/tsunami/network/PacketScheduledException.java
@@ -0,0 +1,6 @@
+package org.betamc.tsunami.network;
+
+public final class PacketScheduledException extends RuntimeException {
+
+ public static final PacketScheduledException INSTANCE = new PacketScheduledException();
+}
From f98dffab22a2e5307627a24fb9d6b288c79a3e92 Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Thu, 26 Feb 2026 08:54:53 +0100
Subject: [PATCH 15/23] Add throttling for chunk packets
---
src/main/java/net/minecraft/server/EntityPlayer.java | 3 ++-
src/main/java/org/betamc/tsunami/TsunamiConfig.java | 5 +++++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
index 15af116..9d6b78f 100644
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
@@ -6,6 +6,7 @@
import com.projectposeidon.api.PoseidonUUID;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import org.betamc.tsunami.Tsunami;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
@@ -248,7 +249,7 @@ public void a(boolean flag) {
if (flag && !this.chunkCoordIntPairQueue.isEmpty()) {
// Tsunami start - improve chunk sending
WorldServer worldserver = this.getWorldServer();
- while (!this.chunkCoordIntPairQueue.isEmpty()) {
+ for (int count = 0; !this.chunkCoordIntPairQueue.isEmpty() && count < Tsunami.config().networking().maxChunkPacketsPerTick(); count++) {
long coordPair = this.chunkCoordIntPairQueue.removeLong(0);
Chunk chunk = worldserver.chunkProviderServer.getChunkAt(LongHash.msw(coordPair), LongHash.lsw(coordPair));
diff --git a/src/main/java/org/betamc/tsunami/TsunamiConfig.java b/src/main/java/org/betamc/tsunami/TsunamiConfig.java
index 16343b9..ddbc59e 100644
--- a/src/main/java/org/betamc/tsunami/TsunamiConfig.java
+++ b/src/main/java/org/betamc/tsunami/TsunamiConfig.java
@@ -105,10 +105,15 @@ public boolean logUnknownCommands() {
@ConfigSerializable
public static class Networking {
private int chunkPacketCompressionLevel = 6;
+ private int maxChunkPacketsPerTick = 10;
public int chunkPacketCompressionLevel() {
return Math.min(Math.max(chunkPacketCompressionLevel, -1), 9);
}
+
+ public int maxChunkPacketsPerTick() {
+ return Math.max(maxChunkPacketsPerTick, 1);
+ }
}
@ConfigSerializable
From 6823b7638c3d6507c043b03533c43b539ae0c810 Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Thu, 26 Feb 2026 23:54:53 +0100
Subject: [PATCH 16/23] Handle packets between ticks as well
---
.../net/minecraft/server/MinecraftServer.java | 51 +++++++++----------
.../net/minecraft/server/NetLoginHandler.java | 13 ++---
.../minecraft/server/NetworkListenThread.java | 2 +-
.../net/minecraft/server/NetworkManager.java | 7 ++-
.../betamc/tsunami/network/NetworkUtil.java | 2 +-
5 files changed, 36 insertions(+), 39 deletions(-)
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index c0c60df..89da65f 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -39,7 +39,6 @@
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.locks.LockSupport;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -502,38 +501,36 @@ public void a() {
public void run() {
try {
if (this.init()) {
- long i = System.nanoTime(); // Tsunami - System.nanoTime()
+ // Tsunami start - improve tick loop
+ long nextTickTime = System.nanoTime();
- for (long j = 0L; this.isRunning;) {
- long k = System.nanoTime(); // Tsunami - System.nanoTime()
- long l = k - i;
-
- if (l > NANOS_PER_TICK * 40L) {
- // Tsunami - improve message
- log.warning("Can't keep up! Did the system time change, or is the server overloaded? Running " + l / 1_000_000 + "ms behind, skipping " + l / NANOS_PER_TICK + " tick(s)");
- l = NANOS_PER_TICK * 40L;
- }
+ while (this.isRunning) {
+ long behind = System.nanoTime() - nextTickTime;
- if (l < 0L) {
- log.warning("Time ran backwards! Did the system time change?");
- l = 0L;
+ if (behind > NANOS_PER_TICK * 40L) {
+ long skipTicks = behind / NANOS_PER_TICK;
+ log.warning("Can't keep up! Did the system time change, or is the server overloaded? Running " + behind / 1_000_000 + "ms behind, skipping " + skipTicks + " tick(s)");
+ nextTickTime += skipTicks * NANOS_PER_TICK;
+ } else {
+ nextTickTime += NANOS_PER_TICK;
}
- j += l;
- i = k;
- if (this.worlds.get(0).everyoneDeeplySleeping()) { // CraftBukkit
- this.h();
- j = 0L;
- } else {
- while (j > NANOS_PER_TICK) { // Tsunami
- MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit
- getWatchdog().tickUpdate(); // Project Poseidon
- j -= NANOS_PER_TICK; // Tsunami
- this.h();
+ MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit
+ getWatchdog().tickUpdate(); // Project Poseidon
+ this.h();
+
+ Runnable task;
+ while (System.nanoTime() < nextTickTime) {
+ task = taskQueue.poll();
+ if (task != null) {
+ try {
+ task.run();
+ } catch (Throwable t) {
+ log.log(Level.SEVERE, "Error executing queued task", t);
+ }
}
}
-
- LockSupport.parkNanos(NANOS_PER_TICK - j); // Tsunami - use LockSupport.parkNanos() instead of Thread.sleep()
+ // Tsunami end
}
} else {
while (this.isRunning) {
diff --git a/src/main/java/net/minecraft/server/NetLoginHandler.java b/src/main/java/net/minecraft/server/NetLoginHandler.java
index 282fc34..be473be 100644
--- a/src/main/java/net/minecraft/server/NetLoginHandler.java
+++ b/src/main/java/net/minecraft/server/NetLoginHandler.java
@@ -28,7 +28,7 @@ public class NetLoginHandler extends NetHandler {
public static Logger a = Logger.getLogger("Minecraft");
private static Random d = new Random();
- public NetworkManager networkManager;
+ public final NetworkManager networkManager; // Tsunami - final
public boolean c = false;
private MinecraftServer server;
private int f = 0;
@@ -52,8 +52,8 @@ public NetLoginHandler(MinecraftServer minecraftserver, Socket socket, String s)
this.server = minecraftserver;
this.networkManager = new NetworkManager(socket, s, this);
this.networkManager.f = 0;
-
this.msgKickShutdown = PoseidonConfig.getInstance().getConfigString("message.kick.shutdown");
+ this.networkManager.startThreads(); // Tsunami
}
// CraftBukkit start
@@ -100,16 +100,11 @@ public NetworkManager getNetManager() {
// Tsunami end
public void a(Packet2Handshake packet2handshake) {
- // Tsunami start
- NetworkManager netManager = this.networkManager;
- if (netManager == null) return;
- // Tsunami end
-
if (this.server.onlineMode) {
this.serverId = Long.toHexString(d.nextLong());
- netManager.queue(new Packet2Handshake(this.serverId));
+ this.networkManager.queue(new Packet2Handshake(this.serverId));
} else {
- netManager.queue(new Packet2Handshake("-"));
+ this.networkManager.queue(new Packet2Handshake("-"));
}
}
diff --git a/src/main/java/net/minecraft/server/NetworkListenThread.java b/src/main/java/net/minecraft/server/NetworkListenThread.java
index 1fbd8f7..b0dd731 100644
--- a/src/main/java/net/minecraft/server/NetworkListenThread.java
+++ b/src/main/java/net/minecraft/server/NetworkListenThread.java
@@ -55,7 +55,7 @@ public void a() {
try {
netHandler.a();
} catch (Exception e) {
- a.log(Level.WARNING, "Failed to tick connection", e);
+ a.log(Level.WARNING, "Failed to tick connection: ", e);
netHandler.disconnect("Internal server error");
}
diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java
index bb879d1..630308d 100644
--- a/src/main/java/net/minecraft/server/NetworkManager.java
+++ b/src/main/java/net/minecraft/server/NetworkManager.java
@@ -90,9 +90,14 @@ public NetworkManager(Socket socket, String s, NetHandler nethandler) {
// CraftBukkit end */
this.s = new NetworkReaderThread(this, s + " read thread");
this.r = new NetworkWriterThread(this, s + " write thread");
+ }
+
+ // Tsunami start - rewrite networking code
+ void startThreads() {
this.s.start();
this.r.start();
}
+ // Tsunami end
//Project Poseidon Start
public void setSocketAddress(SocketAddress socketAddress) {
@@ -241,7 +246,7 @@ private void handleReadPacket(Packet packet) {
packet.a(netHandler);
} catch (PacketScheduledException e) {
} catch (Exception e) {
- MinecraftServer.log.log(Level.WARNING, "Failed to handle packet", e);
+ MinecraftServer.log.log(Level.WARNING, "Failed to handle packet: ", e);
netHandler.disconnect("Internal server error");
}
}
diff --git a/src/main/java/org/betamc/tsunami/network/NetworkUtil.java b/src/main/java/org/betamc/tsunami/network/NetworkUtil.java
index 5e3c7ab..7bbe874 100644
--- a/src/main/java/org/betamc/tsunami/network/NetworkUtil.java
+++ b/src/main/java/org/betamc/tsunami/network/NetworkUtil.java
@@ -14,7 +14,7 @@ public static void ensureOnMainThread(Packet packet, NetHandler netHandler, Mine
try {
packet.a(netHandler);
} catch (Exception e) {
- MinecraftServer.log.log(Level.WARNING, "Failed to handle packet", e);
+ MinecraftServer.log.log(Level.WARNING, "Failed to handle packet: ", e);
netHandler.disconnect("Internal server error");
}
});
From 3347f9494509bf3a7101cb4de48b255983d8e8f4 Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Sun, 1 Mar 2026 12:00:52 +0100
Subject: [PATCH 17/23] Initial implementation of PersistentDataContainer API
---
src/main/java/net/minecraft/server/Chunk.java | 2 +
.../net/minecraft/server/ChunkLoader.java | 9 ++
.../java/net/minecraft/server/Entity.java | 8 ++
.../java/net/minecraft/server/NBTTagList.java | 2 +-
.../java/net/minecraft/server/TileEntity.java | 9 ++
.../java/net/minecraft/server/WorldData.java | 9 ++
src/main/java/org/bukkit/Chunk.java | 3 +-
src/main/java/org/bukkit/World.java | 3 +-
src/main/java/org/bukkit/block/Chest.java | 3 +-
.../org/bukkit/block/CreatureSpawner.java | 3 +-
src/main/java/org/bukkit/block/Dispenser.java | 3 +-
src/main/java/org/bukkit/block/Furnace.java | 3 +-
src/main/java/org/bukkit/block/NoteBlock.java | 3 +-
src/main/java/org/bukkit/block/Sign.java | 3 +-
.../org/bukkit/craftbukkit/CraftChunk.java | 15 +++
.../org/bukkit/craftbukkit/CraftWorld.java | 14 +++
.../bukkit/craftbukkit/block/CraftChest.java | 14 +++
.../block/CraftCreatureSpawner.java | 14 +++
.../craftbukkit/block/CraftDispenser.java | 14 +++
.../craftbukkit/block/CraftFurnace.java | 14 +++
.../craftbukkit/block/CraftNoteBlock.java | 14 +++
.../bukkit/craftbukkit/block/CraftSign.java | 14 +++
.../craftbukkit/entity/CraftEntity.java | 14 +++
.../CraftPersistentDataContainer.java | 110 ++++++++++++++++++
.../PersistentDataTypeRegistry.java | 97 +++++++++++++++
.../persistence/PrimitiveToTagAdapter.java | 32 +++++
src/main/java/org/bukkit/entity/Entity.java | 3 +-
.../persistence/ListPersistentDataType.java | 61 ++++++++++
.../persistence/PersistentDataContainer.java | 24 ++++
.../persistence/PersistentDataHolder.java | 8 ++
.../persistence/PersistentDataType.java | 99 ++++++++++++++++
31 files changed, 614 insertions(+), 10 deletions(-)
create mode 100644 src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java
create mode 100644 src/main/java/org/bukkit/craftbukkit/persistence/PersistentDataTypeRegistry.java
create mode 100644 src/main/java/org/bukkit/craftbukkit/persistence/PrimitiveToTagAdapter.java
create mode 100644 src/main/java/org/bukkit/persistence/ListPersistentDataType.java
create mode 100644 src/main/java/org/bukkit/persistence/PersistentDataContainer.java
create mode 100644 src/main/java/org/bukkit/persistence/PersistentDataHolder.java
create mode 100644 src/main/java/org/bukkit/persistence/PersistentDataType.java
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index dcc30f5..6201664 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -1,6 +1,7 @@
package net.minecraft.server;
import com.legacyminecraft.poseidon.PoseidonConfig;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import java.util.*;
@@ -25,6 +26,7 @@ public class Chunk {
public boolean q;
public long r;
private final int[] chunkSections; // Tsunami
+ public final CraftPersistentDataContainer container = new CraftPersistentDataContainer(); // Tsunami
public Chunk(World world, int i, int j) {
this.tileEntities = new HashMap();
diff --git a/src/main/java/net/minecraft/server/ChunkLoader.java b/src/main/java/net/minecraft/server/ChunkLoader.java
index acb3518..38107c7 100644
--- a/src/main/java/net/minecraft/server/ChunkLoader.java
+++ b/src/main/java/net/minecraft/server/ChunkLoader.java
@@ -1,5 +1,7 @@
package net.minecraft.server;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
+
import java.io.*;
import java.util.Iterator;
@@ -155,6 +157,8 @@ public static void a(Chunk chunk, World world, NBTTagCompound nbttagcompound) {
}
nbttagcompound.a("TileEntities", (NBTBase) nbttaglist1);
+
+ nbttagcompound.a(CraftPersistentDataContainer.TAG_KEY, chunk.container.asCompound()); // Tsunami - PersistentDataContainer API
}
public static Chunk a(World world, NBTTagCompound nbttagcompound) {
@@ -210,6 +214,11 @@ public static Chunk a(World world, NBTTagCompound nbttagcompound) {
}
}
+ // Tsunami start - PersistentDataContainer API
+ CraftPersistentDataContainer container = new CraftPersistentDataContainer(nbttagcompound.k(CraftPersistentDataContainer.TAG_KEY));
+ container.copyTo(chunk.container, true);
+ // Tsunami end
+
return chunk;
}
diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
index cab02a9..1d6e1e7 100644
--- a/src/main/java/net/minecraft/server/Entity.java
+++ b/src/main/java/net/minecraft/server/Entity.java
@@ -4,6 +4,7 @@
import org.bukkit.block.BlockFace;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.metadata.NBTMetadataConvert;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.entity.EntityCombustEvent;
@@ -101,6 +102,7 @@ public synchronized void setSeed(long seed) {
public boolean bK;
public boolean airBorne;
public UUID uniqueId = UUID.randomUUID(); // CraftBukkit
+ public final CraftPersistentDataContainer container = new CraftPersistentDataContainer(); // Tsunami
public final Map metadataStore = new HashMap<>(); // Tsunami
public Entity(World world) {
@@ -928,6 +930,7 @@ public void d(NBTTagCompound nbttagcompound) {
nbttagcompound.setLong("UUIDMost", this.uniqueId.getMostSignificantBits());
// CraftBukkit end
this.b(nbttagcompound);
+ nbttagcompound.a(CraftPersistentDataContainer.TAG_KEY, this.container.asCompound()); // Tsunami - PersistentDataContainer API
nbttagcompound.a("CustomMetadata", NBTMetadataConvert.metadataToCompound(metadataStore)); // Tsunami
}
@@ -1015,6 +1018,11 @@ public void e(NBTTagCompound nbttagcompound) {
}
// CraftBukkit end
+ // Tsunami - PersistentDataContainer API
+ CraftPersistentDataContainer container = new CraftPersistentDataContainer(nbttagcompound.k(CraftPersistentDataContainer.TAG_KEY));
+ container.copyTo(this.container, true);
+ // Tsunami end
+
// Tsunami start
NBTTagCompound metadata = nbttagcompound.k("CustomMetadata");
this.metadataStore.putAll(NBTMetadataConvert.compoundToMetadata(metadata));
diff --git a/src/main/java/net/minecraft/server/NBTTagList.java b/src/main/java/net/minecraft/server/NBTTagList.java
index 53fc22f..2e275a8 100644
--- a/src/main/java/net/minecraft/server/NBTTagList.java
+++ b/src/main/java/net/minecraft/server/NBTTagList.java
@@ -8,7 +8,7 @@
public class NBTTagList extends NBTBase {
- private List a = new ArrayList();
+ public List a = new ArrayList(); // Tsunami - private -> public
private byte b;
public NBTTagList() {}
diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java
index 182b53c..4d618d7 100644
--- a/src/main/java/net/minecraft/server/TileEntity.java
+++ b/src/main/java/net/minecraft/server/TileEntity.java
@@ -1,6 +1,7 @@
package net.minecraft.server;
import org.bukkit.craftbukkit.metadata.NBTMetadataConvert;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.metadata.MetadataValue;
import java.util.HashMap;
@@ -15,6 +16,7 @@ public class TileEntity {
public int y;
public int z;
protected boolean h;
+ public final CraftPersistentDataContainer container = new CraftPersistentDataContainer(); // Tsunami
public final Map metadataStore = new HashMap<>(); // Tsunami
public TileEntity() {}
@@ -32,6 +34,12 @@ public void a(NBTTagCompound nbttagcompound) {
this.x = nbttagcompound.e("x");
this.y = nbttagcompound.e("y");
this.z = nbttagcompound.e("z");
+
+ // Tsunami start - PersistentDataContainer API
+ CraftPersistentDataContainer container = new CraftPersistentDataContainer(nbttagcompound.k(CraftPersistentDataContainer.TAG_KEY));
+ container.copyTo(this.container, true);
+ // Tsunami end
+
// Tsunami start
NBTTagCompound metadata = nbttagcompound.k("CustomMetadata");
this.metadataStore.putAll(NBTMetadataConvert.compoundToMetadata(metadata));
@@ -48,6 +56,7 @@ public void b(NBTTagCompound nbttagcompound) {
nbttagcompound.a("x", this.x);
nbttagcompound.a("y", this.y);
nbttagcompound.a("z", this.z);
+ nbttagcompound.a(CraftPersistentDataContainer.TAG_KEY, this.container.asCompound()); // Tsunami - PersistentDataContainer API
nbttagcompound.a("CustomMetadata", NBTMetadataConvert.metadataToCompound(metadataStore)); // Tsunami
}
}
diff --git a/src/main/java/net/minecraft/server/WorldData.java b/src/main/java/net/minecraft/server/WorldData.java
index d6af2b3..b2dc734 100644
--- a/src/main/java/net/minecraft/server/WorldData.java
+++ b/src/main/java/net/minecraft/server/WorldData.java
@@ -1,6 +1,7 @@
package net.minecraft.server;
import org.bukkit.craftbukkit.metadata.NBTMetadataConvert;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.metadata.MetadataValue;
import java.util.HashMap;
@@ -26,6 +27,7 @@ public class WorldData {
private int m;
private boolean n;
private int o;
+ public final CraftPersistentDataContainer container = new CraftPersistentDataContainer(); // Tsunami
public final Map metadataStore = new HashMap<>(); // Tsunami
public WorldData(NBTTagCompound nbttagcompound) {
@@ -48,6 +50,12 @@ public WorldData(NBTTagCompound nbttagcompound) {
this.h = nbttagcompound.k("Player");
this.i = this.h.e("Dimension");
}
+
+ // Tsunami start - PersistentDataContainer API
+ CraftPersistentDataContainer container = new CraftPersistentDataContainer(nbttagcompound.k(CraftPersistentDataContainer.TAG_KEY));
+ container.copyTo(this.container, true);
+ // Tsunami end
+
// Tsunami start
NBTTagCompound metadata = nbttagcompound.k("CustomMetadata");
this.metadataStore.putAll(NBTMetadataConvert.compoundToMetadata(metadata));
@@ -123,6 +131,7 @@ private void a(NBTTagCompound nbttagcompound, NBTTagCompound nbttagcompound1) {
if (nbttagcompound1 != null) {
nbttagcompound.a("Player", nbttagcompound1);
}
+ nbttagcompound.a(CraftPersistentDataContainer.TAG_KEY, this.container.asCompound()); // Tsunami - PersistentDataContainer API
nbttagcompound.a("CustomMetadata", NBTMetadataConvert.metadataToCompound(metadataStore));
}
diff --git a/src/main/java/org/bukkit/Chunk.java b/src/main/java/org/bukkit/Chunk.java
index f45a2c9..41bfcae 100644
--- a/src/main/java/org/bukkit/Chunk.java
+++ b/src/main/java/org/bukkit/Chunk.java
@@ -3,11 +3,12 @@
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Entity;
+import org.bukkit.persistence.PersistentDataHolder;
/**
* Represents a chunk of blocks
*/
-public interface Chunk {
+public interface Chunk extends PersistentDataHolder { // Tsunami - extends PersistentDataHolder
/**
* Gets the X-coordinate of this chunk
diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java
index 0fbe9ac..88d2300 100644
--- a/src/main/java/org/bukkit/World.java
+++ b/src/main/java/org/bukkit/World.java
@@ -8,6 +8,7 @@
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack;
import org.bukkit.metadata.Metadatable;
+import org.bukkit.persistence.PersistentDataHolder;
import org.bukkit.util.Vector;
import java.util.HashMap;
@@ -19,7 +20,7 @@
/**
* Represents a world, which may contain entities, chunks and blocks
*/
-public interface World extends Metadatable { // Tsunami - extends Metadatable
+public interface World extends PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable
/**
* Gets the {@link Block} at the given coordinates
diff --git a/src/main/java/org/bukkit/block/Chest.java b/src/main/java/org/bukkit/block/Chest.java
index a963ec0..861d5f9 100644
--- a/src/main/java/org/bukkit/block/Chest.java
+++ b/src/main/java/org/bukkit/block/Chest.java
@@ -1,10 +1,11 @@
package org.bukkit.block;
import org.bukkit.metadata.Metadatable;
+import org.bukkit.persistence.PersistentDataHolder;
/**
* Represents a chest.
*
* @author sk89q
*/
-public interface Chest extends BlockState, ContainerBlock, Metadatable {} // Tsunami - extends Metadatable
+public interface Chest extends BlockState, ContainerBlock, PersistentDataHolder, Metadatable {} // Tsunami - extends PersistentDataHolder, Metadatable
diff --git a/src/main/java/org/bukkit/block/CreatureSpawner.java b/src/main/java/org/bukkit/block/CreatureSpawner.java
index ff1875b..41b9b6a 100644
--- a/src/main/java/org/bukkit/block/CreatureSpawner.java
+++ b/src/main/java/org/bukkit/block/CreatureSpawner.java
@@ -2,6 +2,7 @@
import org.bukkit.entity.CreatureType;
import org.bukkit.metadata.Metadatable;
+import org.bukkit.persistence.PersistentDataHolder;
/**
* Represents a creature spawner.
@@ -9,7 +10,7 @@
* @author sk89q
* @author Cogito
*/
-public interface CreatureSpawner extends BlockState, Metadatable { // Tsunami - extends Metadatable
+public interface CreatureSpawner extends BlockState, PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable
/**
* Get the spawner's creature type.
diff --git a/src/main/java/org/bukkit/block/Dispenser.java b/src/main/java/org/bukkit/block/Dispenser.java
index ba95845..22ac22b 100644
--- a/src/main/java/org/bukkit/block/Dispenser.java
+++ b/src/main/java/org/bukkit/block/Dispenser.java
@@ -1,13 +1,14 @@
package org.bukkit.block;
import org.bukkit.metadata.Metadatable;
+import org.bukkit.persistence.PersistentDataHolder;
/**
* Represents a dispenser.
*
* @author sk89q
*/
-public interface Dispenser extends BlockState, ContainerBlock, Metadatable { // Tsunami - extends Metadatable
+public interface Dispenser extends BlockState, ContainerBlock, PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable
/**
* Attempts to dispense the contents of this block
diff --git a/src/main/java/org/bukkit/block/Furnace.java b/src/main/java/org/bukkit/block/Furnace.java
index ebc8670..0f10e32 100644
--- a/src/main/java/org/bukkit/block/Furnace.java
+++ b/src/main/java/org/bukkit/block/Furnace.java
@@ -1,13 +1,14 @@
package org.bukkit.block;
import org.bukkit.metadata.Metadatable;
+import org.bukkit.persistence.PersistentDataHolder;
/**
* Represents a furnace.
*
* @author sk89q
*/
-public interface Furnace extends BlockState, ContainerBlock, Metadatable { // Tsunami - extends Metadatable
+public interface Furnace extends BlockState, ContainerBlock, PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable
/**
* Get burn time.
diff --git a/src/main/java/org/bukkit/block/NoteBlock.java b/src/main/java/org/bukkit/block/NoteBlock.java
index 5f8c4ce..b085c09 100644
--- a/src/main/java/org/bukkit/block/NoteBlock.java
+++ b/src/main/java/org/bukkit/block/NoteBlock.java
@@ -3,11 +3,12 @@
import org.bukkit.Instrument;
import org.bukkit.Note;
import org.bukkit.metadata.Metadatable;
+import org.bukkit.persistence.PersistentDataHolder;
/**
* Represents a note.
*/
-public interface NoteBlock extends BlockState, Metadatable { // Tsunami - extends Metadatable
+public interface NoteBlock extends BlockState, PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable
/**
* Gets the note.
diff --git a/src/main/java/org/bukkit/block/Sign.java b/src/main/java/org/bukkit/block/Sign.java
index 7857fdc..65833be 100644
--- a/src/main/java/org/bukkit/block/Sign.java
+++ b/src/main/java/org/bukkit/block/Sign.java
@@ -1,11 +1,12 @@
package org.bukkit.block;
import org.bukkit.metadata.Metadatable;
+import org.bukkit.persistence.PersistentDataHolder;
/**
* Represents either a SignPost or a WallSign
*/
-public interface Sign extends BlockState, Metadatable { // Tsunami - extends Metadatable
+public interface Sign extends BlockState, PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable
/**
* Gets all the lines of text currently on this sign.
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
index 30015ab..4aac7dd 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
@@ -11,7 +11,9 @@
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.craftbukkit.block.CraftBlock;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.entity.Entity;
+import org.bukkit.persistence.PersistentDataContainer;
import java.lang.ref.WeakReference;
import java.util.concurrent.ConcurrentMap;
@@ -223,4 +225,17 @@ public static ChunkSnapshot getEmptyChunkSnapshot(int x, int z, CraftWorld world
}
return new EmptyChunkSnapshot(x, z, world.getName(), world.getFullTime(), biome, biomeTemp, biomeRain);
}
+
+ // Tsunami start - PersistentDataContainer API
+ @Override
+ public PersistentDataContainer getPersistentDataContainer() {
+ return getHandle().container;
+ }
+
+ @Override
+ public PersistentDataContainer newPersistentDataContainer() {
+ return new CraftPersistentDataContainer();
+ }
+ // Tsunami end
+
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 9fdcc15..4a3f0d6 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -8,6 +8,7 @@
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.entity.*;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.craftbukkit.util.LongHash;
import org.bukkit.entity.Entity;
import org.bukkit.entity.*;
@@ -20,6 +21,7 @@
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack;
import org.bukkit.metadata.MetadataValue;
+import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.Plugin;
import org.bukkit.util.Vector;
@@ -849,6 +851,18 @@ public void setKeepSpawnInMemory(boolean keepLoaded) {
}
}
+ // Tsunami start - PersistentDataContainer API
+ @Override
+ public PersistentDataContainer getPersistentDataContainer() {
+ return getHandle().worldData.container;
+ }
+
+ @Override
+ public PersistentDataContainer newPersistentDataContainer() {
+ return new CraftPersistentDataContainer();
+ }
+ // Tsunami end
+
// Tsunami start
public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) {
Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null");
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
index a4f4abf..86751f4 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
@@ -6,8 +6,10 @@
import org.bukkit.block.Chest;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftInventory;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.inventory.Inventory;
import org.bukkit.metadata.MetadataValue;
+import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.Plugin;
public class CraftChest extends CraftBlockState implements Chest {
@@ -36,6 +38,18 @@ public boolean update(boolean force) {
return result;
}
+ // Tsunami start - PersistentDataContainer API
+ @Override
+ public PersistentDataContainer getPersistentDataContainer() {
+ return this.chest.container;
+ }
+
+ @Override
+ public PersistentDataContainer newPersistentDataContainer() {
+ return new CraftPersistentDataContainer();
+ }
+ // Tsunami end
+
// Tsunami start
public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) {
Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null");
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java
index c552712..0c8eef1 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java
@@ -5,8 +5,10 @@
import org.bukkit.block.Block;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.entity.CreatureType;
import org.bukkit.metadata.MetadataValue;
+import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.Plugin;
public class CraftCreatureSpawner extends CraftBlockState implements CreatureSpawner {
@@ -49,6 +51,18 @@ public void setDelay(int delay) {
spawner.spawnDelay = delay;
}
+ // Tsunami start - PersistentDataContainer API
+ @Override
+ public PersistentDataContainer getPersistentDataContainer() {
+ return this.spawner.container;
+ }
+
+ @Override
+ public PersistentDataContainer newPersistentDataContainer() {
+ return new CraftPersistentDataContainer();
+ }
+ // Tsunami end
+
// Tsunami start
public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) {
Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null");
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java b/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java
index aa02257..f63d88a 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java
@@ -8,8 +8,10 @@
import org.bukkit.block.Dispenser;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftInventory;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.inventory.Inventory;
import org.bukkit.metadata.MetadataValue;
+import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.Plugin;
import java.util.Random;
@@ -55,6 +57,18 @@ public boolean update(boolean force) {
return result;
}
+ // Tsunami start - PersistentDataContainer API
+ @Override
+ public PersistentDataContainer getPersistentDataContainer() {
+ return this.dispenser.container;
+ }
+
+ @Override
+ public PersistentDataContainer newPersistentDataContainer() {
+ return new CraftPersistentDataContainer();
+ }
+ // Tsunami end
+
// Tsunami start
public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) {
Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null");
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java
index caafaf3..5863073 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java
@@ -6,8 +6,10 @@
import org.bukkit.block.Furnace;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftInventory;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.inventory.Inventory;
import org.bukkit.metadata.MetadataValue;
+import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.Plugin;
public class CraftFurnace extends CraftBlockState implements Furnace {
@@ -52,6 +54,18 @@ public void setCookTime(short cookTime) {
furnace.cookTime = cookTime;
}
+ // Tsunami start - PersistentDataContainer API
+ @Override
+ public PersistentDataContainer getPersistentDataContainer() {
+ return this.furnace.container;
+ }
+
+ @Override
+ public PersistentDataContainer newPersistentDataContainer() {
+ return new CraftPersistentDataContainer();
+ }
+ // Tsunami end
+
// Tsunami start
public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) {
Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null");
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java
index 8fae74c..eb0b1d2 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java
@@ -8,7 +8,9 @@
import org.bukkit.block.Block;
import org.bukkit.block.NoteBlock;
import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.metadata.MetadataValue;
+import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.Plugin;
public class CraftNoteBlock extends CraftBlockState implements NoteBlock {
@@ -77,6 +79,18 @@ public boolean play(Instrument instrument, Note note) {
}
}
+ // Tsunami start - PersistentDataContainer API
+ @Override
+ public PersistentDataContainer getPersistentDataContainer() {
+ return this.note.container;
+ }
+
+ @Override
+ public PersistentDataContainer newPersistentDataContainer() {
+ return new CraftPersistentDataContainer();
+ }
+ // Tsunami end
+
// Tsunami start
public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) {
Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null");
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java
index 85174e7..e877608 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java
@@ -5,7 +5,9 @@
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.metadata.MetadataValue;
+import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.Plugin;
public class CraftSign extends CraftBlockState implements Sign {
@@ -42,6 +44,18 @@ public boolean update(boolean force) {
return result;
}
+ // Tsunami start - PersistentDataContainer API
+ @Override
+ public PersistentDataContainer getPersistentDataContainer() {
+ return this.sign.container;
+ }
+
+ @Override
+ public PersistentDataContainer newPersistentDataContainer() {
+ return new CraftPersistentDataContainer();
+ }
+ // Tsunami end
+
// Tsunami start
public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) {
Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null");
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index e59e79c..38ad49a 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -9,8 +9,10 @@
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.metadata.MetadataValue;
+import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.Plugin;
import org.bukkit.util.Vector;
@@ -282,6 +284,18 @@ private static CraftPlayer getPlayer(EntityPlayer entity) {
return result;
}
+ // Tsunami start - PersistentDataContainer API
+ @Override
+ public PersistentDataContainer getPersistentDataContainer() {
+ return getHandle().container;
+ }
+
+ @Override
+ public PersistentDataContainer newPersistentDataContainer() {
+ return new CraftPersistentDataContainer();
+ }
+ // Tsunami end
+
// Tsunami start
public void setMetadata(Plugin owningPlugin, String key, MetadataValue value) {
Preconditions.checkArgument(owningPlugin != null, "owningPlugin cannot be null");
diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java
new file mode 100644
index 0000000..492000f
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java
@@ -0,0 +1,110 @@
+package org.bukkit.craftbukkit.persistence;
+
+import net.minecraft.server.NBTBase;
+import net.minecraft.server.NBTTagCompound;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.bukkit.persistence.PersistentDataType;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+public class CraftPersistentDataContainer implements PersistentDataContainer {
+
+ public static final String TAG_KEY = "PersistentDataContainer";
+ private static final PersistentDataTypeRegistry REGISTRY = new PersistentDataTypeRegistry();
+
+ private final NBTTagCompound compound;
+
+ public CraftPersistentDataContainer() {
+ this.compound = new NBTTagCompound();
+ }
+
+ public CraftPersistentDataContainer(NBTTagCompound compound) {
+ this.compound = compound;
+ }
+
+ @Override
+ public
void set(String key, PersistentDataType
type, C value) {
+ Objects.requireNonNull(key, "key must not be null");
+ Objects.requireNonNull(type, "type must not be null");
+ Objects.requireNonNull(value, "value must not be null");
+
+ NBTBase tag = REGISTRY.getOrCreateAdapter(type).serialize(type.toPrimitive(value));
+ this.compound.a(key, tag);
+ }
+
+ @Override
+ public void remove(String key) {
+ Objects.requireNonNull(key, "key must not be null");
+
+ this.compound.a.remove(key);
+ }
+
+ @Override
+ public
boolean has(String key, PersistentDataType
type) {
+ Objects.requireNonNull(key, "key must not be null");
+ Objects.requireNonNull(type, "type must not be null");
+
+ NBTBase tag = (NBTBase) this.compound.a.get(key);
+ return tag != null && REGISTRY.getOrCreateAdapter(type).matches(tag);
+ }
+
+ @Override
+ public boolean has(String key) {
+ Objects.requireNonNull(key, "key must not be null");
+
+ return this.compound.a.containsKey(key);
+ }
+
+ @Override
+ public
C get(String key, PersistentDataType
type) {
+ Objects.requireNonNull(key, "key must not be null");
+ Objects.requireNonNull(type, "type must not be null");
+
+ NBTBase tag = (NBTBase) this.compound.a.get(key);
+ if (tag == null) {
+ return null;
+ }
+ P primitive = REGISTRY.getOrCreateAdapter(type).deserialize(tag);
+ return type.fromPrimitive(primitive);
+ }
+
+ @Override
+ public
C getOrDefault(String key, PersistentDataType
type, C defaultValue) {
+ Objects.requireNonNull(key, "key must not be null");
+ Objects.requireNonNull(type, "type must not be null");
+ Objects.requireNonNull(defaultValue, "defaultValue must not be null");
+
+ C value = get(key, type);
+ return value != null ? value : defaultValue;
+ }
+
+ @Override
+ public Set getKeys() {
+ return Collections.unmodifiableSet(new HashSet<>(this.compound.a.keySet()));
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.compound.a.isEmpty();
+ }
+
+ @Override
+ public void copyTo(PersistentDataContainer other, boolean replace) {
+ Objects.requireNonNull(other, "other must not be null");
+
+ CraftPersistentDataContainer target = (CraftPersistentDataContainer) other;
+ if (replace) {
+ target.asCompound().a.putAll(this.compound.a);
+ } else {
+ this.compound.a.forEach(target.asCompound().a::putIfAbsent);
+ }
+ }
+
+ public NBTTagCompound asCompound() {
+ return this.compound;
+ }
+
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/PersistentDataTypeRegistry.java b/src/main/java/org/bukkit/craftbukkit/persistence/PersistentDataTypeRegistry.java
new file mode 100644
index 0000000..e8d8569
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/persistence/PersistentDataTypeRegistry.java
@@ -0,0 +1,97 @@
+package org.bukkit.craftbukkit.persistence;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import net.minecraft.server.NBTBase;
+import net.minecraft.server.NBTTagByte;
+import net.minecraft.server.NBTTagByteArray;
+import net.minecraft.server.NBTTagCompound;
+import net.minecraft.server.NBTTagDouble;
+import net.minecraft.server.NBTTagFloat;
+import net.minecraft.server.NBTTagInt;
+import net.minecraft.server.NBTTagList;
+import net.minecraft.server.NBTTagLong;
+import net.minecraft.server.NBTTagShort;
+import net.minecraft.server.NBTTagString;
+import org.bukkit.persistence.ListPersistentDataType;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.bukkit.persistence.PersistentDataType;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class PersistentDataTypeRegistry {
+
+ private final Map, PrimitiveToTagAdapter, ?>> adapters = new HashMap<>();
+
+ public synchronized
{
+
+ PersistentDataType BYTE = new PrimitivePersistentDataType<>(Byte.class);
+ PersistentDataType SHORT = new PrimitivePersistentDataType<>(Short.class);
+ PersistentDataType INTEGER = new PrimitivePersistentDataType<>(Integer.class);
+ PersistentDataType LONG = new PrimitivePersistentDataType<>(Long.class);
+ PersistentDataType FLOAT = new PrimitivePersistentDataType<>(Float.class);
+ PersistentDataType DOUBLE = new PrimitivePersistentDataType<>(Double.class);
+ PersistentDataType BOOLEAN = new BooleanPersistentDataType();
+ PersistentDataType CHARACTER = new CharacterPersistentDataType();
+ PersistentDataType STRING = new PrimitivePersistentDataType<>(String.class);
+ PersistentDataType BYTE_ARRAY = new PrimitivePersistentDataType<>(byte[].class);
+ PersistentDataType DATA_CONTAINER = new PrimitivePersistentDataType<>(PersistentDataContainer.class);
+
+ Class
getPrimitiveType();
+
+ Class getComplexType();
+
+ P toPrimitive(C complex);
+
+ C fromPrimitive(P primitive);
+
+ final class PrimitivePersistentDataType
extends PersistentDataType
ListPersistentDataType BYTE_ARRAY = listTypeFrom(PersistentDataType.BYTE_ARRAY);
ListPersistentDataType DATA_CONTAINER = listTypeFrom(PersistentDataType.DATA_CONTAINER);
+ /**
+ * Returns the data type which elements of a list of this type conform to.
+ *
+ * @return the element data type
+ */
PersistentDataType
getElementType();
+ /**
+ * Creates a {@link ListPersistentDataType} from the specified element data type.
+ *
+ * @param type the element data type
+ * @return a new list data type
+ */
static
ListPersistentDataType
listTypeFrom(PersistentDataType
type) {
return new ListPersistentDataTypeImpl<>(type);
}
diff --git a/src/main/java/org/bukkit/persistence/PersistentDataContainer.java b/src/main/java/org/bukkit/persistence/PersistentDataContainer.java
index 03b0add..cdc91a2 100644
--- a/src/main/java/org/bukkit/persistence/PersistentDataContainer.java
+++ b/src/main/java/org/bukkit/persistence/PersistentDataContainer.java
@@ -2,23 +2,103 @@
import java.util.Set;
+/**
+ * Represents a container which stores persistent data using key-value mappings.
+ *
+ * A container can store simple values, but it can also store lists of values
+ * and even other containers, enabling complex and nested data structures
+ * to be stored.
+ *
+ * A distinction is made between primitive and complex types: when storing a
+ * value, a {@link PersistentDataType} must be provided in order to convert
+ * the complex value into a primitive value. When retrieving a value,
+ * the primitive value is converted back to a complex value.
+ *
+ * @see PersistentDataType
+ */
public interface PersistentDataContainer {
+ /**
+ * Stores a new key-value mapping in this container, or replaces the value
+ * if a mapping with the specified key is already present.
+ *
+ * @param key the unique key of the mapping
+ * @param type the {@link PersistentDataType} used to convert
+ * the complex value to a primitive value
+ * @param value the complex value
+ */
void set(String key, PersistentDataType
type, C value);
+ /**
+ * Removes a key-value mapping from this container if it is present.
+ *
+ * @param key the unique key of the mapping to remove
+ */
void remove(String key);
+ /**
+ * Tests if a key-value mapping with the specified key and a value conforming
+ * to the {@link PersistentDataType} is present in this container.
+ *
+ * @param key the unique key of the mapping to test for
+ * @param type the {@link PersistentDataType} which the value should conform to
+ * @return {@code true} if a key-value mapping with this key and whose value
+ * conforms to the type exists
+ */
boolean has(String key, PersistentDataType
type);
+ /**
+ * Tests if a key-value mapping with the specified key is present in this container.
+ *
+ * @param key the unique key of the mapping to test for
+ * @return {@code true} if a key-value mapping with this key exists
+ */
boolean has(String key);
+ /**
+ * Retrieves a value from a key-value mapping present in this container.
+ *
+ * @param key the unique key of the mapping
+ * @param type the {@link PersistentDataType} used to convert
+ * the primitive value to a complex value
+ * @return the value associated with this key, or {@code null}
+ * if no mapping with this key exists
+ */
C get(String key, PersistentDataType
type);
+ /**
+ * Retrieves a value from a key-value mapping present in this container,
+ * or returns the specified default value if no such mapping exists.
+ *
+ * @param key the unique key of the mapping
+ * @param type the {@link PersistentDataType} used to convert
+ * the primitive value to a complex value
+ * @param defaultValue the value to return if the mapping is not present
+ * @return the value associated with this key, or {@code defaultValue}
+ * if no mapping with this key exists
+ */
C getOrDefault(String key, PersistentDataType
type, C defaultValue);
+ /**
+ * Returns a copy of the keys of all mappings present in this container.
+ *
+ * @return the keys of all mappings in this container
+ */
Set getKeys();
+ /**
+ * Tests if this container holds no key-value mappings.
+ *
+ * @return {@code true} if no mappings are present in this container
+ */
boolean isEmpty();
+ /**
+ * Copies all key-value mappings present in this container to another container.
+ *
+ * @param other the container to copy this container's mappings to
+ * @param replace if mappings from this container should replace mappings
+ * which are already present in the other container
+ */
void copyTo(PersistentDataContainer other, boolean replace);
}
diff --git a/src/main/java/org/bukkit/persistence/PersistentDataHolder.java b/src/main/java/org/bukkit/persistence/PersistentDataHolder.java
index 919abd7..6c275e8 100644
--- a/src/main/java/org/bukkit/persistence/PersistentDataHolder.java
+++ b/src/main/java/org/bukkit/persistence/PersistentDataHolder.java
@@ -1,8 +1,24 @@
package org.bukkit.persistence;
+/**
+ * Represents an object which is able to store persistent data.
+ *
+ * @see PersistentDataContainer
+ */
public interface PersistentDataHolder {
+ /**
+ * Returns the {@link PersistentDataContainer} which holds all persistent data
+ * stored by this object.
+ *
+ * @return this object's {@link PersistentDataContainer}
+ */
PersistentDataContainer getPersistentDataContainer();
+ /**
+ * Creates a new empty {@link PersistentDataContainer}.
+ *
+ * @return a new {@link PersistentDataContainer}
+ */
PersistentDataContainer newPersistentDataContainer();
}
diff --git a/src/main/java/org/bukkit/persistence/PersistentDataType.java b/src/main/java/org/bukkit/persistence/PersistentDataType.java
index f0c1064..25e0c1f 100644
--- a/src/main/java/org/bukkit/persistence/PersistentDataType.java
+++ b/src/main/java/org/bukkit/persistence/PersistentDataType.java
@@ -1,5 +1,18 @@
package org.bukkit.persistence;
+/**
+ * Represents a data type which is used to convert a complex value to a
+ * primitive value, and vice versa. This is used by {@link PersistentDataContainer}
+ * for storage and retrieval of values.
+ *
the primitive type
+ * @param the complex type
+ */
public interface PersistentDataType
{
PersistentDataType BYTE = new PrimitivePersistentDataType<>(Byte.class);
@@ -14,12 +27,34 @@ public interface PersistentDataType
{
PersistentDataType BYTE_ARRAY = new PrimitivePersistentDataType<>(byte[].class);
PersistentDataType DATA_CONTAINER = new PrimitivePersistentDataType<>(PersistentDataContainer.class);
+ /**
+ * Returns the primitive type of a value of this data type.
+ *
+ * @return the primitive type
+ */
Class
getPrimitiveType();
+ /**
+ * Returns the complex type of a value of this data type.
+ *
+ * @return the complex type
+ */
Class getComplexType();
+ /**
+ * Converts the given complex value to a primitive value.
+ *
+ * @param complex the complex value
+ * @return the primitive value
+ */
P toPrimitive(C complex);
+ /**
+ * Converts the given primitive value to a complex value.
+ *
+ * @param primitive the primitive value
+ * @return the complex value
+ */
C fromPrimitive(P primitive);
final class PrimitivePersistentDataType
implements PersistentDataType
{
From ff00e87900235c59df7bc7988d44fcc54b0e6d29 Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Sun, 1 Mar 2026 21:37:26 +0100
Subject: [PATCH 19/23] Deprecate all usage of org.bukkit.metadata
---
.../java/org/bukkit/metadata/ByteMetadataValue.java | 5 +++++
.../org/bukkit/metadata/DoubleMetadataValue.java | 5 +++++
.../java/org/bukkit/metadata/FloatMetadataValue.java | 5 +++++
.../java/org/bukkit/metadata/IntMetadataValue.java | 5 +++++
.../java/org/bukkit/metadata/LongMetadataValue.java | 5 +++++
src/main/java/org/bukkit/metadata/MetadataValue.java | 4 ++++
src/main/java/org/bukkit/metadata/Metadatable.java | 12 ++++++++++++
.../java/org/bukkit/metadata/ShortMetadataValue.java | 5 +++++
.../org/bukkit/metadata/StringMetadataValue.java | 5 +++++
9 files changed, 51 insertions(+)
diff --git a/src/main/java/org/bukkit/metadata/ByteMetadataValue.java b/src/main/java/org/bukkit/metadata/ByteMetadataValue.java
index 719fac1..2df84be 100644
--- a/src/main/java/org/bukkit/metadata/ByteMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/ByteMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#BYTE
+ */
+@Deprecated
public class ByteMetadataValue extends MetadataValue {
public ByteMetadataValue(byte value) {
super(value);
diff --git a/src/main/java/org/bukkit/metadata/DoubleMetadataValue.java b/src/main/java/org/bukkit/metadata/DoubleMetadataValue.java
index ddb67ae..bcadeec 100644
--- a/src/main/java/org/bukkit/metadata/DoubleMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/DoubleMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#DOUBLE
+ */
+@Deprecated
public class DoubleMetadataValue extends MetadataValue {
public DoubleMetadataValue(double value) {
super(value);
diff --git a/src/main/java/org/bukkit/metadata/FloatMetadataValue.java b/src/main/java/org/bukkit/metadata/FloatMetadataValue.java
index 428d4af..dd2e6b0 100644
--- a/src/main/java/org/bukkit/metadata/FloatMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/FloatMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#FLOAT
+ */
+@Deprecated
public class FloatMetadataValue extends MetadataValue {
public FloatMetadataValue(float value) {
super(value);
diff --git a/src/main/java/org/bukkit/metadata/IntMetadataValue.java b/src/main/java/org/bukkit/metadata/IntMetadataValue.java
index 9738544..a51f06e 100644
--- a/src/main/java/org/bukkit/metadata/IntMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/IntMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#INTEGER
+ */
+@Deprecated
public class IntMetadataValue extends MetadataValue {
public IntMetadataValue(int value) {
super(value);
diff --git a/src/main/java/org/bukkit/metadata/LongMetadataValue.java b/src/main/java/org/bukkit/metadata/LongMetadataValue.java
index cc3a685..b4e80e3 100644
--- a/src/main/java/org/bukkit/metadata/LongMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/LongMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#LONG
+ */
+@Deprecated
public class LongMetadataValue extends MetadataValue {
public LongMetadataValue(long value) {
super(value);
diff --git a/src/main/java/org/bukkit/metadata/MetadataValue.java b/src/main/java/org/bukkit/metadata/MetadataValue.java
index d85d566..7be3bda 100644
--- a/src/main/java/org/bukkit/metadata/MetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/MetadataValue.java
@@ -2,7 +2,11 @@
/**
* Represents a metadata value of a {@link Metadatable} object
+ *
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType
*/
+@Deprecated
public abstract class MetadataValue {
private final Object value;
diff --git a/src/main/java/org/bukkit/metadata/Metadatable.java b/src/main/java/org/bukkit/metadata/Metadatable.java
index 23c2be4..26d5e1e 100644
--- a/src/main/java/org/bukkit/metadata/Metadatable.java
+++ b/src/main/java/org/bukkit/metadata/Metadatable.java
@@ -4,7 +4,11 @@
/**
* Represents an object that can provide metadata about itself
+ *
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataContainer
*/
+@Deprecated
public interface Metadatable {
/**
@@ -13,7 +17,9 @@ public interface Metadatable {
* @param owningPlugin the plugin owning the metadata
* @param key the unique identifier for the metadata
* @param value the metadata value
+ * @deprecated use {@link org.bukkit.persistence.PersistentDataContainer#set(String, org.bukkit.persistence.PersistentDataType, Object)}
*/
+ @Deprecated
void setMetadata(Plugin owningPlugin, String key, MetadataValue value);
/**
@@ -21,7 +27,9 @@ public interface Metadatable {
*
* @param owningPlugin the plugin owning the metadata
* @param key the unique identifier for the metadata
+ * @deprecated use {@link org.bukkit.persistence.PersistentDataContainer#remove(String)}
*/
+ @Deprecated
void removeMetadata(Plugin owningPlugin, String key);
/**
@@ -30,7 +38,9 @@ public interface Metadatable {
* @param owningPlugin the plugin owning the metadata
* @param key the unique identifier for the metadata
* @return the metadata value, or null if it does not exist
+ * @deprecated use {@link org.bukkit.persistence.PersistentDataContainer#get(String, org.bukkit.persistence.PersistentDataType)}
*/
+ @Deprecated
MetadataValue getMetadata(Plugin owningPlugin, String key);
/**
@@ -39,7 +49,9 @@ public interface Metadatable {
* @param owningPlugin the plugin owning the metadata
* @param key the unique identifier for the metadata
* @return true if the metadata exists, false if not
+ * @deprecated use {@link org.bukkit.persistence.PersistentDataContainer#has(String)}
*/
+ @Deprecated
boolean hasMetadata(Plugin owningPlugin, String key);
}
diff --git a/src/main/java/org/bukkit/metadata/ShortMetadataValue.java b/src/main/java/org/bukkit/metadata/ShortMetadataValue.java
index 440207e..1dc5d4d 100644
--- a/src/main/java/org/bukkit/metadata/ShortMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/ShortMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#SHORT
+ */
+@Deprecated
public class ShortMetadataValue extends MetadataValue {
public ShortMetadataValue(short value) {
super(value);
diff --git a/src/main/java/org/bukkit/metadata/StringMetadataValue.java b/src/main/java/org/bukkit/metadata/StringMetadataValue.java
index 4f19d83..2fef59a 100644
--- a/src/main/java/org/bukkit/metadata/StringMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/StringMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#STRING
+ */
+@Deprecated
public class StringMetadataValue extends MetadataValue {
public StringMetadataValue(String value) {
super(value);
From db0faf9abae226dc26dd852ae617ac1e576535bc Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Mon, 2 Mar 2026 21:54:19 +0100
Subject: [PATCH 20/23] Rename newPersistentDataContainer() and move it to
Server
---
src/main/java/org/bukkit/Bukkit.java | 7 +++++++
src/main/java/org/bukkit/Server.java | 10 ++++++++++
src/main/java/org/bukkit/craftbukkit/CraftChunk.java | 7 -------
src/main/java/org/bukkit/craftbukkit/CraftServer.java | 8 ++++++++
src/main/java/org/bukkit/craftbukkit/CraftWorld.java | 7 -------
.../java/org/bukkit/craftbukkit/block/CraftChest.java | 7 -------
.../bukkit/craftbukkit/block/CraftCreatureSpawner.java | 7 -------
.../org/bukkit/craftbukkit/block/CraftDispenser.java | 7 -------
.../org/bukkit/craftbukkit/block/CraftFurnace.java | 7 -------
.../org/bukkit/craftbukkit/block/CraftNoteBlock.java | 7 -------
.../java/org/bukkit/craftbukkit/block/CraftSign.java | 7 -------
.../org/bukkit/craftbukkit/entity/CraftEntity.java | 7 -------
.../org/bukkit/persistence/PersistentDataHolder.java | 7 -------
13 files changed, 25 insertions(+), 70 deletions(-)
diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
index 4412e15..e91db1f 100644
--- a/src/main/java/org/bukkit/Bukkit.java
+++ b/src/main/java/org/bukkit/Bukkit.java
@@ -8,6 +8,7 @@
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.Recipe;
import org.bukkit.map.MapView;
+import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.ServicesManager;
import org.bukkit.plugin.messaging.Messenger;
@@ -169,6 +170,12 @@ public static World getWorld(UUID uid) {
return server.getWorld(uid);
}
+ // Tsunami start - PersistentDataContainer API
+ public static PersistentDataContainer createPersistentDataContainer() {
+ return server.createPersistentDataContainer();
+ }
+ // Tsunami end
+
public static MapView getMap(short id) {
return server.getMap(id);
}
diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
index c1c3e05..349ecb7 100644
--- a/src/main/java/org/bukkit/Server.java
+++ b/src/main/java/org/bukkit/Server.java
@@ -7,6 +7,7 @@
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.Recipe;
import org.bukkit.map.MapView;
+import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.ServicesManager;
import org.bukkit.plugin.messaging.Messenger;
@@ -327,6 +328,15 @@ public interface Server extends PluginMessageRecipient { // Tsunami - extends Pl
* @return World with the given Unique ID, or null if none exists.
*/
public World getWorld(UUID uid);
+
+ // Tsunami start - PersistentDataContainer API
+ /**
+ * Creates a new empty {@link PersistentDataContainer}.
+ *
+ * @return a new {@link PersistentDataContainer}
+ */
+ public PersistentDataContainer createPersistentDataContainer();
+ // Tsunami end
/**
* Gets the map from the given item ID.
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
index 4aac7dd..502b53c 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
@@ -11,7 +11,6 @@
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.craftbukkit.block.CraftBlock;
-import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.entity.Entity;
import org.bukkit.persistence.PersistentDataContainer;
@@ -227,15 +226,9 @@ public static ChunkSnapshot getEmptyChunkSnapshot(int x, int z, CraftWorld world
}
// Tsunami start - PersistentDataContainer API
- @Override
public PersistentDataContainer getPersistentDataContainer() {
return getHandle().container;
}
-
- @Override
- public PersistentDataContainer newPersistentDataContainer() {
- return new CraftPersistentDataContainer();
- }
// Tsunami end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index cc47a88..78d6aed 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -21,6 +21,7 @@
import org.bukkit.craftbukkit.inventory.CraftShapedRecipe;
import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe;
import org.bukkit.craftbukkit.map.CraftMapView;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.craftbukkit.scheduler.CraftScheduler;
import org.bukkit.entity.Player;
import org.bukkit.event.world.WorldInitEvent;
@@ -33,6 +34,7 @@
import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.inventory.ShapelessRecipe;
import org.bukkit.permissions.Permission;
+import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.*;
import org.bukkit.plugin.java.JavaPluginLoader;
import org.bukkit.plugin.messaging.Messenger;
@@ -666,6 +668,12 @@ public void addWorld(World world) {
worlds.put(world.getName().toLowerCase(), world);
}
+ // Tsunami start - PersistentDataContainer API
+ public PersistentDataContainer createPersistentDataContainer() {
+ return new CraftPersistentDataContainer();
+ }
+ // Tsunami end
+
public Logger getLogger() {
return MinecraftServer.log;
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 4a3f0d6..0ebeb34 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -8,7 +8,6 @@
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.entity.*;
-import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.craftbukkit.util.LongHash;
import org.bukkit.entity.Entity;
import org.bukkit.entity.*;
@@ -852,15 +851,9 @@ public void setKeepSpawnInMemory(boolean keepLoaded) {
}
// Tsunami start - PersistentDataContainer API
- @Override
public PersistentDataContainer getPersistentDataContainer() {
return getHandle().worldData.container;
}
-
- @Override
- public PersistentDataContainer newPersistentDataContainer() {
- return new CraftPersistentDataContainer();
- }
// Tsunami end
// Tsunami start
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
index 86751f4..d11265b 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
@@ -6,7 +6,6 @@
import org.bukkit.block.Chest;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftInventory;
-import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.inventory.Inventory;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.persistence.PersistentDataContainer;
@@ -39,15 +38,9 @@ public boolean update(boolean force) {
}
// Tsunami start - PersistentDataContainer API
- @Override
public PersistentDataContainer getPersistentDataContainer() {
return this.chest.container;
}
-
- @Override
- public PersistentDataContainer newPersistentDataContainer() {
- return new CraftPersistentDataContainer();
- }
// Tsunami end
// Tsunami start
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java
index 0c8eef1..83e012e 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java
@@ -5,7 +5,6 @@
import org.bukkit.block.Block;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.craftbukkit.CraftWorld;
-import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.entity.CreatureType;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.persistence.PersistentDataContainer;
@@ -52,15 +51,9 @@ public void setDelay(int delay) {
}
// Tsunami start - PersistentDataContainer API
- @Override
public PersistentDataContainer getPersistentDataContainer() {
return this.spawner.container;
}
-
- @Override
- public PersistentDataContainer newPersistentDataContainer() {
- return new CraftPersistentDataContainer();
- }
// Tsunami end
// Tsunami start
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java b/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java
index f63d88a..a41b69b 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java
@@ -8,7 +8,6 @@
import org.bukkit.block.Dispenser;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftInventory;
-import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.inventory.Inventory;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.persistence.PersistentDataContainer;
@@ -58,15 +57,9 @@ public boolean update(boolean force) {
}
// Tsunami start - PersistentDataContainer API
- @Override
public PersistentDataContainer getPersistentDataContainer() {
return this.dispenser.container;
}
-
- @Override
- public PersistentDataContainer newPersistentDataContainer() {
- return new CraftPersistentDataContainer();
- }
// Tsunami end
// Tsunami start
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java
index 5863073..86b311c 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java
@@ -6,7 +6,6 @@
import org.bukkit.block.Furnace;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftInventory;
-import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.inventory.Inventory;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.persistence.PersistentDataContainer;
@@ -55,15 +54,9 @@ public void setCookTime(short cookTime) {
}
// Tsunami start - PersistentDataContainer API
- @Override
public PersistentDataContainer getPersistentDataContainer() {
return this.furnace.container;
}
-
- @Override
- public PersistentDataContainer newPersistentDataContainer() {
- return new CraftPersistentDataContainer();
- }
// Tsunami end
// Tsunami start
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java
index eb0b1d2..0cf6b1b 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftNoteBlock.java
@@ -8,7 +8,6 @@
import org.bukkit.block.Block;
import org.bukkit.block.NoteBlock;
import org.bukkit.craftbukkit.CraftWorld;
-import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.Plugin;
@@ -80,15 +79,9 @@ public boolean play(Instrument instrument, Note note) {
}
// Tsunami start - PersistentDataContainer API
- @Override
public PersistentDataContainer getPersistentDataContainer() {
return this.note.container;
}
-
- @Override
- public PersistentDataContainer newPersistentDataContainer() {
- return new CraftPersistentDataContainer();
- }
// Tsunami end
// Tsunami start
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java
index e877608..cad29fc 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java
@@ -5,7 +5,6 @@
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import org.bukkit.craftbukkit.CraftWorld;
-import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.plugin.Plugin;
@@ -45,15 +44,9 @@ public boolean update(boolean force) {
}
// Tsunami start - PersistentDataContainer API
- @Override
public PersistentDataContainer getPersistentDataContainer() {
return this.sign.container;
}
-
- @Override
- public PersistentDataContainer newPersistentDataContainer() {
- return new CraftPersistentDataContainer();
- }
// Tsunami end
// Tsunami start
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 38ad49a..0334ffc 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -9,7 +9,6 @@
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.CraftWorld;
-import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.persistence.PersistentDataContainer;
@@ -285,15 +284,9 @@ private static CraftPlayer getPlayer(EntityPlayer entity) {
}
// Tsunami start - PersistentDataContainer API
- @Override
public PersistentDataContainer getPersistentDataContainer() {
return getHandle().container;
}
-
- @Override
- public PersistentDataContainer newPersistentDataContainer() {
- return new CraftPersistentDataContainer();
- }
// Tsunami end
// Tsunami start
diff --git a/src/main/java/org/bukkit/persistence/PersistentDataHolder.java b/src/main/java/org/bukkit/persistence/PersistentDataHolder.java
index 6c275e8..85aee7c 100644
--- a/src/main/java/org/bukkit/persistence/PersistentDataHolder.java
+++ b/src/main/java/org/bukkit/persistence/PersistentDataHolder.java
@@ -14,11 +14,4 @@ public interface PersistentDataHolder {
* @return this object's {@link PersistentDataContainer}
*/
PersistentDataContainer getPersistentDataContainer();
-
- /**
- * Creates a new empty {@link PersistentDataContainer}.
- *
- * @return a new {@link PersistentDataContainer}
- */
- PersistentDataContainer newPersistentDataContainer();
}
From 1e0ad4eac2b8a116a7175d76b2fa0d7552650184 Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Wed, 22 Apr 2026 19:42:13 +0200
Subject: [PATCH 21/23] Fix incorrect block updates for doors
---
src/main/java/net/minecraft/server/ItemDoor.java | 16 +++++-----------
1 file changed, 5 insertions(+), 11 deletions(-)
diff --git a/src/main/java/net/minecraft/server/ItemDoor.java b/src/main/java/net/minecraft/server/ItemDoor.java
index 73af3e8..73f85bb 100644
--- a/src/main/java/net/minecraft/server/ItemDoor.java
+++ b/src/main/java/net/minecraft/server/ItemDoor.java
@@ -18,8 +18,6 @@ public boolean a(ItemStack itemstack, EntityHuman entityhuman, World world, int
if (l != 1) {
return false;
} else {
- int clickedX = i, clickedY = j, clickedZ = k; // CraftBukkit
-
++j;
Block block;
@@ -70,26 +68,22 @@ public boolean a(ItemStack itemstack, EntityHuman entityhuman, World world, int
}
CraftBlockState blockState = CraftBlockState.getBlockState(world, i, j, k); // CraftBukkit
-
world.suppressPhysics = true;
world.setTypeIdAndData(i, j, k, block.id, i1);
- // CraftBukkit start - bed
- world.suppressPhysics = false;
- world.applyPhysics(i, j, k, Block.REDSTONE_WIRE.id);
- BlockPlaceEvent event = CraftEventFactory.callBlockPlaceEvent(world, entityhuman, blockState, clickedX, clickedY, clickedZ, block);
+ // CraftBukkit start
+ BlockPlaceEvent event = CraftEventFactory.callBlockPlaceEvent(world, entityhuman, blockState, i, j, k, block);
if (event.isCancelled() || !event.canBuild()) {
event.getBlockPlaced().setTypeIdAndData(blockState.getTypeId(), blockState.getRawData(), false);
return false;
}
-
- world.suppressPhysics = true;
// CraftBukkit end
+
world.setTypeIdAndData(i, j + 1, k, block.id, i1 + 8);
world.suppressPhysics = false;
- // world.applyPhysics(i, j, k, block.id); // CraftBukkit - moved up
- world.applyPhysics(i, j + 1, k, Block.REDSTONE_WIRE.id);
+ world.applyPhysics(i, j, k, block.id);
+ world.applyPhysics(i, j + 1, k, block.id);
--itemstack.count;
return true;
}
From e57363fec2d6ec20552e560f138c627240c51629 Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Fri, 1 May 2026 11:06:35 +0200
Subject: [PATCH 22/23] Fix passive and aquatic mobs spawning infrequently
---
.../tsunami/world/LocalCreatureSpawner.java | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/src/main/java/org/betamc/tsunami/world/LocalCreatureSpawner.java b/src/main/java/org/betamc/tsunami/world/LocalCreatureSpawner.java
index 18f3e70..9750fdd 100644
--- a/src/main/java/org/betamc/tsunami/world/LocalCreatureSpawner.java
+++ b/src/main/java/org/betamc/tsunami/world/LocalCreatureSpawner.java
@@ -1,6 +1,6 @@
package org.betamc.tsunami.world;
-import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import net.minecraft.server.BiomeBase;
import net.minecraft.server.BiomeMeta;
import net.minecraft.server.Block;
@@ -22,13 +22,13 @@
public class LocalCreatureSpawner {
private static final LocalMobCapCalculator calculator = new LocalMobCapCalculator();
- private static final LongOpenHashSet chunks = new LongOpenHashSet();
+ private static final IntOpenHashSet creatureTypePerChunk = new IntOpenHashSet();
private LocalCreatureSpawner() {
}
public static void spawnCreatures(World world, boolean spawnMonsters, boolean spawnAnimals) {
- chunks.clear();
+ creatureTypePerChunk.clear();
calculator.prepare(world);
calculator.forEachEntry((player, chunks) -> spawnForPlayer(world, player, chunks, spawnMonsters, spawnAnimals));
}
@@ -43,7 +43,7 @@ private static void spawnForPlayer(World world, EntityPlayer player, List
}
private static void spawnCreatureTypeForChunk(EnumCreatureType creatureType, World world, Chunk chunk) {
- if (!chunks.add(LongHash.toLong(chunk.x, chunk.z))) return;
+ if (!creatureTypePerChunk.add(getKey(creatureType, chunk.x, chunk.z))) return;
int x = (chunk.x << 4) + world.random.nextInt(16);
int y = world.random.nextInt(128);
int z = (chunk.z << 4) + world.random.nextInt(16);
@@ -88,6 +88,13 @@ private static void spawnCreatureTypeForChunk(EnumCreatureType creatureType, Wor
}
}
+ private static int getKey(EnumCreatureType creatureType, int x, int z) {
+ int key = creatureType.ordinal();
+ key = 31 * key + x;
+ key = 31 * key + z;
+ return key;
+ }
+
private static int getTypeId(Chunk chunk, int x, int y, int z) {
if (y >= 0 && y < 128) {
return chunk.getTypeId(x & 15, y, z & 15);
From 9aa02f32e1111a75bdd500f01f694407e128b0e9 Mon Sep 17 00:00:00 2001
From: zavdav <157126752+zavdav@users.noreply.github.com>
Date: Fri, 1 May 2026 11:11:00 +0200
Subject: [PATCH 23/23] Bump to 1.0.8
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 3aac7fd..fd2d135 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
org.betamctsunami
- 1.0.7
+ 1.0.8jar