Compare commits

..

53 Commits

Author SHA1 Message Date
Pasqual Koschmieder
0e9e0b58d2
#3850: Prevent some jdk24+ memory/native access warnings 2025-06-20 20:27:49 +10:00
md_5
8cb49bc10a
SPIGOT-8061: Add property to configure central library URL 2025-06-18 06:42:47 +10:00
md_5
5b05934fe8
Minecraft 1.21.6 support 2025-06-18 01:15:00 +10:00
md_5
97f65726d2
Bump version to 1.21-R0.4-SNAPSHOT 2025-06-16 07:34:53 +10:00
md_5
e3ab8ef15f
Release 1.21-R0.3 2025-06-16 07:32:05 +10:00
FlorianMichael
d5bcabdc60
#3848: Add support for showing dialogs in configuration state
Co-authored-by: Outfluencer <git@outfluencer.dev>
2025-06-16 07:29:16 +10:00
Outfluencer
3cd530f007
#3843: Improve NBT javadocs 2025-06-08 11:08:46 +10:00
Outfluencer
2b9808cd13
#3846, #3847: Handle packet bundles 2025-06-08 11:07:20 +10:00
Outfluencer
70fa02f3a4
#3845: Register CustomClickAction in Configuration state 2025-06-08 11:07:18 +10:00
Outfluencer
4ebc3c96b2
#3845: Apply vanilla limits to nbt read operation 2025-06-08 11:07:16 +10:00
Outfluencer
dd1531e28d
#3845: Add method to read tag with custom limiter 2025-06-08 11:07:11 +10:00
md_5
5709a65785
Minecraft 1.21.6-pre3 support 2025-06-07 09:48:12 +10:00
Outfluencer
5348aad094
#3844: Fix parsing nested array chat components 2025-06-05 21:03:24 +10:00
md_5
b60c1bdb37
Limit length of chat string conversions 2025-06-05 21:00:01 +10:00
Outfluencer
93508d5083
#3841, #3842: Speed up TagUtil#fromJson()
Reduce complexity and recursive calls
2025-06-04 06:50:02 +10:00
md_5
1f159f8eaa
Fix copy-pasted URL 2025-06-04 06:45:31 +10:00
Yannick Lamprecht
31f7ef8c54
#3834: Update to SLF4J 2 2025-06-03 20:06:28 +10:00
Mickey42302
244412405a
#3840: Add separate permission for "/alertraw" 2025-06-03 19:58:32 +10:00
Outfluencer
9cd0d3289f
#3838, #3839: Check CaseInsensitiveHashingStrategy equals for null strings 2025-06-02 06:03:06 +10:00
md_5
7cde213e63
#3837: Update to JLine 3 2025-06-01 12:09:40 +10:00
md_5
aa44ebe770
Remove obsolete/unused findbugs-annotations 2025-05-31 13:07:09 +10:00
md_5
5dad41034b
Migrate from trove to fastutil 2025-05-31 12:54:26 +10:00
md_5
cd1ceb4c31
Refactor dialog actions 2025-05-31 11:15:41 +10:00
md_5
23ba5141f1
Remove CustomClickEvent.ACTION_KEY 2025-05-31 10:35:27 +10:00
md_5
415ac8c81e
Move dialog serializer package 2025-05-31 10:27:36 +10:00
Outfluencer
41e49dad6b
#3803: Add NBT module 2025-05-31 10:16:29 +10:00
md_5
bec329352d
Update dialog javadoc and example 2025-05-29 21:53:59 +10:00
md_5
89e66ed648
Minecraft 1.21.6-pre1 support 2025-05-29 21:50:45 +10:00
Outfluencer
d8f9d81b30
#3835: Check for dialog loops 2025-05-27 19:37:54 +10:00
Outfluencer
363003d8c7
#3833: Small fixes/improvements to dialog api 2025-05-26 07:06:09 +10:00
md_5
a696bb0e9f
Fix extra whitespace in pom.xml 2025-05-25 11:17:20 +10:00
md_5
68f4f6bd40
Add Java 25-ea to GitHub Actions 2025-05-25 10:32:25 +10:00
Outfluencer
442ff808f3
#3832: Fix width setter in PlainMessageBody and make all API fluent 2025-05-25 10:15:07 +10:00
Outfluencer
fbbcc454d5
#3831: Respect vanilla limits in dialogs 2025-05-25 09:12:37 +10:00
md_5
f8de305477
Expand CustomClickEvent to have raw and parsed data 2025-05-22 22:16:22 +10:00
md_5
f1f5be18f9
Add further documentation to dialog API 2025-05-21 22:23:05 +10:00
md_5
e05560976b
Bump Netty to 4.2.1.Final 2025-05-21 21:46:38 +10:00
Outfluencer
8bff00f15b
#3830: Dialog & 25w21a changes 2025-05-21 07:09:59 +10:00
md_5
4d37c2488e
Document packages net.md_5.bungee.api.dialog.action and net.md_5.bungee.api.dialog.body. 2025-05-20 21:49:44 +10:00
md_5
75456b2c0a
Document package net.md_5.bungee.api.dialog 2025-05-20 21:37:03 +10:00
md_5
53365e4b18
Fix java 8 javadoc 2025-05-17 16:06:41 +10:00
md_5
69e4872f40
Minecraft 25w20a protocol support 2025-05-17 16:01:00 +10:00
Outfluencer
a336efb8fa
#3825, #3826: Check memory address availability before using natives 2025-05-12 20:45:27 +10:00
onebeastchris
ae2fc30b7b
#3828: Set allocator using Netty system property to apply pooled allocator default globally 2025-05-12 20:39:24 +10:00
md_5
2bafb70581
Minecraft 25w19a protocol support 2025-05-09 19:45:28 +10:00
md_5
617c2728a2
Minecraft 25w18a protocol support 2025-05-03 16:55:39 +10:00
Outfluencer
89069a362d
#3823: Add client brand API 2025-04-21 16:28:16 +10:00
Outfluencer
1279cca971
#3810: Use retainedSlice if possible in MinecraftDecoder 2025-04-19 15:40:09 +10:00
Outfluencer
26433bf021
#3821: Fix race condition that leads to incorrect packet order 2025-04-19 15:32:34 +10:00
md_5
d81040cd6f
Bump lombok version 2025-04-13 09:39:40 +10:00
Outfluencer
d7538df91b
#3815: Ensure ping response for unthrottling 2025-04-12 17:14:38 +10:00
Outfluencer
2516de6586
#3816: Upgrade Netty to 4.2.0.Final 2025-04-12 17:13:31 +10:00
Valentine
1da3a8c240
#3814: Fire exception in pipeline if async task in eventloop throws exception 2025-04-12 17:10:51 +10:00
174 changed files with 4654 additions and 1505 deletions

View File

@ -4,12 +4,12 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
java: [8, 11, 17, 21]
java: [8, 11, 17, 21, 25-ea]
name: Java ${{ matrix.java }}

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-API</name>
@ -31,6 +31,12 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-dialog</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-event</artifactId>

View File

@ -12,6 +12,7 @@ import net.md_5.bungee.api.SkinConfiguration;
import net.md_5.bungee.api.Title;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.dialog.Dialog;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.score.Scoreboard;
import org.jetbrains.annotations.ApiStatus;
@ -382,4 +383,31 @@ public interface ProxiedPlayer extends Connection, CommandSender
*/
@ApiStatus.Experimental
void transfer(String host, int port);
/**
* Gets the client brand of this player.
* If the player has not sent a brand packet yet, it will return null.
*
* @return the brand of the client, or null if not received yet
*/
String getClientBrand();
/**
* Clear the player's open dialog.
*
* @throws IllegalStateException if the players version is not at least
* 1.21.6
*/
@ApiStatus.Experimental
void clearDialog();
/**
* Show a dialog to the player.
*
* @param dialog the dialog to show
* @throws IllegalStateException if the players version is not at least
* 1.21.6
*/
@ApiStatus.Experimental
void showDialog(Dialog dialog);
}

View File

@ -0,0 +1,39 @@
package net.md_5.bungee.api.event;
import com.google.gson.JsonElement;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.api.plugin.Event;
import org.jetbrains.annotations.ApiStatus;
/**
* Called after a {@link ProxiedPlayer} runs a custom action from a chat event
* or form submission.
*/
@Data
@ToString(callSuper = false)
@EqualsAndHashCode(callSuper = false)
@ApiStatus.Experimental
public class CustomClickEvent extends Event implements Cancellable
{
/**
* Player who clicked.
*/
private final ProxiedPlayer player;
/**
* Custom action ID.
*/
private final String id;
/**
* The data as submitted.
*/
private final JsonElement data;
/**
* Cancelled state.
*/
private boolean cancelled;
}

View File

@ -35,6 +35,7 @@ import org.eclipse.aether.transport.http.HttpTransporterFactory;
class LibraryLoader
{
private static final String REPOSITORY_PROPERTY = "net.md_5.bungee.api.plugin.centralURL";
private final Logger logger;
private final RepositorySystem repository;
private final DefaultRepositorySystemSession session;
@ -68,7 +69,7 @@ class LibraryLoader
session.setSystemProperties( System.getProperties() );
session.setReadOnly();
this.repositories = repository.newResolutionRepositories( session, Arrays.asList( new RemoteRepository.Builder( "central", "default", "https://repo.maven.apache.org/maven2" ).build() ) );
this.repositories = repository.newResolutionRepositories( session, Arrays.asList( new RemoteRepository.Builder( "central", "default", System.getProperty( REPOSITORY_PROPERTY, "https://repo.maven.apache.org/maven2" ) ).build() ) );
}
public ClassLoader createLoader(PluginDescription desc)

View File

@ -1,22 +1,37 @@
package net.md_5.bungee.util;
import gnu.trove.strategy.HashingStrategy;
import it.unimi.dsi.fastutil.Hash;
import java.util.Locale;
class CaseInsensitiveHashingStrategy implements HashingStrategy
class CaseInsensitiveHashingStrategy implements Hash.Strategy<String>
{
static final CaseInsensitiveHashingStrategy INSTANCE = new CaseInsensitiveHashingStrategy();
@Override
public int computeHashCode(Object object)
public int hashCode(String object)
{
return ( (String) object ).toLowerCase( Locale.ROOT ).hashCode();
if ( object == null )
{
return 0;
}
return object.toLowerCase( Locale.ROOT ).hashCode();
}
@Override
public boolean equals(Object o1, Object o2)
public boolean equals(String o1, String o2)
{
return o1.equals( o2 ) || ( o1 instanceof String && o2 instanceof String && ( (String) o1 ).toLowerCase( Locale.ROOT ).equals( ( (String) o2 ).toLowerCase( Locale.ROOT ) ) );
if ( o1 == o2 )
{
return true;
}
if ( o1 == null || o2 == null )
{
return false;
}
return o1.equals( o2 ) || o1.toLowerCase( Locale.ROOT ).equals( o2.toLowerCase( Locale.ROOT ) );
}
}

