SPIGOT-8024, #3811, #3812: Add versioned chat serialization (beta)

This commit is contained in:
md_5 2025-03-28 07:01:06 +11:00
parent 77b81f2612
commit 7587f03306
No known key found for this signature in database
GPG Key ID: E8E901AC7C617C11
24 changed files with 464 additions and 279 deletions

View File

@ -3,9 +3,7 @@ package net.md_5.bungee.api.chat;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.jetbrains.annotations.ApiStatus;
@Getter
@ToString
@ -25,13 +23,6 @@ public final class ClickEvent
*/
private final String value;
/**
* Returns whether this click event is used for version above 1.21.4
*/
@Setter
@ApiStatus.Internal
private boolean v1_21_5 = false;
public enum Action
{

View File

@ -13,7 +13,7 @@ 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.ComponentSerializer;
import net.md_5.bungee.chat.VersionedComponentSerializer;
import org.jetbrains.annotations.ApiStatus;
@Getter
@ -38,30 +38,6 @@ public final class HoverEvent
@ApiStatus.Internal
private boolean legacy = false;
/**
* Returns whether this hover event is used for version above 1.21.4
*/
@ApiStatus.Internal
private boolean v1_21_5 = false;
/**
* Set the compatibility to 1.21.5, also modifies the underlying entities.
*
* @param v1_21_5 the compatibility to set
*/
@ApiStatus.Internal
public void setV1_21_5(boolean v1_21_5)
{
this.v1_21_5 = v1_21_5;
for ( Content content : contents )
{
if ( content instanceof Entity )
{
( (Entity) content ).setV1_21_5( v1_21_5 );
}
}
}
/**
* Creates event with an action and a list of contents.
*
@ -106,7 +82,7 @@ public final class HoverEvent
return (BaseComponent[]) ( (Text) content ).getValue();
}
TextComponent component = new TextComponent( ComponentSerializer.toString( content ) );
TextComponent component = new TextComponent( VersionedComponentSerializer.getDefault().toString( content ) );
return new BaseComponent[]
{
component

View File

@ -7,7 +7,6 @@ import lombok.NonNull;
import lombok.ToString;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.HoverEvent;
import org.jetbrains.annotations.ApiStatus;
@Data
@AllArgsConstructor
@ -16,18 +15,6 @@ import org.jetbrains.annotations.ApiStatus;
public class Entity extends Content
{
/**
* Required for backwards compatibility.
*
* @param type the type of the entity, for example 'minecraft:pig'
* @param id for example '6cb1b229-ce5c-4179-af8d-eea185c25963'
* @param name the name of the entity
*/
public Entity(String type, @NonNull String id, BaseComponent name)
{
this( type, id, name, false );
}
/**
* Namespaced entity ID.
*
@ -48,12 +35,6 @@ public class Entity extends Content
*/
private BaseComponent name;
/**
* True if this entity is for 1.21.5 or later
*/
@ApiStatus.Internal
private boolean v1_21_5;
@Override
public HoverEvent.Action requiredAction()
{

View File

@ -10,10 +10,17 @@ import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
import java.util.UUID;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.chat.BaseComponentSerializer;
import net.md_5.bungee.chat.VersionedComponentSerializer;
public class EntitySerializer implements JsonSerializer<Entity>, JsonDeserializer<Entity>
public class EntitySerializer extends BaseComponentSerializer implements JsonSerializer<Entity>, JsonDeserializer<Entity>
{
public EntitySerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override
public Entity deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
{
@ -34,8 +41,7 @@ public class EntitySerializer implements JsonSerializer<Entity>, JsonDeserialize
return new Entity(
( value.has( newEntity ? "id" : "type" ) ) ? value.get( newEntity ? "id" : "type" ).getAsString() : null,
idString,
( value.has( "name" ) ) ? context.deserialize( value.get( "name" ), BaseComponent.class ) : null,
newEntity
( value.has( "name" ) ) ? context.deserialize( value.get( "name" ), BaseComponent.class ) : null
);
}
@ -44,8 +50,20 @@ public class EntitySerializer implements JsonSerializer<Entity>, JsonDeserialize
{
JsonObject object = new JsonObject();
object.addProperty( content.isV1_21_5() ? "id" : "type", ( content.getType() != null ) ? content.getType() : "minecraft:pig" );
object.addProperty( content.isV1_21_5() ? "uuid" : "id", content.getId() );
switch ( serializer.getVersion() )
{
case V1_21_5:
object.addProperty( "id", ( content.getType() != null ) ? content.getType() : "minecraft:pig" );
object.addProperty( "uuid", content.getId() );
break;
case V1_16:
object.addProperty( "type", ( content.getType() != null ) ? content.getType() : "minecraft:pig" );
object.addProperty( "id", content.getId() );
break;
default:
throw new IllegalArgumentException( "Unknown version " + serializer.getVersion() );
}
if ( content.getName() != null )
{
object.add( "name", context.serialize( content.getName() ) );

View File

@ -10,15 +10,19 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Locale;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentStyle;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Content;
@RequiredArgsConstructor
public class BaseComponentSerializer
{
protected final VersionedComponentSerializer serializer;
protected void deserialize(JsonObject object, BaseComponent component, JsonDeserializationContext context)
{
component.applyStyle( context.deserialize( object, ComponentStyle.class ) );
@ -59,7 +63,6 @@ public class BaseComponentSerializer
component.setClickEvent( new ClickEvent( action, ( clickEvent.has( "value" ) ) ? clickEvent.get( "value" ).getAsString() : "" ) );
break;
}
component.getClickEvent().setV1_21_5( true );
} else
{
component.setClickEvent( new ClickEvent( action, ( clickEvent.has( "value" ) ) ? clickEvent.get( "value" ).getAsString() : "" ) );
@ -101,7 +104,6 @@ public class BaseComponentSerializer
};
}
hoverEvent = new HoverEvent( action, new ArrayList<>( Arrays.asList( list ) ) );
hoverEvent.setV1_21_5( newHoverEvent );
}
} else
{
@ -141,15 +143,15 @@ public class BaseComponentSerializer
protected void serialize(JsonObject object, BaseComponent component, JsonSerializationContext context)
{
boolean first = false;
if ( ComponentSerializer.serializedComponents.get() == null )
if ( VersionedComponentSerializer.serializedComponents.get() == null )
{
first = true;
ComponentSerializer.serializedComponents.set( Collections.newSetFromMap( new IdentityHashMap<BaseComponent, Boolean>() ) );
VersionedComponentSerializer.serializedComponents.set( Collections.newSetFromMap( new IdentityHashMap<BaseComponent, Boolean>() ) );
}
try
{
Preconditions.checkArgument( !ComponentSerializer.serializedComponents.get().contains( component ), "Component loop" );
ComponentSerializer.serializedComponents.get().add( component );
Preconditions.checkArgument( !VersionedComponentSerializer.serializedComponents.get().contains( component ), "Component loop" );
VersionedComponentSerializer.serializedComponents.get().add( component );
ComponentStyleSerializer.serializeTo( component.getStyle(), object );
@ -164,30 +166,34 @@ public class BaseComponentSerializer
JsonObject clickEvent = new JsonObject();
String actionName = component.getClickEvent().getAction().toString().toLowerCase( Locale.ROOT );
clickEvent.addProperty( "action", actionName.toLowerCase( Locale.ROOT ) );
if ( component.getClickEvent().isV1_21_5() )
switch ( serializer.getVersion() )
{
ClickEvent.Action action = ClickEvent.Action.valueOf( actionName.toUpperCase( Locale.ROOT ) );
switch ( action )
{
case OPEN_URL:
clickEvent.addProperty( "url", component.getClickEvent().getValue() );
break;
case RUN_COMMAND:
case SUGGEST_COMMAND:
clickEvent.addProperty( "command", component.getClickEvent().getValue() );
break;
case CHANGE_PAGE:
clickEvent.addProperty( "page", Integer.parseInt( component.getClickEvent().getValue() ) );
break;
default:
clickEvent.addProperty( "value", component.getClickEvent().getValue() );
break;
}
object.add( "click_event", clickEvent );
} else
{
clickEvent.addProperty( "value", component.getClickEvent().getValue() );
object.add( "clickEvent", clickEvent );
case V1_21_5:
ClickEvent.Action action = ClickEvent.Action.valueOf( actionName.toUpperCase( Locale.ROOT ) );
switch ( action )
{
case OPEN_URL:
clickEvent.addProperty( "url", component.getClickEvent().getValue() );
break;
case RUN_COMMAND:
case SUGGEST_COMMAND:
clickEvent.addProperty( "command", component.getClickEvent().getValue() );
break;
case CHANGE_PAGE:
clickEvent.addProperty( "page", Integer.parseInt( component.getClickEvent().getValue() ) );
break;
default:
clickEvent.addProperty( "value", component.getClickEvent().getValue() );
break;
}
object.add( "click_event", clickEvent );
break;
case V1_16:
clickEvent.addProperty( "value", component.getClickEvent().getValue() );
object.add( "clickEvent", clickEvent );
break;
default:
throw new IllegalArgumentException( "Unknown version " + serializer.getVersion() );
}
}
@ -195,32 +201,44 @@ public class BaseComponentSerializer
{
JsonObject hoverEvent = new JsonObject();
hoverEvent.addProperty( "action", component.getHoverEvent().getAction().toString().toLowerCase( Locale.ROOT ) );
boolean newFormat = component.getHoverEvent().isV1_21_5();
if ( component.getHoverEvent().isLegacy() )
{
hoverEvent.add( "value", context.serialize( component.getHoverEvent().getContents().get( 0 ) ) );
} else
{
if ( newFormat )
switch ( serializer.getVersion() )
{
if ( component.getHoverEvent().getAction() == HoverEvent.Action.SHOW_ITEM || component.getHoverEvent().getAction() == HoverEvent.Action.SHOW_ENTITY )
{
JsonObject inlined = context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
? component.getHoverEvent().getContents().get( 0 ) : component.getHoverEvent().getContents() ).getAsJsonObject();
inlined.entrySet().forEach( entry -> hoverEvent.add( entry.getKey(), entry.getValue() ) );
} else
{
hoverEvent.add( "value", context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
case V1_21_5:
if ( component.getHoverEvent().getAction() == HoverEvent.Action.SHOW_ITEM || component.getHoverEvent().getAction() == HoverEvent.Action.SHOW_ENTITY )
{
JsonObject inlined = context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
? component.getHoverEvent().getContents().get( 0 ) : component.getHoverEvent().getContents() ).getAsJsonObject();
inlined.entrySet().forEach( entry -> hoverEvent.add( entry.getKey(), entry.getValue() ) );
} else
{
hoverEvent.add( "value", context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
? component.getHoverEvent().getContents().get( 0 ) : component.getHoverEvent().getContents() ) );
}
break;
case V1_16:
hoverEvent.add( "contents", context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
? component.getHoverEvent().getContents().get( 0 ) : component.getHoverEvent().getContents() ) );
}
} else
{
hoverEvent.add( "contents", context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
? component.getHoverEvent().getContents().get( 0 ) : component.getHoverEvent().getContents() ) );
break;
default:
throw new IllegalArgumentException( "Unknown version " + serializer.getVersion() );
}
}
object.add( newFormat ? "hover_event" : "hoverEvent", hoverEvent );
switch ( serializer.getVersion() )
{
case V1_21_5:
object.add( "hover_event", hoverEvent );
break;
case V1_16:
object.add( "hoverEvent", hoverEvent );
break;
default:
throw new IllegalArgumentException( "Unknown version " + serializer.getVersion() );
}
}
if ( component.getExtra() != null )
@ -229,10 +247,10 @@ public class BaseComponentSerializer
}
} finally
{
ComponentSerializer.serializedComponents.get().remove( component );
VersionedComponentSerializer.serializedComponents.get().remove( component );
if ( first )
{
ComponentSerializer.serializedComponents.set( null );
VersionedComponentSerializer.serializedComponents.set( null );
}
}
}