View File

@ -1,9 +1,9 @@
package net.md_5.bungee.util;
import gnu.trove.map.hash.TCustomHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
import java.util.Map;
public class CaseInsensitiveMap<V> extends TCustomHashMap<String, V>
public class CaseInsensitiveMap<V> extends Object2ObjectOpenCustomHashMap<String, V>
{
public CaseInsensitiveMap()
@ -13,6 +13,6 @@ public class CaseInsensitiveMap<V> extends TCustomHashMap<String, V>
public CaseInsensitiveMap(Map<? extends String, ? extends V> map)
{
super( CaseInsensitiveHashingStrategy.INSTANCE, map );
super( map, CaseInsensitiveHashingStrategy.INSTANCE );
}
}

View File

@ -1,9 +1,9 @@
package net.md_5.bungee.util;
import gnu.trove.set.hash.TCustomHashSet;
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
import java.util.Collection;
public class CaseInsensitiveSet extends TCustomHashSet<String>
public class CaseInsensitiveSet extends ObjectOpenCustomHashSet<String>
{
public CaseInsensitiveSet()
@ -13,6 +13,6 @@ public class CaseInsensitiveSet extends TCustomHashSet<String>
public CaseInsensitiveSet(Collection<? extends String> collection)
{
super( CaseInsensitiveHashingStrategy.INSTANCE, collection );
super( collection, CaseInsensitiveHashingStrategy.INSTANCE );
}
}

View File

@ -13,12 +13,12 @@ public class CaseInsensitiveTest
CaseInsensitiveMap<Object> map = new CaseInsensitiveMap<>();
map.put( "FOO", obj );
assertTrue( map.contains( "foo" ) ); // Assert that contains is case insensitive
assertTrue( map.containsKey( "foo" ) ); // Assert that contains is case insensitive
assertTrue( map.entrySet().iterator().next().getKey().equals( "FOO" ) ); // Assert that case is preserved
// Assert that remove is case insensitive
map.remove( "FoO" );
assertFalse( map.contains( "foo" ) );
assertFalse( map.containsKey( "foo" ) );
}
@Test

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-bootstrap</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Bootstrap</name>
@ -48,6 +48,7 @@
<Main-Class>net.md_5.bungee.Bootstrap</Main-Class>
<Implementation-Version>${describe}</Implementation-Version>
<Specification-Version>${maven.build.timestamp}</Specification-Version>
<Enable-Native-Access>ALL-UNNAMED</Enable-Native-Access>
</manifestEntries>
</archive>
</configuration>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-chat</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Chat</name>

View File

@ -4,6 +4,7 @@ import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
@ -677,11 +678,11 @@ public abstract class BaseComponent
public String toPlainText()
{
StringBuilder builder = new StringBuilder();
toPlainText( builder );
toPlainText( new LimitedStringVisitor( builder, Short.MAX_VALUE ) );
return builder.toString();
}
void toPlainText(StringBuilder builder)
void toPlainText(StringVisitor builder)
{
if ( extra != null )
{
@ -701,11 +702,11 @@ public abstract class BaseComponent
public String toLegacyText()
{
StringBuilder builder = new StringBuilder();
toLegacyText( builder );
toLegacyText( new LimitedStringVisitor( builder, Short.MAX_VALUE ) );
return builder.toString();
}
void toLegacyText(StringBuilder builder)
void toLegacyText(StringVisitor builder)
{
if ( extra != null )
{
@ -716,7 +717,7 @@ public abstract class BaseComponent
}
}
void addFormat(StringBuilder builder)
void addFormat(StringVisitor builder)
{
builder.append( getColor() );
if ( isBold() )
@ -740,4 +741,35 @@ public abstract class BaseComponent
builder.append( ChatColor.MAGIC );
}
}
@FunctionalInterface
protected static interface StringVisitor
{
void append(String s);
default void append(Object obj)
{
append( String.valueOf( obj ) );
}
}
@Data
protected static class LimitedStringVisitor implements StringVisitor
{
private final StringBuilder builder;
private final int maxLength;
@Override
public void append(String s)
{
if ( builder.length() >= maxLength )
{
throw new IllegalArgumentException( "String exceeded maximum length " + maxLength );
}
builder.append( s );
}
}
}

View File

@ -4,12 +4,14 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.ApiStatus;
@Getter
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor
public final class ClickEvent
@ApiStatus.NonExtendable
public class ClickEvent
{
/**
@ -52,11 +54,19 @@ public final class ClickEvent
* {@link net.md_5.bungee.api.chat.ClickEvent#value} in a book.
*/
CHANGE_PAGE,
/**
* Must use subclass ShowDialogClickEvent.
*/
SHOW_DIALOG,
/**
* Copy the string given by
* {@link net.md_5.bungee.api.chat.ClickEvent#value} into the player's
* clipboard.
*/
COPY_TO_CLIPBOARD
COPY_TO_CLIPBOARD,
/**
* Must use subclass {@link ClickEventCustom}.
*/
CUSTOM,
}
}

View File

@ -0,0 +1,30 @@
package net.md_5.bungee.api.chat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* Click event which sends a custom payload to the server.
*/
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ClickEventCustom extends ClickEvent
{
/**
* The custom payload.
*/
private final String payload;
/**
* @param id identifier for the event (lower case, no special characters)
* @param payload custom payload
*/
public ClickEventCustom(String id, String payload)
{
super( ClickEvent.Action.CUSTOM, id );
this.payload = payload;
}
}

View File

@ -13,7 +13,6 @@ import net.md_5.bungee.api.chat.hover.content.Content;
import net.md_5.bungee.api.chat.hover.content.Entity;
import net.md_5.bungee.api.chat.hover.content.Item;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.md_5.bungee.chat.VersionedComponentSerializer;
import org.jetbrains.annotations.ApiStatus;
@Getter
@ -73,22 +72,6 @@ public final class HoverEvent
this.legacy = true;
}
@Deprecated
public BaseComponent[] getValue()
{
Content content = contents.get( 0 );
if ( content instanceof Text && ( (Text) content ).getValue() instanceof BaseComponent[] )
{
return (BaseComponent[]) ( (Text) content ).getValue();
}
TextComponent component = new TextComponent( VersionedComponentSerializer.getDefault().toString( content ) );
return new BaseComponent[]
{
component
};
}
/**
* Adds a content to this hover event.
*

View File

@ -50,14 +50,14 @@ public final class KeybindComponent extends BaseComponent
}
@Override
protected void toPlainText(StringBuilder builder)
protected void toPlainText(StringVisitor builder)
{
builder.append( getKeybind() );
super.toPlainText( builder );
}
@Override
protected void toLegacyText(StringBuilder builder)
protected void toLegacyText(StringVisitor builder)
{
addFormat( builder );
builder.append( getKeybind() );

View File

@ -85,14 +85,14 @@ public final class ScoreComponent extends BaseComponent
}
@Override
protected void toPlainText(StringBuilder builder)
protected void toPlainText(StringVisitor builder)
{
builder.append( this.value );
super.toPlainText( builder );
}
@Override
protected void toLegacyText(StringBuilder builder)
protected void toLegacyText(StringVisitor builder)
{
addFormat( builder );
builder.append( this.value );

View File

@ -69,14 +69,14 @@ public final class SelectorComponent extends BaseComponent
}
@Override
protected void toPlainText(StringBuilder builder)
protected void toPlainText(StringVisitor builder)
{
builder.append( this.selector );
super.toPlainText( builder );
}
@Override
protected void toLegacyText(StringBuilder builder)
protected void toLegacyText(StringVisitor builder)
{
addFormat( builder );
builder.append( this.selector );

View File

@ -280,14 +280,14 @@ public final class TextComponent extends BaseComponent
}
@Override
protected void toPlainText(StringBuilder builder)
protected void toPlainText(StringVisitor builder)
{
builder.append( text );
super.toPlainText( builder );
}
@Override
protected void toLegacyText(StringBuilder builder)
protected void toLegacyText(StringVisitor builder)
{
addFormat( builder );
builder.append( text );

View File

@ -156,20 +156,20 @@ public final class TranslatableComponent extends BaseComponent
}
@Override
protected void toPlainText(StringBuilder builder)
protected void toPlainText(StringVisitor builder)
{
convert( builder, false );
super.toPlainText( builder );
}
@Override
protected void toLegacyText(StringBuilder builder)
protected void toLegacyText(StringVisitor builder)
{
convert( builder, true );
super.toLegacyText( builder );
}
private void convert(StringBuilder builder, boolean applyFormat)
private void convert(StringVisitor builder, boolean applyFormat)
{
String trans = TranslationRegistry.INSTANCE.translate( translate );

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-config</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Config</name>

28
dialog/LICENSE Normal file
View File

@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2025, SpigotMC Pty. Ltd.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

38
dialog/README.md Normal file
View File

@ -0,0 +1,38 @@
BungeeCord-Dialog
=================
Highly experimental API, subject to breakage. All contributions welcome, including major refactors/design changes.
Sample Plugin
-------------
```java
private class TestCommand extends Command
{
public TestCommand()
{
super( "btest" );
}
@Override
public void execute(CommandSender sender, String[] args)
{
ProxiedPlayer player = (ProxiedPlayer) sender;
Dialog notice = new NoticeDialog( new DialogBase( new ComponentBuilder( "Hello" ).color( ChatColor.RED ).build() ) );
player.showDialog( notice );
notice = new NoticeDialog(
new DialogBase( new ComponentBuilder( "Hello" ).color( ChatColor.RED ).build() )
.inputs(
Arrays.asList( new TextInput( "first", new ComponentBuilder( "First" ).build() ),
new TextInput( "second", new ComponentBuilder( "Second" ).build() )
)
) )
.action( new ActionButton( new ComponentBuilder( "Submit Button" ).build(), new CustomClickAction( "customform" ) ) );
player.sendMessage( new ComponentBuilder( "click me" ).event( new ShowDialogClickEvent( notice ) ).build() );
}
}
```

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-shared-configuration>
<!--
This file contains additional configuration written by modules in the NetBeans IDE.
The configuration is intended to be shared among all the users of project and
therefore it is assumed to be part of version control checkout.
Without this configuration present, some functionality in the IDE may be limited or fail altogether.
-->
<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
<!--
Properties that influence various parts of the IDE, especially code formatting and the like.
You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
That way multiple projects can share the same settings (useful for formatting rules for example).
Any value defined here will override the pom.xml file value but is only applicable to the current project.
-->
<org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>
</properties>
</project-shared-configuration>

36
dialog/pom.xml Normal file
View File

@ -0,0 +1,36 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-dialog</artifactId>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Dialog</name>
<description>Minecraft dialog API intended for use with BungeeCord</description>
<licenses>
<license>
<name>BSD-3-Clause</name>
<url>https://github.com/SpigotMC/BungeeCord/blob/master/dialog/LICENSE</url>
<distribution>repo</distribution>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-chat</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,39 @@
package net.md_5.bungee.api.dialog;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.dialog.action.ActionButton;
/**
* Represents a simple dialog with text and two actions at the bottom (default:
* "yes", "no").
*/
@Data
@ToString
@EqualsAndHashCode
@AllArgsConstructor
@Accessors(fluent = true)
public final class ConfirmationDialog implements Dialog
{
@NonNull
@Accessors(fluent = false)
private DialogBase base;
/**
* The "yes" click action / bottom (appears on the left).
*/
private ActionButton yes;
/**
* The "no" click action / bottom (appears on the right).
*/
private ActionButton no;
public ConfirmationDialog(@NonNull DialogBase base)
{
this( base, null, null );
}
}

View File

@ -0,0 +1,29 @@
package net.md_5.bungee.api.dialog;
import org.jetbrains.annotations.ApiStatus;
/**
* Represents a dialog GUI.
*/
public interface Dialog
{
/**
* Gets the dialog base which contains the dialog title and other options
* common to all types of dialogs.
*
* @return mutable reference to the dialog base
*/
DialogBase getBase();
/**
* Sets the dialog base.
* <br>
* For internal use only as this is mandatory and should be specified in the
* constructor.
*
* @param base the new dialog base
*/
@ApiStatus.Internal
void setBase(DialogBase base);
}

View File

@ -0,0 +1,84 @@
package net.md_5.bungee.api.dialog;
import com.google.gson.annotations.SerializedName;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.dialog.body.DialogBody;
import net.md_5.bungee.api.dialog.input.DialogInput;
/**
* Represents the title and other options common to all dialogs.
*/
@Data
@AllArgsConstructor
@Accessors(fluent = true)
public final class DialogBase
{
/**
* The mandatory dialog title.
*/
@NonNull
private BaseComponent title;
/**
* The name which is used for any buttons leading to this dialog (eg from a
* {@link DialogListDialog}). Otherwise defaults to {@link #title}.
*/
@SerializedName("external_title")
private BaseComponent externalTitle;
/**
* The inputs to the dialog.
*/
private List<DialogInput> inputs;
/**
* The body elements which make up this dialog.
*/
private List<DialogBody> body;
/**
* Whether this dialog can be closed with the escape key (default: true).
*/
@SerializedName("can_close_with_escape")
private Boolean canCloseWithEscape;
/**
* Whether this dialog should pause the game in single-player mode (default:
* true).
*/
private Boolean pause;
/**
* Action to take after the a click or submit action is performed on the
* dialog (default: close).
*/
@SerializedName("after_action")
private AfterAction afterAction;
public DialogBase(@NonNull BaseComponent title)
{
this( title, null, null, null, null, null, null );
}
/**
* Types of action which may be taken after the dialog.
*/
public enum AfterAction
{
/**
* Close the dialog.
*/
@SerializedName("close")
CLOSE,
/**
* Do nothing.
*/
@SerializedName("none")
NONE,
/**
* Show a waiting for response screen.
*/
@SerializedName("wait_for_response")
WAIT_FOR_RESPONSE;
}
}

View File

@ -0,0 +1,73 @@
package net.md_5.bungee.api.dialog;
import com.google.common.base.Preconditions;
import com.google.gson.annotations.SerializedName;
import java.util.Arrays;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.dialog.action.ActionButton;
/**
* Represents a dialog which contains buttons that link to other dialogs.
*/
@Data
@ToString
@EqualsAndHashCode
@Accessors(fluent = true)
public final class DialogListDialog implements Dialog
{
@NonNull
@Accessors(fluent = false)
private DialogBase base;
/**
* The child dialogs behind each button.
*/
private List<Dialog> dialogs;
/**
* The {@link ActionButton} activated when the dialog is exited.
*/
@SerializedName("exit_action")
private ActionButton exitAction;
/**
* The number of columns for the dialog buttons (default: 2).
*/
private Integer columns;
/**
* The width of the dialog buttons (default: 150, minimum: 1, maximum: 1024).
*/
@SerializedName("button_width")
private Integer buttonWidth;
public DialogListDialog(@NonNull DialogBase base, Dialog... dialogs)
{
this( base, Arrays.asList( dialogs ), null, null, null );
}
public DialogListDialog(@NonNull DialogBase base, List<Dialog> dialogs, ActionButton exitAction, Integer columns, Integer buttonWidth)
{
this.base = base;
this.dialogs = dialogs;
this.exitAction = exitAction;
columns( columns );
buttonWidth( buttonWidth );
}
public DialogListDialog columns(Integer columns)
{
Preconditions.checkArgument( columns == null || columns > 0, "At least one column is required" );
this.columns = columns;
return this;
}
public DialogListDialog buttonWidth(Integer buttonWidth)
{
Preconditions.checkArgument( buttonWidth == null || ( buttonWidth >= 1 && buttonWidth <= 1024 ), "buttonWidth must be between 1 and 1024" );
this.buttonWidth = buttonWidth;
return this;
}
}

View File

@ -0,0 +1,64 @@
package net.md_5.bungee.api.dialog;
import com.google.common.base.Preconditions;
import com.google.gson.annotations.SerializedName;
import java.util.Arrays;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.dialog.action.ActionButton;
/**
* Represents a dialog with text a list of action buttons grouped into columns
* and scrollable if necessary.
*/
@Data
@ToString
@EqualsAndHashCode
@Accessors(fluent = true)
public final class MultiActionDialog implements Dialog
{
@NonNull
@Accessors(fluent = false)
private DialogBase base;
/**
* The action buttons in the dialog. At least one must be provided.
*/
@NonNull
private List<ActionButton> actions;
/**
* The number of columns for the dialog buttons (default: 2).
*/
private Integer columns;
/**
* The {@link ActionButton} activated when the dialog is exited.
*/
@SerializedName("exit_action")
private ActionButton exitAction;
public MultiActionDialog(@NonNull DialogBase base, @NonNull ActionButton... actions)
{
this( base, Arrays.asList( actions ), null, null );
}
public MultiActionDialog(@NonNull DialogBase base, @NonNull List<ActionButton> actions, Integer columns, ActionButton exitAction)
{
Preconditions.checkArgument( !actions.isEmpty(), "At least one action must be provided" );
this.base = base;
this.actions = actions;
columns( columns );
this.exitAction = exitAction;
}
public MultiActionDialog columns(Integer columns)
{
Preconditions.checkArgument( columns == null || columns > 0, "At least one column is required" );
this.columns = columns;
return this;
}
}

View File

@ -0,0 +1,35 @@
package net.md_5.bungee.api.dialog;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.dialog.action.ActionButton;
/**
* Represents a simple dialog with text and one action at the bottom (default:
* "OK").
*/
@Data
@ToString
@EqualsAndHashCode
@AllArgsConstructor
@Accessors(fluent = true)
public final class NoticeDialog implements Dialog
{
@NonNull
@Accessors(fluent = false)
private DialogBase base;
/**
* The "OK" action button for the dialog.
*/
private ActionButton action;
public NoticeDialog(DialogBase base)
{
this( base, null );
}
}

View File

@ -0,0 +1,71 @@
package net.md_5.bungee.api.dialog;
import com.google.common.base.Preconditions;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.dialog.action.ActionButton;
/**
* Represents a dialog which shows the links configured/sent from the server.
*/
@Data
@ToString
@EqualsAndHashCode
@Accessors(fluent = true)
public final class ServerLinksDialog implements Dialog
{
@NonNull
@Accessors(fluent = false)
private DialogBase base;
/**
* The optional {@link ActionButton} for this dialog.
*/
@SerializedName("action")
private ActionButton action;
/**
* The {@link ActionButton} activated when the dialog is exited.
*/
@SerializedName("exit_action")
private ActionButton exitAction;
/**
* The number of columns for the dialog buttons (default: 2).
*/
private Integer columns;
/**
* The width of the dialog buttons (default: 150, minimum: 1, maximum: 1024).
*/
@SerializedName("button_width")
private Integer buttonWidth;
public ServerLinksDialog(@NonNull DialogBase base)
{
this( base, null, null, null );
}
public ServerLinksDialog(@NonNull DialogBase base, ActionButton action, Integer columns, Integer buttonWidth)
{
this.base = base;
this.action = action;
columns( columns );
buttonWidth( buttonWidth );
}
public ServerLinksDialog columns(Integer columns)
{
Preconditions.checkArgument( columns == null || columns > 0, "At least one column is required" );
this.columns = columns;
return this;
}
public ServerLinksDialog buttonWidth(Integer buttonWidth)
{
Preconditions.checkArgument( buttonWidth == null || ( buttonWidth >= 1 && buttonWidth <= 1024 ), "buttonWidth must be between 1 and 1024" );
this.buttonWidth = buttonWidth;
return this;
}
}

View File

@ -0,0 +1,6 @@
package net.md_5.bungee.api.dialog.action;
public interface Action
{
}

View File

@ -0,0 +1,54 @@
package net.md_5.bungee.api.dialog.action;
import com.google.common.base.Preconditions;
import lombok.Data;
import lombok.NonNull;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Represents a dialog action which will usually appear as a button.
*/
@Data
@Accessors(fluent = true)
public class ActionButton
{
/**
* The text label of the button, mandatory.
*/
@NonNull
private BaseComponent label;
/**
* The hover tooltip of the button.
*/
private BaseComponent tooltip;
/**
* The width of the button (default: 150, minimum: 1, maximum: 1024).
*/
private Integer width;
/**
* The action to take.
*/
@NonNull
private Action action;
public ActionButton(@NonNull BaseComponent label, BaseComponent tooltip, Integer width, @NonNull Action action)
{
this.label = label;
this.tooltip = tooltip;
setWidth( width );
this.action = action;
}
public ActionButton(@NonNull BaseComponent label, @NonNull Action action)
{
this( label, null, null, action );
}
public void setWidth(Integer width)
{
Preconditions.checkArgument( width == null || ( width >= 1 && width <= 1024 ), "width must be between 1 and 1024" );
this.width = width;
}
}