View File

@ -0,0 +1,10 @@
package net.md_5.bungee.chat;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public enum ChatVersion
{
V1_16,
V1_21_5;
}

View File

@ -1,52 +1,17 @@
package net.md_5.bungee.chat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import java.lang.reflect.Type;
import java.util.Set;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentStyle;
import net.md_5.bungee.api.chat.ItemTag;
import net.md_5.bungee.api.chat.KeybindComponent;
import net.md_5.bungee.api.chat.ScoreComponent;
import net.md_5.bungee.api.chat.SelectorComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
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.EntitySerializer;
import net.md_5.bungee.api.chat.hover.content.Item;
import net.md_5.bungee.api.chat.hover.content.ItemSerializer;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.md_5.bungee.api.chat.hover.content.TextSerializer;
public class ComponentSerializer implements JsonDeserializer<BaseComponent>
{
private static final Gson gson = new GsonBuilder().
registerTypeAdapter( BaseComponent.class, new ComponentSerializer() ).
registerTypeAdapter( TextComponent.class, new TextComponentSerializer() ).
registerTypeAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() ).
registerTypeAdapter( KeybindComponent.class, new KeybindComponentSerializer() ).
registerTypeAdapter( ScoreComponent.class, new ScoreComponentSerializer() ).
registerTypeAdapter( SelectorComponent.class, new SelectorComponentSerializer() ).
registerTypeAdapter( ComponentStyle.class, new ComponentStyleSerializer() ).
registerTypeAdapter( Entity.class, new EntitySerializer() ).
registerTypeAdapter( Text.class, new TextSerializer() ).
registerTypeAdapter( Item.class, new ItemSerializer() ).
registerTypeAdapter( ItemTag.class, new ItemTag.Serializer() ).
create();
public static final ThreadLocal<Set<BaseComponent>> serializedComponents = new ThreadLocal<Set<BaseComponent>>();
/**
* Parse a JSON-compliant String as an array of base components. The input
* can be one of either an array of components, or a single component
@ -66,18 +31,7 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
*/
public static BaseComponent[] parse(String json)
{
JsonElement jsonElement = JsonParser.parseString( json );
if ( jsonElement.isJsonArray() )
{
return gson.fromJson( jsonElement, BaseComponent[].class );
} else
{
return new BaseComponent[]
{
gson.fromJson( jsonElement, BaseComponent.class )
};
}
return VersionedComponentSerializer.getDefault().parse( json );
}
/**
@ -90,9 +44,7 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
*/
public static BaseComponent deserialize(String json)
{
JsonElement jsonElement = JsonParser.parseString( json );
return deserialize( jsonElement );
return VersionedComponentSerializer.getDefault().deserialize( json );
}
/**
@ -105,20 +57,7 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
*/
public static BaseComponent deserialize(JsonElement jsonElement)
{
if ( jsonElement instanceof JsonPrimitive )
{
JsonPrimitive primitive = (JsonPrimitive) jsonElement;
if ( primitive.isString() )
{
return new TextComponent( primitive.getAsString() );
}
} else if ( jsonElement instanceof JsonArray )
{
BaseComponent[] array = gson.fromJson( jsonElement, BaseComponent[].class );
return TextComponent.fromArray( array );
}
return gson.fromJson( jsonElement, BaseComponent.class );
return VersionedComponentSerializer.getDefault().deserialize( jsonElement );
}
/**
@ -131,9 +70,7 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
*/
public static ComponentStyle deserializeStyle(String json)
{
JsonElement jsonElement = JsonParser.parseString( json );
return deserializeStyle( jsonElement );
return VersionedComponentSerializer.getDefault().deserializeStyle( json );
}
/**
@ -146,17 +83,17 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
*/
public static ComponentStyle deserializeStyle(JsonElement jsonElement)
{
return gson.fromJson( jsonElement, ComponentStyle.class );
return VersionedComponentSerializer.getDefault().deserializeStyle( jsonElement );
}
public static JsonElement toJson(BaseComponent component)
{
return gson.toJsonTree( component );
return VersionedComponentSerializer.getDefault().toJson( component );
}
public static JsonElement toJson(ComponentStyle style)
{
return gson.toJsonTree( style );
return VersionedComponentSerializer.getDefault().toJson( style );
}
/**
@ -167,7 +104,7 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
@Deprecated
public static String toString(Object object)
{
return gson.toJson( object );
return VersionedComponentSerializer.getDefault().toString( object );
}
/**
@ -178,54 +115,27 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
@Deprecated
public static String toString(Content content)
{
return gson.toJson( content );
return VersionedComponentSerializer.getDefault().toString( content );
}
public static String toString(BaseComponent component)
{
return gson.toJson( component );
return VersionedComponentSerializer.getDefault().toString( component );
}
public static String toString(BaseComponent... components)
{
if ( components.length == 1 )
{
return gson.toJson( components[0] );
} else
{
return gson.toJson( new TextComponent( components ) );
}
return VersionedComponentSerializer.getDefault().toString( components );
}
public static String toString(ComponentStyle style)
{
return gson.toJson( style );
return VersionedComponentSerializer.getDefault().toString( style );
}
@Override
public BaseComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
if ( json.isJsonPrimitive() )
{
return new TextComponent( json.getAsString() );
}
JsonObject object = json.getAsJsonObject();
if ( object.has( "translate" ) )
{
return context.deserialize( json, TranslatableComponent.class );
}
if ( object.has( "keybind" ) )
{
return context.deserialize( json, KeybindComponent.class );
}
if ( object.has( "score" ) )
{
return context.deserialize( json, ScoreComponent.class );
}
if ( object.has( "selector" ) )
{
return context.deserialize( json, SelectorComponent.class );
}
return context.deserialize( json, TextComponent.class );
return VersionedComponentSerializer.getDefault().deserialize( json, typeOfT, context );
}
}

View File

@ -13,6 +13,11 @@ import net.md_5.bungee.api.chat.KeybindComponent;
public class KeybindComponentSerializer extends BaseComponentSerializer implements JsonSerializer<KeybindComponent>, JsonDeserializer<KeybindComponent>
{
public KeybindComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override
public KeybindComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{

View File

@ -13,6 +13,11 @@ import net.md_5.bungee.api.chat.ScoreComponent;
public class ScoreComponentSerializer extends BaseComponentSerializer implements JsonSerializer<ScoreComponent>, JsonDeserializer<ScoreComponent>
{
public ScoreComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override
public ScoreComponent deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
{

View File

@ -13,6 +13,11 @@ import net.md_5.bungee.api.chat.SelectorComponent;
public class SelectorComponentSerializer extends BaseComponentSerializer implements JsonSerializer<SelectorComponent>, JsonDeserializer<SelectorComponent>
{
public SelectorComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override
public SelectorComponent deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
{
@ -27,7 +32,7 @@ public class SelectorComponentSerializer extends BaseComponentSerializer impleme
JsonElement separator = object.get( "separator" );
if ( separator != null )
{
component.setSeparator( ComponentSerializer.deserialize( separator.getAsString() ) );
component.setSeparator( serializer.deserialize( separator.getAsString() ) );
}
deserialize( object, component, context );
@ -43,7 +48,7 @@ public class SelectorComponentSerializer extends BaseComponentSerializer impleme
if ( component.getSeparator() != null )
{
object.addProperty( "separator", ComponentSerializer.toString( component.getSeparator() ) );
object.addProperty( "separator", serializer.toString( component.getSeparator() ) );
}
return object;
}

View File

@ -13,6 +13,11 @@ import net.md_5.bungee.api.chat.TextComponent;
public class TextComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TextComponent>, JsonDeserializer<TextComponent>
{
public TextComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override
public TextComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{

View File

@ -15,6 +15,11 @@ import net.md_5.bungee.api.chat.TranslatableComponent;
public class TranslatableComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TranslatableComponent>, JsonDeserializer<TranslatableComponent>
{
public TranslatableComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override
public TranslatableComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{

View File

@ -0,0 +1,269 @@
package net.md_5.bungee.chat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import java.lang.reflect.Type;
import java.util.Set;
import lombok.Getter;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentStyle;
import net.md_5.bungee.api.chat.ItemTag;
import net.md_5.bungee.api.chat.KeybindComponent;
import net.md_5.bungee.api.chat.ScoreComponent;
import net.md_5.bungee.api.chat.SelectorComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
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.EntitySerializer;
import net.md_5.bungee.api.chat.hover.content.Item;
import net.md_5.bungee.api.chat.hover.content.ItemSerializer;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.md_5.bungee.api.chat.hover.content.TextSerializer;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Experimental
public class VersionedComponentSerializer implements JsonDeserializer<BaseComponent>
{
@Getter
@ApiStatus.Internal
private final Gson gson;
@Getter
@ApiStatus.Internal
private final ChatVersion version;
public VersionedComponentSerializer(ChatVersion version)
{
this.version = version;
this.gson = new GsonBuilder().
registerTypeAdapter( BaseComponent.class, this ).
registerTypeAdapter( TextComponent.class, new TextComponentSerializer( this ) ).
registerTypeAdapter( TranslatableComponent.class, new TranslatableComponentSerializer( this ) ).
registerTypeAdapter( KeybindComponent.class, new KeybindComponentSerializer( this ) ).
registerTypeAdapter( ScoreComponent.class, new ScoreComponentSerializer( this ) ).
registerTypeAdapter( SelectorComponent.class, new SelectorComponentSerializer( this ) ).
registerTypeAdapter( ComponentStyle.class, new ComponentStyleSerializer() ).
registerTypeAdapter( Entity.class, new EntitySerializer( this ) ).
registerTypeAdapter( Text.class, new TextSerializer() ).
registerTypeAdapter( Item.class, new ItemSerializer() ).
registerTypeAdapter( ItemTag.class, new ItemTag.Serializer() ).
create();
}
private static final VersionedComponentSerializer v1_16 = new VersionedComponentSerializer( ChatVersion.V1_16 );
private static final VersionedComponentSerializer v1_21_5 = new VersionedComponentSerializer( ChatVersion.V1_21_5 );
public static VersionedComponentSerializer forVersion(ChatVersion version)
{
switch ( version )
{
case V1_16:
return v1_16;
case V1_21_5:
return v1_21_5;
default:
throw new IllegalArgumentException( "Unknown version " + version );
}
}
@Deprecated
@ApiStatus.Internal
public static VersionedComponentSerializer getDefault()
{
return v1_16;
}
@ApiStatus.Internal
public static final ThreadLocal<Set<BaseComponent>> serializedComponents = new ThreadLocal<Set<BaseComponent>>();
/**
* Parse a JSON-compliant String as an array of base components. The input
* can be one of either an array of components, or a single component
* object. If the input is an array, each component will be parsed
* individually and returned in the order that they were parsed. If the
* input is a single component object, a single-valued array with the
* component will be returned.
* <p>
* <strong>NOTE:</strong> {@link #deserialize(String)} is preferred as it
* will parse only one component as opposed to an array of components which
* is non- standard behavior. This method is still appropriate for parsing
* multiple components at once, although such use case is rarely (if at all)
* exhibited in vanilla Minecraft.
*
* @param json the component json to parse
* @return an array of all parsed components
*/
public BaseComponent[] parse(String json)
{
JsonElement jsonElement = JsonParser.parseString( json );
if ( jsonElement.isJsonArray() )
{
return gson.fromJson( jsonElement, BaseComponent[].class );
} else
{
return new BaseComponent[]
{
gson.fromJson( jsonElement, BaseComponent.class )
};
}
}
/**
* Deserialize a JSON-compliant String as a single component.
*
* @param json the component json to parse
* @return the deserialized component
* @throws IllegalArgumentException if anything other than a valid JSON
* component string is passed as input
*/
public BaseComponent deserialize(String json)
{
JsonElement jsonElement = JsonParser.parseString( json );
return deserialize( jsonElement );
}
/**
* Deserialize a JSON element as a single component.
*
* @param jsonElement the component json to parse
* @return the deserialized component
* @throws IllegalArgumentException if anything other than a valid JSON
* component is passed as input
*/
public BaseComponent deserialize(JsonElement jsonElement)
{
if ( jsonElement instanceof JsonPrimitive )
{
JsonPrimitive primitive = (JsonPrimitive) jsonElement;
if ( primitive.isString() )
{
return new TextComponent( primitive.getAsString() );
}
} else if ( jsonElement instanceof JsonArray )
{
BaseComponent[] array = gson.fromJson( jsonElement, BaseComponent[].class );
return TextComponent.fromArray( array );
}
return gson.fromJson( jsonElement, BaseComponent.class );
}
/**
* Deserialize a JSON-compliant String as a component style.
*
* @param json the component style json to parse
* @return the deserialized component style
* @throws IllegalArgumentException if anything other than a valid JSON
* component style string is passed as input
*/
public ComponentStyle deserializeStyle(String json)
{
JsonElement jsonElement = JsonParser.parseString( json );
return deserializeStyle( jsonElement );
}
/**
* Deserialize a JSON element as a component style.
*
* @param jsonElement the component style json to parse
* @return the deserialized component style
* @throws IllegalArgumentException if anything other than a valid JSON
* component style is passed as input
*/
public ComponentStyle deserializeStyle(JsonElement jsonElement)
{
return gson.fromJson( jsonElement, ComponentStyle.class );
}
public JsonElement toJson(BaseComponent component)
{
return gson.toJsonTree( component );
}
public JsonElement toJson(ComponentStyle style)
{
return gson.toJsonTree( style );
}
/**
* @param object the object to serialize
* @return the JSON string representation of the object
* @deprecated Error-prone, be careful which object you input here
*/
@Deprecated
public String toString(Object object)
{
return gson.toJson( object );
}
/**
* @param content the content to serialize
* @return the JSON string representation of the object
* @deprecated for legacy internal use only
*/
@Deprecated
public String toString(Content content)
{
return gson.toJson( content );
}
public String toString(BaseComponent component)
{
return gson.toJson( component );
}
public String toString(BaseComponent... components)
{
if ( components.length == 1 )
{
return gson.toJson( components[0] );
} else
{
return gson.toJson( new TextComponent( components ) );
}
}
public String toString(ComponentStyle style)
{
return gson.toJson( style );
}
@Override
public BaseComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
if ( json.isJsonPrimitive() )
{
return new TextComponent( json.getAsString() );
}
JsonObject object = json.getAsJsonObject();
if ( object.has( "translate" ) )
{
return context.deserialize( json, TranslatableComponent.class );
}
if ( object.has( "keybind" ) )
{
return context.deserialize( json, KeybindComponent.class );
}
if ( object.has( "score" ) )
{
return context.deserialize( json, ScoreComponent.class );
}
if ( object.has( "selector" ) )
{
return context.deserialize( json, SelectorComponent.class );
}
return context.deserialize( json, TextComponent.class );
}
}