View File

@ -0,0 +1,27 @@
package net.md_5.bungee.api.dialog.action;
import com.google.gson.JsonElement;
import lombok.Data;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* Submits the dialog with the given ID and values as a payload.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
public class CustomClickAction implements Action
{
/**
* The namespaced key of the submission.
*/
@NonNull
private String id;
/**
* Fields to be added to the submission payload.
*/
private JsonElement additions;
}

View File

@ -0,0 +1,25 @@
package net.md_5.bungee.api.dialog.action;
import lombok.Data;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* Executes a command. If the command requires a permission
* higher than 0, a confirmation dialog will be shown by the client.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
public class RunCommandAction implements Action
{
/**
* The template to be applied, where variables of the form
* <code>$(key)</code> will be replaced by their
* {@link net.md_5.bungee.api.dialog.input.DialogInput#key} value.
*/
@NonNull
private String template;
}

View File

@ -0,0 +1,20 @@
package net.md_5.bungee.api.dialog.action;
import lombok.Data;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.ClickEvent;
/**
* Represents a static dialog action.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
public class StaticAction implements Action
{
@NonNull
private ClickEvent clickEvent;
}

View File

@ -0,0 +1,4 @@
/**
* Contains the different actions/buttons for a {@link net.md_5.bungee.api.dialog.Dialog}.
*/
package net.md_5.bungee.api.dialog.action;

View File

@ -0,0 +1,20 @@
package net.md_5.bungee.api.dialog.body;
import lombok.Data;
import lombok.NonNull;
import org.jetbrains.annotations.ApiStatus;
/**
* Represents the body content of a {@link net.md_5.bungee.api.dialog.Dialog}.
*/
@Data
public abstract class DialogBody
{
/**
* The internal body type.
*/
@NonNull
@ApiStatus.Internal
private final String type;
}

View File

@ -0,0 +1,49 @@
package net.md_5.bungee.api.dialog.body;
import com.google.common.base.Preconditions;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Represents a dialog body which consists of text constrained to a certain
* width.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class PlainMessageBody extends DialogBody
{
/**
* The text body.
*/
@NonNull
private BaseComponent contents;
/**
* The maximum width (default: 200, minimum: 1, maximum: 1024).
*/
private Integer width;
public PlainMessageBody(@NonNull BaseComponent contents)
{
this( contents, null );
}
public PlainMessageBody(@NonNull BaseComponent contents, Integer width)
{
super( "minecraft:plain_message" );
this.contents = contents;
width( width );
}
public void width(Integer width)
{
Preconditions.checkArgument( width == null || ( width >= 1 && width <= 1024 ), "width must be between 1 and 1024" );
this.width = width;
}
}

View File

@ -0,0 +1,5 @@
/**
* Contains the different {@link net.md_5.bungee.api.dialog.Dialog} body content
* types.
*/
package net.md_5.bungee.api.dialog.body;

View File

@ -0,0 +1,42 @@
package net.md_5.bungee.api.dialog.chat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.dialog.Dialog;
/**
* Click event which displays either a pre-existing dialog by key or a custom
* dialog.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class ShowDialogClickEvent extends ClickEvent
{
/**
* Key for a pre-existing dialog to show.
*/
private String reference;
/**
* Dialog to show.
*/
private Dialog dialog;
public ShowDialogClickEvent(String reference)
{
this( reference, null );
}
public ShowDialogClickEvent(Dialog dialog)
{
this( null, dialog );
}
private ShowDialogClickEvent(String reference, Dialog dialog)
{
super( Action.SHOW_DIALOG, null );
this.reference = reference;
this.dialog = dialog;
}
}

View File

@ -0,0 +1,4 @@
/**
* Contains dialog extensions to the chat API.
*/
package net.md_5.bungee.api.dialog.chat;

View File

@ -0,0 +1,54 @@
package net.md_5.bungee.api.dialog.input;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Represents a checkbox input control.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class BooleanInput extends DialogInput
{
/**
* The input label.
*/
@NonNull
private BaseComponent label;
/**
* The initial value (default: false/unchecked).
*/
private Boolean initial;
/**
* The string value to be submitted when true/checked (default: "true").
*/
@SerializedName("on_true")
private String onTrue;
/**
* The string value to be submitted when false/unchecked (default: "false").
*/
@SerializedName("on_false")
private String onFalse;
public BooleanInput(@NonNull String key, @NonNull BaseComponent label)
{
this( key, label, null, "true", "false" );
}
public BooleanInput(@NonNull String key, @NonNull BaseComponent label, Boolean initial, String onTrue, String onFalse)
{
super( "minecraft:boolean", key );
this.label = label;
this.initial = initial;
this.onTrue = onTrue;
this.onFalse = onFalse;
}
}

View File

@ -0,0 +1,29 @@
package net.md_5.bungee.api.dialog.input;
import lombok.Data;
import lombok.NonNull;
import lombok.experimental.Accessors;
import org.jetbrains.annotations.ApiStatus;
/**
* Represents a type of input which may be displayed/submitted with a form
* dialog.
*/
@Data
@Accessors(fluent = true)
public class DialogInput
{
/**
* The internal input type.
*/
@NonNull
@ApiStatus.Internal
private final String type;
/**
* The key corresponding to this input and associated with the value
* submitted.
*/
@NonNull
private final String key;
}

View File

@ -0,0 +1,39 @@
package net.md_5.bungee.api.dialog.input;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Represents an option choice which may form part of a
* {@link SingleOptionInput}.
*/
@Data
@AllArgsConstructor
@Accessors(fluent = true)
public class InputOption
{
/**
* The string value associated with this option, to be submitted when
* selected.
*/
@NonNull
private String id;
/**
* The text to display for this option.
*/
private BaseComponent display;
/**
* Whether this option is the one initially selected. Only one option may
* have this value as true (default: first option).
*/
private Boolean initial;
public InputOption(@NonNull String id)
{
this( id, null, null );
}
}

View File

@ -0,0 +1,103 @@
package net.md_5.bungee.api.dialog.input;
import com.google.common.base.Preconditions;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Represents a number slider input.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class NumberRangeInput extends DialogInput
{
/**
* The width of the input (default: 200, minimum: 1, maximum: 1024).
*/
private Integer width;
/**
* The label of the slider.
*/
@NonNull
private BaseComponent label;
/**
* A translate key used to display the label value (default:
* options.generic_value).
*/
private String labelFormat;
/**
* The start position of the slider (leftmost position).
*/
private float start;
/**
* The end position of the slider (rightmost position).
*/
private float end;
/**
* The steps in which the input will be increased or decreased, or null if
* no specific steps.
*/
private Float step;
/**
* The initial value of number input, or null to fall back to the middle.
*/
private Float initial;
public NumberRangeInput(@NonNull String key, @NonNull BaseComponent label, float start, float end)
{
this( key, null, label, "options.generic_value", start, end, null, null );
}
public NumberRangeInput(@NonNull String key, @NonNull BaseComponent label, float start, float end, Float step)
{
this( key, null, label, "options.generic_value", start, end, step, null );
}
public NumberRangeInput(@NonNull String key, @NonNull BaseComponent label, float start, float end, Float step, Float initial)
{
this( key, null, label, "options.generic_value", start, end, step, initial );
}
public NumberRangeInput(@NonNull String key, Integer width, @NonNull BaseComponent label, String labelFormat, float start, float end, Float step, Float initial)
{
super( "minecraft:number_range", key );
width( width );
this.label = label;
this.labelFormat = labelFormat;
this.start = start;
this.end = end;
step( step );
initial( initial );
}
public NumberRangeInput width(Integer width)
{
Preconditions.checkArgument( width == null || ( width >= 1 && width <= 1024 ), "with must be between 1 and 1024" );
this.width = width;
return this;
}
public NumberRangeInput step(Float step)
{
Preconditions.checkArgument( step == null || step > 0, "step must be null or greater than zero" );
this.step = step;
return this;
}
public NumberRangeInput initial(Float initial)
{
// we need to calculate if the initial value is between start and end, regardless of the order
float min = Math.min( start, end );
float max = Math.max( start, end );
Preconditions.checkArgument( initial == null || ( initial >= min && initial <= max ), "initial must be null or between start and end" );
this.initial = initial;
return this;
}
}

View File

@ -0,0 +1,66 @@
package net.md_5.bungee.api.dialog.input;
import com.google.common.base.Preconditions;
import com.google.gson.annotations.SerializedName;
import java.util.Arrays;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Represents a single option (dropdown) input.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class SingleOptionInput extends DialogInput
{
/**
* The width of the input (default: 200, minimum: 1, maximum: 1024).
*/
private Integer width;
/**
* The input label.
*/
@NonNull
private BaseComponent label;
/**
* Whether the label is visible (default: true).
*/
@SerializedName("label_visible")
private Boolean labelVisible;
/**
* The non-empty list of options to be selected from.
*/
@NonNull
private List<InputOption> options;
public SingleOptionInput(@NonNull String key, @NonNull BaseComponent label, @NonNull InputOption... options)
{
this( key, null, label, null, Arrays.asList( options ) );
}
public SingleOptionInput(@NonNull String key, Integer width, @NonNull BaseComponent label, Boolean labelVisible, @NonNull List<InputOption> options)
{
super( "minecraft:single_option", key );
Preconditions.checkArgument( !options.isEmpty(), "At least one option must be provided" );
width( width );
this.label = label;
this.labelVisible = labelVisible;
this.options = options;
}
public SingleOptionInput width(Integer width)
{
Preconditions.checkArgument( width == null || ( width >= 1 && width <= 1024 ), "width must be between 1 and 1024" );
this.width = width;
return this;
}
}

View File