View File

@ -0,0 +1,19 @@
package net.md_5.bungee.protocol;
import net.md_5.bungee.chat.ChatVersion;
import net.md_5.bungee.chat.VersionedComponentSerializer;
public class ChatSerializer
{
public static VersionedComponentSerializer forVersion(int protocolVersion)
{
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_21_5 )
{
return VersionedComponentSerializer.forVersion( ChatVersion.V1_21_5 );
} else
{
return VersionedComponentSerializer.forVersion( ChatVersion.V1_16 );
}
}
}

View File

@ -21,7 +21,6 @@ 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 net.md_5.bungee.chat.ComponentSerializer;
import se.llbit.nbt.ErrorTag;
import se.llbit.nbt.NamedTag;
import se.llbit.nbt.SpecificTag;
@ -120,12 +119,12 @@ public abstract class DefinedPacket
SpecificTag nbt = (SpecificTag) readTag( buf, protocolVersion );
JsonElement json = TagUtil.toJson( nbt );
return ComponentSerializer.deserialize( json );
return ChatSerializer.forVersion( protocolVersion ).deserialize( json );
} else
{
String string = readString( buf, maxStringLength );
return ComponentSerializer.deserialize( string );
return ChatSerializer.forVersion( protocolVersion ).deserialize( string );
}
}
@ -134,7 +133,7 @@ public abstract class DefinedPacket
SpecificTag nbt = (SpecificTag) readTag( buf, protocolVersion );
JsonElement json = TagUtil.toJson( nbt );
return ComponentSerializer.deserializeStyle( json );
return ChatSerializer.forVersion( protocolVersion ).deserializeStyle( json );
}
public static void writeEitherBaseComponent(Either<String, BaseComponent> message, ByteBuf buf, int protocolVersion)
@ -152,13 +151,13 @@ public abstract class DefinedPacket
{
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_20_3 )
{
JsonElement json = ComponentSerializer.toJson( message );
JsonElement json = ChatSerializer.forVersion( protocolVersion ).toJson( message );
SpecificTag nbt = TagUtil.fromJson( json );
writeTag( nbt, buf, protocolVersion );
} else
{
String string = ComponentSerializer.toString( message );
String string = ChatSerializer.forVersion( protocolVersion ).toString( message );
writeString( string, buf );
}
@ -166,7 +165,7 @@ public abstract class DefinedPacket
public static void writeComponentStyle(ComponentStyle style, ByteBuf buf, int protocolVersion)
{
JsonElement json = ComponentSerializer.toJson( style );
JsonElement json = ChatSerializer.forVersion( protocolVersion ).toJson( style );
SpecificTag nbt = TagUtil.fromJson( json );
writeTag( nbt, buf, protocolVersion );

View File

@ -6,8 +6,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.ChatSerializer;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.ProtocolConstants;
@ -26,7 +26,7 @@ public class Kick extends DefinedPacket
{
if ( protocol == Protocol.LOGIN )
{
message = ComponentSerializer.deserialize( readString( buf ) );
message = ChatSerializer.forVersion( protocolVersion ).deserialize( readString( buf ) );
} else
{
message = readBaseComponent( buf, protocolVersion );
@ -38,7 +38,7 @@ public class Kick extends DefinedPacket
{
if ( protocol == Protocol.LOGIN )
{
writeString( ComponentSerializer.toString( message ), buf );
writeString( ChatSerializer.forVersion( protocolVersion ).toString( message ), buf );
} else
{
writeBaseComponent( message, buf, protocolVersion );

View File

@ -6,8 +6,6 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
@ -52,31 +50,17 @@ import lombok.Getter;
import lombok.Setter;
import lombok.Synchronized;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.Favicon;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ReconnectHandler;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.Title;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentStyle;
import net.md_5.bungee.api.chat.KeybindComponent;
import net.md_5.bungee.api.chat.ScoreComponent;
import net.md_5.bungee.api.chat.SelectorComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import net.md_5.bungee.api.config.ConfigurationAdapter;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.chat.ComponentStyleSerializer;
import net.md_5.bungee.chat.KeybindComponentSerializer;
import net.md_5.bungee.chat.ScoreComponentSerializer;
import net.md_5.bungee.chat.SelectorComponentSerializer;
import net.md_5.bungee.chat.TextComponentSerializer;
import net.md_5.bungee.chat.TranslatableComponentSerializer;
import net.md_5.bungee.command.CommandBungee;
import net.md_5.bungee.command.CommandEnd;
import net.md_5.bungee.command.CommandIP;
@ -165,16 +149,6 @@ public class BungeeCord extends ProxyServer
private final ConsoleReader consoleReader;
@Getter
private final Logger logger;
public final Gson gson = new GsonBuilder()
.registerTypeAdapter( BaseComponent.class, new ComponentSerializer() )
.registerTypeAdapter( TextComponent.class, new TextComponentSerializer() )
.registerTypeAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() )
.registerTypeAdapter( KeybindComponent.class, new KeybindComponentSerializer() )
.registerTypeAdapter( ScoreComponent.class, new ScoreComponentSerializer() )
.registerTypeAdapter( SelectorComponent.class, new SelectorComponentSerializer() )
.registerTypeAdapter( ComponentStyle.class, new ComponentStyleSerializer() )
.registerTypeAdapter( ServerPing.PlayerInfo.class, new PlayerInfoSerializer() )
.registerTypeAdapter( Favicon.class, Favicon.getFaviconTypeAdapter() ).create();
@Getter
private ConnectionThrottle connectionThrottle;
private final ModuleManager moduleManager = new ModuleManager();

View File

@ -25,7 +25,6 @@ import net.md_5.bungee.api.score.Objective;
import net.md_5.bungee.api.score.Score;
import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.api.score.Team;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.CancelSendSignal;
import net.md_5.bungee.connection.DownstreamBridge;
import net.md_5.bungee.connection.LoginResult;
@ -118,7 +117,7 @@ public class ServerConnector extends PacketHandler
LoginResult profile = user.getPendingConnection().getLoginProfile();
if ( profile != null && profile.getProperties() != null && profile.getProperties().length > 0 )
{
newHost += "\00" + BungeeCord.getInstance().gson.toJson( profile.getProperties() );
newHost += "\00" + LoginResult.GSON.toJson( profile.getProperties() );
}
copiedHandshake.setHost( newHost );
} else if ( !user.getExtraDataInHandshake().isEmpty() )
@ -302,7 +301,7 @@ public class ServerConnector extends PacketHandler
{
user.unsafe().sendPacket( new ScoreboardObjective(
objective.getName(),
( user.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_1_13 ) ? Either.right( ComponentSerializer.deserialize( objective.getValue() ) ) : Either.left( objective.getValue() ),
( user.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_1_13 ) ? Either.right( user.getChatSerializer().deserialize( objective.getValue() ) ) : Either.left( objective.getValue() ),
ScoreboardObjective.HealthDisplay.fromString( objective.getType() ),
(byte) 1, null )
);

View File

@ -38,7 +38,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PermissionCheckEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.chat.VersionedComponentSerializer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.entitymap.EntityMap;
import net.md_5.bungee.forge.ForgeClientHandler;
@ -47,6 +47,7 @@ import net.md_5.bungee.forge.ForgeServerHandler;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.ChatSerializer;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.Protocol;
@ -133,6 +134,8 @@ public final class UserConnection implements ProxiedPlayer
private String displayName;
@Getter
private EntityMap entityRewrite;
@Getter
private VersionedComponentSerializer chatSerializer;
private Locale locale;
/*========================================================================*/
@Getter
@ -167,6 +170,7 @@ public final class UserConnection implements ProxiedPlayer
public boolean init()
{
this.entityRewrite = EntityMap.getEntityMap( getPendingConnection().getVersion() );
this.chatSerializer = ChatSerializer.forVersion( getPendingConnection().getVersion() );
this.displayName = name;
@ -556,7 +560,7 @@ public final class UserConnection implements ProxiedPlayer
sendPacketQueued( new SystemChat( message, position.ordinal() ) );
} else
{
sendPacketQueued( new Chat( ComponentSerializer.toString( message ), (byte) position.ordinal(), sender ) );
sendPacketQueued( new Chat( chatSerializer.toString( message ), (byte) position.ordinal(), sender ) );
}
}

View File

@ -46,7 +46,6 @@ import net.md_5.bungee.api.score.Position;
import net.md_5.bungee.api.score.Score;
import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.api.score.Team;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.entitymap.EntityMap;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.PacketHandler;
@ -196,7 +195,7 @@ public class DownstreamBridge extends PacketHandler
switch ( objective.getAction() )
{
case 0:
serverScoreboard.addObjective( new Objective( objective.getName(), ( objective.getValue().isLeft() ) ? objective.getValue().getLeft() : ComponentSerializer.toString( objective.getValue().getRight() ), objective.getType().toString() ) );
serverScoreboard.addObjective( new Objective( objective.getName(), ( objective.getValue().isLeft() ) ? objective.getValue().getLeft() : con.getChatSerializer().toString( objective.getValue().getRight() ), objective.getType().toString() ) );
break;
case 1:
serverScoreboard.removeObjective( objective.getName() );
@ -205,7 +204,7 @@ public class DownstreamBridge extends PacketHandler
Objective oldObjective = serverScoreboard.getObjective( objective.getName() );
if ( oldObjective != null )
{
oldObjective.setValue( ( objective.getValue().isLeft() ) ? objective.getValue().getLeft() : ComponentSerializer.toString( objective.getValue().getRight() ) );
oldObjective.setValue( ( objective.getValue().isLeft() ) ? objective.getValue().getLeft() : con.getChatSerializer().toString( objective.getValue().getRight() ) );
oldObjective.setType( objective.getType().toString() );
}
break;
@ -279,9 +278,9 @@ public class DownstreamBridge extends PacketHandler
{
if ( team.getMode() == 0 || team.getMode() == 2 )
{
t.setDisplayName( team.getDisplayName().getLeftOrCompute( ComponentSerializer::toString ) );
t.setPrefix( team.getPrefix().getLeftOrCompute( ComponentSerializer::toString ) );
t.setSuffix( team.getSuffix().getLeftOrCompute( ComponentSerializer::toString ) );
t.setDisplayName( team.getDisplayName().getLeftOrCompute( (component) -> con.getChatSerializer().toString( component ) ) );
t.setPrefix( team.getPrefix().getLeftOrCompute( (component) -> con.getChatSerializer().toString( component ) ) );
t.setSuffix( team.getSuffix().getLeftOrCompute( (component) -> con.getChatSerializer().toString( component ) ) );
t.setFriendlyFire( team.getFriendlyFire() );
t.setNameTagVisibility( team.getNameTagVisibility().getKey() );
t.setColor( team.getColor() );
@ -556,7 +555,7 @@ public class DownstreamBridge extends PacketHandler
case "MessageRaw":
{
String target = in.readUTF();
BaseComponent[] message = ComponentSerializer.parse( in.readUTF() );
BaseComponent[] message = con.getChatSerializer().parse( in.readUTF() );
if ( target.equals( "ALL" ) )
{
for ( ProxiedPlayer player : bungee.getPlayers() )
@ -623,7 +622,7 @@ public class DownstreamBridge extends PacketHandler
ProxiedPlayer player = bungee.getPlayer( in.readUTF() );
if ( player != null )
{
BaseComponent[] kickReason = ComponentSerializer.parse( in.readUTF() );
BaseComponent[] kickReason = con.getChatSerializer().parse( in.readUTF() );
player.disconnect( kickReason );
}
break;

View File

@ -305,7 +305,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
@Override
public void done(ProxyPingEvent pingResult, Throwable error)
{
Gson gson = BungeeCord.getInstance().gson;
Gson gson = PingHandler.gson;
unsafe.sendPacket( new StatusResponse( gson.toJson( pingResult.getResponse() ) ) );
if ( bungee.getConnectionThrottle() != null )
{
@ -532,7 +532,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
{
if ( error == null )
{
LoginResult obj = BungeeCord.getInstance().gson.fromJson( result, LoginResult.class );
LoginResult obj = LoginResult.GSON.fromJson( result, LoginResult.class );
if ( obj != null && obj.getId() != null )
{
loginProfile = obj;

View File

@ -1,5 +1,6 @@
package net.md_5.bungee.connection;
import com.google.gson.Gson;
import lombok.AllArgsConstructor;
import lombok.Data;
import net.md_5.bungee.protocol.Property;
@ -9,6 +10,8 @@ import net.md_5.bungee.protocol.Property;
public class LoginResult
{
public static final Gson GSON = new Gson();
//
private String id;
private String name;
private Property[] properties;

View File

@ -3,12 +3,14 @@ package net.md_5.bungee.connection;
import com.google.gson.Gson;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.BungeeServerInfo;
import net.md_5.bungee.PlayerInfoSerializer;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.Favicon;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.chat.VersionedComponentSerializer;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.netty.PipelineUtils;
@ -26,6 +28,9 @@ import net.md_5.bungee.util.QuietException;
public class PingHandler extends PacketHandler
{
static final Gson gson = VersionedComponentSerializer.getDefault().getGson().newBuilder()
.registerTypeAdapter( ServerPing.PlayerInfo.class, new PlayerInfoSerializer() )
.registerTypeAdapter( Favicon.class, Favicon.getFaviconTypeAdapter() ).create();
private final ServerInfo target;
private final Callback<ServerPing> callback;
private final int protocol;
@ -65,7 +70,6 @@ public class PingHandler extends PacketHandler
@SuppressFBWarnings("UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR")
public void handle(StatusResponse statusResponse) throws Exception
{
Gson gson = BungeeCord.getInstance().gson;
ServerPing serverPing = gson.fromJson( statusResponse.getResponse(), ServerPing.class );
( (BungeeServerInfo) target ).cachePing( serverPing );
callback.done( serverPing, null );

View File

@ -50,20 +50,6 @@ public final class ChatComponentTransformer
next.getHoverEvent().getContents().clear();
next.getHoverEvent().getContents().add( exception );
}
} else if ( player.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_1_21_5 )
{
if ( next.getHoverEvent() != null && !next.getHoverEvent().isV1_21_5() )
{
next = next.duplicate();
next.getHoverEvent().setV1_21_5( true );
}
if ( next.getClickEvent() != null && !next.getClickEvent().isV1_21_5() )
{
next = next.duplicate();
next.getClickEvent().setV1_21_5( true );
}
}
return next;