@ -0,0 +1,110 @@
package net.md_5.bungee.api.dialog.input;
import com.google.common.base.Preconditions;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Represents a textbox input.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class TextInput extends DialogInput
{
/**
* The width of this text input (default: 200, minimum: 1, maximum: 1024).
*/
private Integer width;
/**
* The label of this text input.
*/
@NonNull
private BaseComponent label;
/**
* The visibility of this text input's label.
*/
@SerializedName("label_visible")
private Boolean labelVisible;
/**
* The initial value of this text input.
*/
private String initial;
/**
* The maximum length of the input (default: 32).
*/
@SerializedName("max_length")
private Integer maxLength;
/**
* If present, allows users to input multiple lines.
*/
private Multiline multiline;
public TextInput(@NonNull String key, @NonNull BaseComponent label)
{
this( key, null, label, null, null, null, null );
}
public TextInput(@NonNull String key, Integer width, @NonNull BaseComponent label, Boolean labelVisible, String initial, Integer maxLength)
{
this( key, width, label, labelVisible, initial, maxLength, null );
}
public TextInput(@NonNull String key, Integer width, @NonNull BaseComponent label, Boolean labelVisible, String initial, Integer maxLength, Multiline multiline)
{
super( "minecraft:text", key );
width( width );
this.label = label;
this.labelVisible = labelVisible;
this.initial = initial;
this.maxLength = maxLength;
this.multiline = multiline;
}
/**
* Configuration data for a multiline input.
*/
@Data
@NoArgsConstructor
@Accessors(fluent = true)
public static class Multiline
{
/**
* The maximum length of input, or null to disable any limits.
*/
@SerializedName("max_lines")
private Integer maxLines;
/**
* The height of this input (default: 32, minimum: 1, maximum: 512).
*/
private Integer height;
public Multiline(Integer maxLines, Integer height)
{
height( height ).maxLines( maxLines );
}
public Multiline height(Integer height)
{
Preconditions.checkArgument( height == null || height >= 1 && height <= 512, "height must null or be between 1 and 512" );
this.height = height;
return this;
}
}
public TextInput width(Integer width)
{
Preconditions.checkArgument( width == null || ( width >= 1 && width <= 1024 ), "width must be between 1 and 1024" );
this.width = width;
return this;
}
}

View File

@ -0,0 +1,4 @@
/**
* Represents the various input controls which may be present on form dialogs.
*/
package net.md_5.bungee.api.dialog.input;

View File

@ -0,0 +1,4 @@
/**
* Contains the core classes for the display of a {@link net.md_5.bungee.api.dialog.Dialog}.
*/
package net.md_5.bungee.api.dialog;

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-event</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Event</name>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-log</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Log</name>
@ -20,9 +20,9 @@
<dependencies>
<dependency>
<groupId>jline</groupId>
<groupId>org.jline</groupId>
<artifactId>jline</artifactId>
<version>2.12.1</version>
<version>3.30.4</version>
<scope>compile</scope>
</dependency>
<dependency>

View File

@ -1,26 +1,18 @@
package net.md_5.bungee.log;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import jline.console.ConsoleReader;
import org.jline.reader.LineReader;
public class BungeeLogger extends Logger
{
private final LogDispatcher dispatcher = new LogDispatcher( this );
// CHECKSTYLE:OFF
@SuppressWarnings(
{
"CallToPrintStackTrace", "CallToThreadStartDuringObjectConstruction"
})
// CHECKSTYLE:ON
@SuppressFBWarnings("SC_START_IN_CTOR")
public BungeeLogger(String loggerName, String filePattern, ConsoleReader reader)
public BungeeLogger(String loggerName, String filePattern, LineReader reader)
{
super( loggerName, null );
setLevel( Level.ALL );

View File

@ -1,15 +1,15 @@
package net.md_5.bungee.log;
import java.io.IOException;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.regex.Pattern;
import jline.console.ConsoleReader;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.ChatColor;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.Ansi.Erase;
import org.jline.jansi.Ansi;
import org.jline.reader.LineReader;
@RequiredArgsConstructor
public class ColouredWriter extends Handler
{
@ -52,12 +52,7 @@ public class ColouredWriter extends Handler
compile( ChatColor.RESET, Ansi.ansi().a( Ansi.Attribute.RESET ).toString() ),
};
//
private final ConsoleReader console;
public ColouredWriter(ConsoleReader console)
{
this.console = console;
}
private final LineReader console;
public void print(String s)
{
@ -65,14 +60,7 @@ public class ColouredWriter extends Handler
{
s = replacement.pattern.matcher( s ).replaceAll( replacement.replacement );
}
try
{
console.print( Ansi.ansi().eraseLine( Erase.ALL ).toString() + ConsoleReader.RESET_LINE + s + Ansi.ansi().reset().toString() );
console.drawLine();
console.flush();
} catch ( IOException ex )
{
}
console.printAbove( s + Ansi.ansi().reset().toString() );
}
@Override

View File

@ -18,7 +18,6 @@ public class ConciseFormatter extends Formatter
private final boolean coloured;
@Override
@SuppressWarnings("ThrowableResultIgnored")
public String format(LogRecord record)
{
StringBuilder formatted = new StringBuilder();

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module-cmd-alert</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cmd_alert</name>

View File

@ -15,7 +15,7 @@ public class CommandAlertRaw extends Command
public CommandAlertRaw()
{
super( "alertraw", "bungeecord.command.alert" );
super( "alertraw", "bungeecord.command.alertraw" );
}
@Override

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module-cmd-find</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cmd_find</name>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module-cmd-kick</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cmd_kick</name>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module-cmd-list</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cmd_list</name>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module-cmd-send</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cmd_send</name>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module-cmd-server</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cmd_server</name>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>pom</packaging>
<name>BungeeCord Modules</name>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module-reconnect-yaml</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>reconnect_yaml</name>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-native</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Native</name>

View File

@ -1,6 +1,8 @@
package net.md_5.bungee.jni;
import com.google.common.io.ByteStreams;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -93,7 +95,31 @@ public final class NativeCode<T>
return loaded;
}
public static boolean hasDirectBuffers()
{
ByteBuf directBuffer = null;
boolean hasMemoryAddress = false;
try
{
directBuffer = Unpooled.directBuffer();
hasMemoryAddress = directBuffer.hasMemoryAddress();
} finally
{
if ( directBuffer != null )
{
directBuffer.release();
}
}
return hasMemoryAddress;
}
public static boolean isSupported()
{
return isSupportedPlatformAndArch() && hasDirectBuffers();
}
private static boolean isSupportedPlatformAndArch()
{
return "Linux".equals( System.getProperty( "os.name" ) ) && ( isAmd64() || isAarch64() );
}

28
nbt/LICENSE Normal file
View File

@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2025, SpigotMC Pty. Ltd.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

4
nbt/README.md Normal file
View File

@ -0,0 +1,4 @@
BungeeCord-NBT
=================
Minimal implementation of NBT for use in BungeeCord

31
nbt/nb-configuration.xml Normal file
View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-shared-configuration>
<!--
This file contains additional configuration written by modules in the NetBeans IDE.
The configuration is intended to be shared among all the users of project and
therefore it is assumed to be part of version control checkout.
Without this configuration present, some functionality in the IDE may be limited or fail altogether.
-->
<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
<!--
Properties that influence various parts of the IDE, especially code formatting and the like.
You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
That way multiple projects can share the same settings (useful for formatting rules for example).
Any value defined here will override the pom.xml file value but is only applicable to the current project.
-->
<org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>
</properties>
</project-shared-configuration>

27
nbt/pom.xml Normal file
View File

@ -0,0 +1,27 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-nbt</artifactId>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-NBT</name>
<description>Minimal implementation of NBT for use in BungeeCord</description>
<licenses>
<license>
<name>BSD-3-Clause</name>
<url>https://github.com/SpigotMC/BungeeCord/blob/master/nbt/LICENSE</url>
<distribution>repo</distribution>
</license>
</licenses>
</project>

View File

@ -0,0 +1,52 @@
package net.md_5.bungee.nbt;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.limit.NBTLimiter;
import net.md_5.bungee.nbt.type.CompoundTag;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class NamedTag implements Tag
{
private String name;
private TypedTag tag;
/**
* Reads the data of the {@link DataInput} and parses it into this
* {@link NamedTag}.
*
* @param input input to read from
* @param limiter limitation of the read data
*/
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
byte type = input.readByte();
name = CompoundTag.readString( input, limiter );
tag = Tag.readById( type, input, limiter );
}
/**
* Write this {@link NamedTag} into a {@link DataOutput}.
*
* @param output the output to write to
*/
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( name, "name cannot be null" );
Preconditions.checkNotNull( tag, "tag cannot be null" );
output.writeByte( tag.getId() );
CompoundTag.writeString( name, output );
tag.write( output );
}
}

View File

@ -0,0 +1,167 @@
package net.md_5.bungee.nbt;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.function.Supplier;
import net.md_5.bungee.nbt.exception.NBTFormatException;
import net.md_5.bungee.nbt.limit.NBTLimiter;
import net.md_5.bungee.nbt.type.ByteArrayTag;
import net.md_5.bungee.nbt.type.ByteTag;
import net.md_5.bungee.nbt.type.CompoundTag;
import net.md_5.bungee.nbt.type.DoubleTag;
import net.md_5.bungee.nbt.type.EndTag;
import net.md_5.bungee.nbt.type.FloatTag;
import net.md_5.bungee.nbt.type.IntArrayTag;
import net.md_5.bungee.nbt.type.IntTag;
import net.md_5.bungee.nbt.type.ListTag;
import net.md_5.bungee.nbt.type.LongArrayTag;
import net.md_5.bungee.nbt.type.LongTag;
import net.md_5.bungee.nbt.type.ShortTag;
import net.md_5.bungee.nbt.type.StringTag;
public interface Tag
{
int OBJECT_HEADER = 8;
int ARRAY_HEADER = 12;
int STRING_SIZE = 28;
int OBJECT_REFERENCE = 4;
Supplier<? extends TypedTag>[] CONSTRUCTORS = new Supplier[]
{
EndTag::new,
ByteTag::new,
ShortTag::new,
IntTag::new,
LongTag::new,
FloatTag::new,
DoubleTag::new,
ByteArrayTag::new,
StringTag::new,
ListTag::new,
CompoundTag::new,
IntArrayTag::new,
LongArrayTag::new
};
byte END = 0;
byte BYTE = 1;
byte SHORT = 2;
byte INT = 3;
byte LONG = 4;
byte FLOAT = 5;
byte DOUBLE = 6;
byte BYTE_ARRAY = 7;
byte STRING = 8;
byte LIST = 9;
byte COMPOUND = 10;
byte INT_ARRAY = 11;
byte LONG_ARRAY = 12;
/**
* Reads the data into this tag.
*
* @param input the input to read from
* @param limiter the limiter for this read operation
* @throws IOException if an exception occurs during io operations
*/
void read(DataInput input, NBTLimiter limiter) throws IOException;
/**
* Writes this tag into a {@link DataOutput}.
*
* @param output the output to write to
* @throws IOException if an exception occurs during io operations
*/
void write(DataOutput output) throws IOException;
/**
* Reads a {@link Tag} from the given {@link DataInput},
* based on the specified tag type, with limitations of the
* {@link NBTLimiter}.
*
* @param id the nbt type
* @param input the input to read from
* @param limiter the limiter for this read operation
* @return the deserialized {@link Tag}
* @throws IOException if an exception occurs during io operations
*/
static TypedTag readById(byte id, DataInput input, NBTLimiter limiter) throws IOException
{
if ( id < END || id > LONG_ARRAY )
{
throw new NBTFormatException( "Invalid tag id: " + id );
}
TypedTag tag = CONSTRUCTORS[id].get();
tag.read( input, limiter );
return tag;
}
/**
* Reads a {@link NamedTag} from the given {@link DataInput},
* with limitations of the {@link NBTLimiter}.
*
* @param input the data input to read from
* @param limiter the limiter for this read operation
* @return the deserialized {@link NamedTag}
* @throws IOException if an exception occurs during io operations
*/
static NamedTag readNamedTag(DataInput input, NBTLimiter limiter) throws IOException
{
NamedTag namedTag = new NamedTag();
namedTag.read( input, limiter );
return namedTag;
}
/**
* Serializes the given {@link TypedTag} into a byte array.
* This is the inverse operation of {@link #fromByteArray(byte[])}.
*
* @param tag the tag to convert
* @return the serialized byte array
* @throws IOException if an exception occurs during io operations
*/
static byte[] toByteArray(TypedTag tag) throws IOException
{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream( byteArrayOutputStream );
dataOutputStream.writeByte( tag.getId() );
tag.write( dataOutputStream );
return byteArrayOutputStream.toByteArray();
}
/**
* Deserializes the given byte array into a {@link TypedTag}.
* This is the inverse operation of {@link #toByteArray(TypedTag)}.
*
* @param data the byte array to read from
* @return the deserialized {@link TypedTag}
* @throws IOException if an exception occurs during io operations
*/
static TypedTag fromByteArray(byte[] data) throws IOException
{
return fromByteArray( data, NBTLimiter.unlimitedSize() );
}
/**
* Deserializes the given byte array into a {@link TypedTag},
* with limitations of the {@link NBTLimiter}.
*
* @param data the byte array to read from
* @param limiter the limiter for this read operation
* @return the deserialized {@link TypedTag}
* @throws IOException if an exception occurs during io operations
*/
static TypedTag fromByteArray(byte[] data, NBTLimiter limiter) throws IOException
{
DataInputStream stream = new DataInputStream( new ByteArrayInputStream( data ) );
byte type = stream.readByte();
return readById( type, stream, limiter );
}
}

View File

@ -0,0 +1,12 @@
package net.md_5.bungee.nbt;
public interface TypedTag extends Tag
{
/**
* Gets the id of this tag's type.
*
* @return the id related to this tag's type
*/
byte getId();
}

View File

@ -0,0 +1,10 @@
package net.md_5.bungee.nbt.exception;
public class NBTException extends RuntimeException
{
public NBTException(String message)
{
super( message );
}
}

View File

@ -0,0 +1,10 @@
package net.md_5.bungee.nbt.exception;
public class NBTFormatException extends NBTException
{
public NBTFormatException(String message)
{
super( message );
}
}

View File

@ -0,0 +1,10 @@
package net.md_5.bungee.nbt.exception;
public class NBTLimitException extends NBTException
{
public NBTLimitException(String message)
{
super( message );
}
}

View File

@ -0,0 +1,66 @@
package net.md_5.bungee.nbt.limit;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.nbt.exception.NBTLimitException;
@RequiredArgsConstructor
public class NBTLimiter
{
private static final int MAX_STACK_DEPTH = 512;
//
private final long maxBytes;
private final int maxDepth;
public static NBTLimiter unlimitedSize()
{
return new NBTLimiter( Long.MAX_VALUE, MAX_STACK_DEPTH );
}
public NBTLimiter(long maxBytes)
{
this( maxBytes, MAX_STACK_DEPTH );
}
private long usedBytes;
private int depth;
public void countBytes(long amount)
{
if ( amount < 0 )
{
throw new NBTLimitException( "NBT limiter tried to count negative byte amount" );
}
if ( ( usedBytes = Math.addExact( usedBytes, amount ) ) > maxBytes )
{
throw new NBTLimitException( "NBT tag is to big, bytes > " + maxBytes );
}
}
public void countBytes(long amount, long factor)
{
if ( amount < 0 || factor < 0 )
{
throw new NBTLimitException( "NBT limiter tried to count negative byte amount" );
}
countBytes( Math.multiplyExact( amount, factor ) );
}
public void push()
{
if ( ( depth = Math.addExact( depth, 1 ) ) > maxDepth )
{
throw new NBTLimitException( "NBT tag is to complex, depth > " + maxDepth );
}
}
public void pop()
{
if ( --depth < 0 )
{
throw new NBTLimitException( "NBT limiter tried to pop depth 0" );
}
}
}

View File

@ -0,0 +1,44 @@
package net.md_5.bungee.nbt.type;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ByteArrayTag implements TypedTag
{
private byte[] value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + ARRAY_HEADER + Integer.BYTES );
int length = input.readInt();
limiter.countBytes( length, Byte.BYTES );
input.readFully( value = new byte[ length ] );
}
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( value, "byte array value cannot be null" );
output.writeInt( value.length );
output.write( value );
}
@Override
public byte getId()
{
return Tag.BYTE_ARRAY;
}
}

View File

@ -0,0 +1,39 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ByteTag implements TypedTag
{
private byte value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + Byte.BYTES );
value = input.readByte();
}
@Override
public void write(DataOutput output) throws IOException
{
output.write( value );
}
@Override
public byte getId()
{
return Tag.BYTE;
}
}

View File

@ -0,0 +1,99 @@
package net.md_5.bungee.nbt.type;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.exception.NBTFormatException;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CompoundTag implements TypedTag
{
private static final int MAP_SIZE_IN_BYTES = 48;
private static final int MAP_ENTRY_SIZE_IN_BYTES = 32;
//
private Map<String, TypedTag> value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.push();
limiter.countBytes( MAP_SIZE_IN_BYTES );
Map<String, TypedTag> map = new LinkedHashMap<>();
for ( byte type; ( type = input.readByte() ) != Tag.END; )
{
String name = readString( input, limiter );
TypedTag tag = Tag.readById( type, input, limiter );
if ( map.put( name, tag ) == null )
{
limiter.countBytes( MAP_ENTRY_SIZE_IN_BYTES + OBJECT_REFERENCE );
}
}
limiter.pop();
value = map;
}
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( value, "compound tag map cannot be null" );
for ( Map.Entry<String, TypedTag> entry : value.entrySet() )
{
String name = entry.getKey();
TypedTag tag = entry.getValue();
output.writeByte( tag.getId() );
if ( tag.getId() == Tag.END )
{
throw new NBTFormatException( "invalid end tag in compound tag" );
}
writeString( name, output );
tag.write( output );
}
output.writeByte( 0 );
}
@Override
public byte getId()
{
return Tag.COMPOUND;
}
public static String readString(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( STRING_SIZE );
String string = input.readUTF();
limiter.countBytes( string.length(), Character.BYTES );
return string;
}
public static void writeString(String string, DataOutput output) throws IOException
{
output.writeUTF( string );
}
public TypedTag get(String key)
{
return value.get( key );
}
public void put(String key, TypedTag tag)
{
value.put( key, tag );
}
public int size()
{
return value.size();
}
}

View File

@ -0,0 +1,39 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DoubleTag implements TypedTag
{
private double value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + Double.BYTES );
value = input.readDouble();
}
@Override
public void write(DataOutput output) throws IOException
{
output.writeDouble( value );
}
@Override
public byte getId()
{
return Tag.DOUBLE;
}
}

View File

@ -0,0 +1,34 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
public class EndTag implements TypedTag
{
public static final EndTag INSTANCE = new EndTag();
@Override
public void read(DataInput input, NBTLimiter limiter)
{
limiter.countBytes( OBJECT_HEADER );
}
@Override
public void write(DataOutput output)
{
}
@Override
public byte getId()
{
return Tag.END;
}
}

View File

@ -0,0 +1,39 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FloatTag implements TypedTag
{
private float value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + Float.BYTES );
value = input.readFloat();
}
@Override
public void write(DataOutput output) throws IOException
{
output.writeFloat( value );
}
@Override
public byte getId()
{
return Tag.FLOAT;
}
}

View File

@ -0,0 +1,52 @@
package net.md_5.bungee.nbt.type;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class IntArrayTag implements TypedTag
{
private int[] value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + ARRAY_HEADER + Integer.BYTES );
int length = input.readInt();
limiter.countBytes( length, Integer.BYTES );
int[] data = new int[ length ];
for ( int i = 0; i < length; i++ )
{
data[i] = input.readInt();
}
value = data;
}
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( value, "int array value cannot be null" );
output.writeInt( value.length );
for ( int i : value )
{
output.writeInt( i );
}
}
@Override
public byte getId()
{
return Tag.INT_ARRAY;
}
}

View File

@ -0,0 +1,39 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class IntTag implements TypedTag
{
private int value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + Integer.BYTES );
value = input.readInt();
}
@Override
public void write(DataOutput output) throws IOException
{
output.writeInt( value );
}
@Override
public byte getId()
{
return Tag.INT;
}
}

View File

@ -0,0 +1,92 @@
package net.md_5.bungee.nbt.type;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.exception.NBTFormatException;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ListTag implements TypedTag
{
public static final int LIST_HEADER = 12;
//
private List<TypedTag> value;
private byte listType;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.push();
limiter.countBytes( OBJECT_HEADER + LIST_HEADER + ARRAY_HEADER + Byte.BYTES + Integer.BYTES );
listType = input.readByte();
int length = input.readInt();
if ( length < 0 )
{
throw new NBTFormatException( "ListTag length cannot be negative" );
}
if ( listType == Tag.END && length > 0 )
{
throw new NBTFormatException( "Missing type in ListTag" );
}
limiter.countBytes( length, OBJECT_REFERENCE );
List<TypedTag> tagList = new ArrayList<>( length );
for ( int i = 0; i < length; i++ )
{
tagList.add( Tag.readById( listType, input, limiter ) );
}
limiter.pop();
value = tagList;
}
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( value, "list value cannot be null" );
if ( listType == Tag.END && !value.isEmpty() )
{
throw new NBTFormatException( "Missing type in ListTag" );
}
output.writeByte( listType );
output.writeInt( value.size() );
for ( TypedTag tag : value )
{
if ( tag.getId() != listType )
{
throw new NBTFormatException( "ListTag type mismatch" );
}
tag.write( output );
}
}
@Override
public byte getId()
{
return Tag.LIST;
}
public TypedTag get(int index)
{
return value.get( index );
}
public int size()
{
return value.size();
}
}

View File

@ -0,0 +1,53 @@
package net.md_5.bungee.nbt.type;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LongArrayTag implements TypedTag
{
private long[] value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + ARRAY_HEADER + Integer.BYTES );
int length = input.readInt();
limiter.countBytes( length, Long.BYTES );
long[] data = new long[ length ];
for ( int i = 0; i < length; i++ )
{
data[i] = input.readLong();
}
value = data;
}
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( value, "long array value cannot be null" );
output.writeInt( value.length );
for ( long i : value )
{
output.writeLong( i );
}
}
@Override
public byte getId()
{
return Tag.LONG_ARRAY;
}
}

View File

@ -0,0 +1,39 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LongTag implements TypedTag
{
private long value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + Long.BYTES );
value = input.readLong();
}
@Override
public void write(DataOutput output) throws IOException
{
output.writeLong( value );
}
@Override
public byte getId()
{
return Tag.LONG;
}
}

View File

@ -0,0 +1,39 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ShortTag implements TypedTag
{
private short value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + Short.BYTES );
value = input.readShort();
}
@Override
public void write(DataOutput output) throws IOException
{
output.writeShort( value );
}
@Override
public byte getId()
{
return Tag.SHORT;
}
}

View File

@ -0,0 +1,44 @@
package net.md_5.bungee.nbt.type;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StringTag implements TypedTag
{
private String value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + STRING_SIZE );
String string = input.readUTF();
limiter.countBytes( string.length(), Character.BYTES );
value = string;
}
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( value, "string value cannot be null" );
output.writeUTF( value );
}
@Override
public byte getId()
{
return Tag.STRING;
}
}

View File

@ -0,0 +1,53 @@
package net.md_5.bungee.nbt;
import static org.junit.jupiter.api.Assertions.*;
import java.util.ArrayList;
import net.md_5.bungee.nbt.exception.NBTLimitException;
import net.md_5.bungee.nbt.limit.NBTLimiter;
import net.md_5.bungee.nbt.type.ByteArrayTag;
import net.md_5.bungee.nbt.type.ListTag;
import org.junit.jupiter.api.Test;
public class NBTLimiterTest
{
@Test
public void testNbtLimiter()
{
assertThrows( NBTLimitException.class, () ->
{
ByteArrayTag byteArrayTag = new ByteArrayTag( new byte[ 1000 ] );
byte[] arr = Tag.toByteArray( byteArrayTag );
Tag.fromByteArray( arr, new NBTLimiter( 100, 1 ) );
} );
assertDoesNotThrow( () ->
{
ByteArrayTag byteArrayTag = new ByteArrayTag( new byte[ 1000 ] );
byte[] arr = Tag.toByteArray( byteArrayTag );
Tag.fromByteArray( arr, new NBTLimiter( 99999999, 1 ) );
} );
}
@Test
public void testDepth()
{
assertThrows( NBTLimitException.class, () ->
{
ListTag root = new ListTag( new ArrayList<>(), Tag.LIST );
root.getValue().add( new ListTag( new ArrayList<>(), Tag.LIST ) );
byte[] arr = Tag.toByteArray( root );
Tag.fromByteArray( arr, new NBTLimiter( 100, 1 ) );
} );
assertDoesNotThrow( () ->
{
ListTag root = new ListTag( new ArrayList<>(), Tag.LIST );
root.getValue().add( new ListTag( new ArrayList<>(), Tag.LIST ) );
byte[] arr = Tag.toByteArray( root );
Tag.fromByteArray( arr, new NBTLimiter( 100, 2 ) );
} );
}
}

View File

@ -0,0 +1,247 @@
package net.md_5.bungee.nbt;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import net.md_5.bungee.nbt.exception.NBTFormatException;
import net.md_5.bungee.nbt.type.ByteArrayTag;
import net.md_5.bungee.nbt.type.ByteTag;
import net.md_5.bungee.nbt.type.CompoundTag;
import net.md_5.bungee.nbt.type.DoubleTag;
import net.md_5.bungee.nbt.type.EndTag;
import net.md_5.bungee.nbt.type.FloatTag;
import net.md_5.bungee.nbt.type.IntArrayTag;
import net.md_5.bungee.nbt.type.IntTag;
import net.md_5.bungee.nbt.type.ListTag;
import net.md_5.bungee.nbt.type.LongArrayTag;
import net.md_5.bungee.nbt.type.LongTag;
import net.md_5.bungee.nbt.type.ShortTag;
import net.md_5.bungee.nbt.type.StringTag;
import org.junit.jupiter.api.Test;
public class NBTTagTest
{
@Test
public void testByteTag() throws IOException
{
byte[] tests = new byte[]
{
0, Byte.MAX_VALUE, Byte.MIN_VALUE
};
for ( byte value : tests )
{
ByteTag byteTag = new ByteTag( value );
byte[] deserialized = Tag.toByteArray( byteTag );
ByteTag reSerialized = (ByteTag) Tag.fromByteArray( deserialized );
assertEquals( byteTag, reSerialized );
}
}
@Test
public void testShortTag() throws IOException
{
short[] tests = new short[]
{
0, Short.MAX_VALUE, Short.MIN_VALUE
};
for ( short value : tests )
{
ShortTag tag = new ShortTag( value );
byte[] deserialized = Tag.toByteArray( tag );
ShortTag reSerialized = (ShortTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
}
@Test
public void testIntTag() throws IOException
{
int[] tests = new int[]
{
0, Integer.MAX_VALUE, Integer.MIN_VALUE
};
for ( int value : tests )
{
IntTag tag = new IntTag( value );
byte[] deserialized = Tag.toByteArray( tag );
IntTag reSerialized = (IntTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
}
@Test
public void testLongTag() throws IOException
{
long[] tests = new long[]
{
0, Long.MAX_VALUE, Long.MIN_VALUE
};
for ( long value : tests )
{
LongTag tag = new LongTag( value );
byte[] deserialized = Tag.toByteArray( tag );
LongTag reSerialized = (LongTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
}
@Test
public void testDoubleTag() throws IOException
{
double[] tests = new double[]
{
0, Double.MAX_VALUE, Double.MIN_VALUE, Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
};
for ( double value : tests )
{
DoubleTag tag = new DoubleTag( value );
byte[] deserialized = Tag.toByteArray( tag );
DoubleTag reSerialized = (DoubleTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
}
@Test
public void testFloatTag() throws IOException
{
float[] tests = new float[]
{
0, Float.MAX_VALUE, Float.MIN_VALUE, Float.NaN, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY
};
for ( float value : tests )
{
FloatTag tag = new FloatTag( value );
byte[] deserialized = Tag.toByteArray( tag );
FloatTag reSerialized = (FloatTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
}
@Test
public void testStringTag() throws IOException
{
String[] tests = new String[]
{
"Outfluencer", "", String.valueOf( System.currentTimeMillis() ), "BungeeCord", new Object().toString()
};
for ( String value : tests )
{
StringTag tag = new StringTag( value );
byte[] deserialized = Tag.toByteArray( tag );
StringTag reSerialized = (StringTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
}
@Test
public void testByteArrayTag() throws IOException
{
byte[] value = new byte[ 1024 ];
ThreadLocalRandom.current().nextBytes( value );
ByteArrayTag tag = new ByteArrayTag( value );
byte[] deserialized = Tag.toByteArray( tag );
ByteArrayTag reSerialized = (ByteArrayTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
value = new byte[ 0 ];
ThreadLocalRandom.current().nextBytes( value );
tag = new ByteArrayTag( value );
deserialized = Tag.toByteArray( tag );
reSerialized = (ByteArrayTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
@Test
public void testIntArrayTag() throws IOException
{
int[] value = new int[ 1024 ];
for ( int i = 0; i < value.length; i++ )
{
value[i] = ThreadLocalRandom.current().nextInt();
}
IntArrayTag tag = new IntArrayTag( value );
byte[] deserialized = Tag.toByteArray( tag );
IntArrayTag reSerialized = (IntArrayTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
value = new int[ 0 ];
tag = new IntArrayTag( value );
deserialized = Tag.toByteArray( tag );
reSerialized = (IntArrayTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
@Test
public void testLongArrayTag() throws IOException
{
long[] value = new long[ 1024 ];
for ( int i = 0; i < value.length; i++ )
{
value[i] = ThreadLocalRandom.current().nextLong();
}
LongArrayTag tag = new LongArrayTag( value );
byte[] deserialized = Tag.toByteArray( tag );
LongArrayTag reSerialized = (LongArrayTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
value = new long[ 0 ];
tag = new LongArrayTag( value );
deserialized = Tag.toByteArray( tag );
reSerialized = (LongArrayTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
@Test
public void testListTag() throws IOException
{
List<TypedTag> tags = new ArrayList<>();
for ( int i = 0; i < 100; i++ )
{
tags.add( new IntTag( i ) );
}
ListTag listTag = new ListTag( tags, Tag.INT );
byte[] deserialized = Tag.toByteArray( listTag );
ListTag reSerialized = (ListTag) Tag.fromByteArray( deserialized );
assertEquals( reSerialized.getValue(), tags );
List<TypedTag> tags2 = new ArrayList<>();
for ( int i = 0; i < 100; i++ )
{
tags2.add( new IntTag( i ) );
}
tags2.add( new ByteTag( Byte.MIN_VALUE ) );
assertThrows( NBTFormatException.class, () -> Tag.toByteArray( new ListTag( tags2, Tag.INT ) ) );
assertThrows( NBTFormatException.class, () -> Tag.toByteArray( new ListTag( Collections.singletonList( new EndTag() ), Tag.END ) ) );
}
@Test
public void testCompoundTag() throws IOException
{
Map<String, TypedTag> map = new HashMap<>();
for ( int i = 0; i < 100; i++ )
{
map.put( "" + i, new IntTag( i ) );
map.put( "a" + i, new ByteTag( (byte) i ) );
map.put( "b" + i, new ShortTag( (short) i ) );
map.put( "c" + i, new LongTag( i ) );
map.put( "f" + i, new FloatTag( i ) );
map.put( "d" + i, new DoubleTag( i ) );
}
CompoundTag compoundTag = new CompoundTag( map );
byte[] deserialized = Tag.toByteArray( compoundTag );
CompoundTag reSerialized = (CompoundTag) Tag.fromByteArray( deserialized );
assertEquals( reSerialized, compoundTag );
Map<String, TypedTag> map2 = new LinkedHashMap<>();
map2.put( "", new EndTag() );
CompoundTag compoundTag2 = new CompoundTag( map2 );
assertThrows( NBTFormatException.class, () -> Tag.toByteArray( compoundTag2 ) );
}
}

View File

@ -0,0 +1,29 @@
package net.md_5.bungee.nbt;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Objects;
import net.md_5.bungee.nbt.limit.NBTLimiter;
import net.md_5.bungee.nbt.type.CompoundTag;
import org.junit.jupiter.api.Test;
public class PlayerDataTest
{
@Test
public void testPlayerData() throws URISyntaxException, IOException
{
ClassLoader classLoader = PlayerDataTest.class.getClassLoader();
File file = new File( Objects.requireNonNull( classLoader.getResource( "playerdata.nbt" ) ).toURI() );
FileInputStream fileInputStream = new FileInputStream( file );
DataInputStream dis = new DataInputStream( fileInputStream );
NamedTag namedTag = Tag.readNamedTag( dis, NBTLimiter.unlimitedSize() );
assertInstanceOf( CompoundTag.class, namedTag.getTag() );
assertEquals( namedTag.getName(), "" );
}
}

Binary file not shown.

19
pom.xml
View File

@ -5,7 +5,7 @@
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>pom</packaging>
<name>BungeeCord-Parent</name>
@ -35,14 +35,17 @@
<module>bootstrap</module>
<module>chat</module>
<module>config</module>
<module>dialog</module>
<module>event</module>
<module>log</module>
<module>module</module>
<module>protocol</module>
<module>proxy</module>
<module>query</module>
<module>serializer</module>
<module>slf4j</module>
<module>native</module>
<module>nbt</module>
</modules>
<scm>
@ -72,7 +75,7 @@
<properties>
<build.number>unknown</build.number>
<lombok.version>1.18.36</lombok.version>
<lombok.version>1.18.38</lombok.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -83,7 +86,7 @@
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<version>4.1.119.Final</version>
<version>4.2.1.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@ -99,15 +102,9 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.3.1-jre</version>
<version>33.4.8-jre</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>findbugs-annotations</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations-java5</artifactId>
@ -182,7 +179,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.23</version>
<version>1.24</version>
<executions>
<execution>
<phase>process-classes</phase>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-protocol</artifactId>
<version>1.21-R0.3-SNAPSHOT</version>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Protocol</name>
@ -36,7 +36,13 @@
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-chat</artifactId>
<artifactId>bungeecord-serializer</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-nbt</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
@ -46,15 +52,9 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.sf.trove4j</groupId>
<artifactId>core</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>se.llbit</groupId>
<artifactId>jo-nbt</artifactId>
<version>1.3.0</version>
<groupId>it.unimi.dsi</groupId>
<artifactId>fastutil-core</artifactId>
<version>8.5.15</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@ -1,7 +1,9 @@
package net.md_5.bungee.protocol;
import net.md_5.bungee.protocol.packet.BossBar;
import net.md_5.bungee.protocol.packet.BundleDelimiter;
import net.md_5.bungee.protocol.packet.Chat;
import net.md_5.bungee.protocol.packet.ClearDialog;
import net.md_5.bungee.protocol.packet.ClearTitles;
import net.md_5.bungee.protocol.packet.ClientChat;
import net.md_5.bungee.protocol.packet.ClientCommand;
@ -10,6 +12,7 @@ import net.md_5.bungee.protocol.packet.ClientStatus;
import net.md_5.bungee.protocol.packet.Commands;
import net.md_5.bungee.protocol.packet.CookieRequest;
import net.md_5.bungee.protocol.packet.CookieResponse;
import net.md_5.bungee.protocol.packet.CustomClickAction;
import net.md_5.bungee.protocol.packet.DisconnectReportDetails;
import net.md_5.bungee.protocol.packet.EncryptionRequest;
import net.md_5.bungee.protocol.packet.EncryptionResponse;
@ -41,6 +44,8 @@ import net.md_5.bungee.protocol.packet.ScoreboardScoreReset;
import net.md_5.bungee.protocol.packet.ServerData;
import net.md_5.bungee.protocol.packet.ServerLinks;
import net.md_5.bungee.protocol.packet.SetCompression;
import net.md_5.bungee.protocol.packet.ShowDialog;
import net.md_5.bungee.protocol.packet.ShowDialogDirect;
import net.md_5.bungee.protocol.packet.StartConfiguration;
import net.md_5.bungee.protocol.packet.StatusRequest;
import net.md_5.bungee.protocol.packet.StatusResponse;
@ -278,4 +283,24 @@ public abstract class AbstractPacketHandler
public void handle(ServerLinks serverLinks) throws Exception
{
}
public void handle(ShowDialog showDialog) throws Exception
{
}
public void handle(ShowDialogDirect showDialogDirect) throws Exception
{
}
public void handle(ClearDialog clearDialog) throws Exception
{
}
public void handle(CustomClickAction customClickAction) throws Exception
{
}
public void handle(BundleDelimiter bundleDelimiter) throws Exception
{
}
}

View File

@ -21,10 +21,11 @@ import java.util.function.BiConsumer;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentStyle;
import se.llbit.nbt.ErrorTag;
import se.llbit.nbt.NamedTag;
import se.llbit.nbt.SpecificTag;
import se.llbit.nbt.Tag;
import net.md_5.bungee.nbt.NamedTag;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
import net.md_5.bungee.nbt.type.EndTag;
@RequiredArgsConstructor
public abstract class DefinedPacket
@ -116,7 +117,7 @@ public abstract class DefinedPacket
{
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_20_3 )
{
SpecificTag nbt = (SpecificTag) readTag( buf, protocolVersion );
TypedTag nbt = (TypedTag) readTag( buf, protocolVersion );
JsonElement json = TagUtil.toJson( nbt );
return ChatSerializer.forVersion( protocolVersion ).deserialize( json );
@ -130,7 +131,7 @@ public abstract class DefinedPacket
public static ComponentStyle readComponentStyle(ByteBuf buf, int protocolVersion)
{
SpecificTag nbt = (SpecificTag) readTag( buf, protocolVersion );
TypedTag nbt = (TypedTag) readTag( buf, protocolVersion );
JsonElement json = TagUtil.toJson( nbt );
return ChatSerializer.forVersion( protocolVersion ).deserializeStyle( json );
@ -152,8 +153,7 @@ public abstract class DefinedPacket
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_20_3 )
{
JsonElement json = ChatSerializer.forVersion( protocolVersion ).toJson( message );
SpecificTag nbt = TagUtil.fromJson( json );
TypedTag nbt = TagUtil.fromJson( json );
writeTag( nbt, buf, protocolVersion );
} else
{
@ -166,8 +166,7 @@ public abstract class DefinedPacket
public static void writeComponentStyle(ComponentStyle style, ByteBuf buf, int protocolVersion)
{
JsonElement json = ChatSerializer.forVersion( protocolVersion ).toJson( style );
SpecificTag nbt = TagUtil.fromJson( json );
TypedTag nbt = TagUtil.fromJson( json );
writeTag( nbt, buf, protocolVersion );
}
@ -454,31 +453,33 @@ public abstract class DefinedPacket
}
public static Tag readTag(ByteBuf input, int protocolVersion)
{
return readTag( input, protocolVersion, new NBTLimiter( 1 << 21 ) );
}
public static Tag readTag(ByteBuf input, int protocolVersion, NBTLimiter limiter)
{
DataInputStream in = new DataInputStream( new ByteBufInputStream( input ) );
Tag tag;
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_20_2 )
try
{
try
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_20_2 )
{
byte type = in.readByte();
if ( type == 0 )
{
return Tag.END;
return EndTag.INSTANCE;
} else
{
tag = SpecificTag.read( type, in );
return Tag.readById( type, in, limiter );
}
} catch ( IOException ex )
{
tag = new ErrorTag( "IOException while reading tag type:\n" + ex.getMessage() );
}
} else
NamedTag namedTag = new NamedTag();
namedTag.read( in, limiter );
return namedTag;
} catch ( IOException ex )
{
tag = NamedTag.read( in );
throw new RuntimeException( "Exception reading tag", ex );
}
Preconditions.checkArgument( !tag.isError(), "Error reading tag: %s", tag.error() );
return tag;
}
public static void writeTag(Tag tag, ByteBuf output, int protocolVersion)
@ -486,11 +487,11 @@ public abstract class DefinedPacket
DataOutputStream out = new DataOutputStream( new ByteBufOutputStream( output ) );
try
{
if ( tag instanceof SpecificTag )
if ( tag instanceof TypedTag )
{
SpecificTag specificTag = (SpecificTag) tag;
specificTag.writeType( out );
specificTag.write( out );
TypedTag typedTag = (TypedTag) tag;
out.writeByte( typedTag.getId() );
typedTag.write( out );
} else
{
tag.write( out );

View File

@ -12,12 +12,17 @@ import lombok.Setter;
public class MinecraftDecoder extends MessageToMessageDecoder<ByteBuf>
{
public MinecraftDecoder(Protocol protocol, boolean server, int protocolVersion)
{
this( protocol, server, protocolVersion, shouldCopyBuffer( protocol, protocolVersion ) );
}
@Getter
@Setter
private Protocol protocol;
private final boolean server;
@Setter
private int protocolVersion;
private boolean copyBuffer;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception
@ -30,8 +35,7 @@ public class MinecraftDecoder extends MessageToMessageDecoder<ByteBuf>
}
Protocol.DirectionData prot = ( server ) ? protocol.TO_SERVER : protocol.TO_CLIENT;
ByteBuf slice = in.copy(); // Can't slice this one due to EntityMap :(
ByteBuf slice = ( copyBuffer ) ? in.copy() : in.retainedSlice();
try
{
int packetId = DefinedPacket.readVarInt( in );
@ -60,4 +64,17 @@ public class MinecraftDecoder extends MessageToMessageDecoder<ByteBuf>
}
}
}
public void setProtocol(Protocol protocol)
{
this.protocol = protocol;
this.copyBuffer = shouldCopyBuffer( protocol, protocolVersion );
}
private static boolean shouldCopyBuffer(Protocol protocol, int protocolVersion)
{
// We only use the entity map in game state, we can avoid many buffer copies by checking this
// EntityMap is removed for 1.20.2 and up
return protocol == Protocol.GAME && protocolVersion < ProtocolConstants.MINECRAFT_1_20_2;
}
}

Some files were not shown because too many files have changed in this diff Show More