#3774: Minecraft 25w04a chat component changes

This commit is contained in:
Outfluencer 2025-01-29 20:35:46 +11:00 committed by md_5
parent 4fded9828f
commit 80bb237289
No known key found for this signature in database
GPG Key ID: E8E901AC7C617C11
6 changed files with 197 additions and 39 deletions

View File

@ -3,7 +3,9 @@ package net.md_5.bungee.api.chat;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import org.jetbrains.annotations.ApiStatus;
@Getter @Getter
@ToString @ToString
@ -23,6 +25,13 @@ public final class ClickEvent
*/ */
private final String value; 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 public enum Action
{ {

View File

@ -14,6 +14,7 @@ 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.Item;
import net.md_5.bungee.api.chat.hover.content.Text; import net.md_5.bungee.api.chat.hover.content.Text;
import net.md_5.bungee.chat.ComponentSerializer; import net.md_5.bungee.chat.ComponentSerializer;
import org.jetbrains.annotations.ApiStatus;
@Getter @Getter
@ToString @ToString
@ -34,8 +35,33 @@ public final class HoverEvent
* Returns whether this hover event is prior to 1.16 * Returns whether this hover event is prior to 1.16
*/ */
@Setter @Setter
@ApiStatus.Internal
private boolean legacy = false; 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. * Creates event with an action and a list of contents.
* *

View File

@ -7,6 +7,7 @@ import lombok.NonNull;
import lombok.ToString; import lombok.ToString;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.HoverEvent;
import org.jetbrains.annotations.ApiStatus;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@ -15,6 +16,18 @@ import net.md_5.bungee.api.chat.HoverEvent;
public class Entity extends Content 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. * Namespaced entity ID.
* *
@ -35,6 +48,12 @@ public class Entity extends Content
*/ */
private BaseComponent name; private BaseComponent name;
/**
* True if this entity is for 1.21.5 or later
*/
@ApiStatus.Internal
private boolean v1_21_5;
@Override @Override
public HoverEvent.Action requiredAction() public HoverEvent.Action requiredAction()
{ {

View File

@ -19,20 +19,23 @@ public class EntitySerializer implements JsonSerializer<Entity>, JsonDeserialize
{ {
JsonObject value = element.getAsJsonObject(); JsonObject value = element.getAsJsonObject();
boolean newEntity = value.has( "uuid" );
String idString; String idString;
JsonElement id = value.get( "id" ); JsonElement uuid = value.get( newEntity ? "uuid" : "id" );
if ( id.isJsonArray() ) if ( uuid.isJsonArray() )
{ {
idString = parseUUID( context.deserialize( id, int[].class ) ).toString(); idString = parseUUID( context.deserialize( uuid, int[].class ) ).toString();
} else } else
{ {
idString = id.getAsString(); idString = uuid.getAsString();
} }
return new Entity( return new Entity(
( value.has( "type" ) ) ? value.get( "type" ).getAsString() : null, ( value.has( newEntity ? "id" : "type" ) ) ? value.get( newEntity ? "id" : "type" ).getAsString() : null,
idString, idString,
( value.has( "name" ) ) ? context.deserialize( value.get( "name" ), BaseComponent.class ) : null ( value.has( "name" ) ) ? context.deserialize( value.get( "name" ), BaseComponent.class ) : null,
newEntity
); );
} }
@ -40,8 +43,9 @@ public class EntitySerializer implements JsonSerializer<Entity>, JsonDeserialize
public JsonElement serialize(Entity content, Type type, JsonSerializationContext context) public JsonElement serialize(Entity content, Type type, JsonSerializationContext context)
{ {
JsonObject object = new JsonObject(); JsonObject object = new JsonObject();
object.addProperty( "type", ( content.getType() != null ) ? content.getType() : "minecraft:pig" );
object.addProperty( "id", content.getId() ); 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() );
if ( content.getName() != null ) if ( content.getName() != null )
{ {
object.add( "name", context.serialize( content.getName() ) ); object.add( "name", context.serialize( content.getName() ) );

View File

@ -30,42 +30,65 @@ public class BaseComponentSerializer
} }
//Events //Events
JsonObject clickEvent = object.getAsJsonObject( "clickEvent" ); JsonObject clickEvent;
boolean newClickEvent = ( clickEvent = object.getAsJsonObject( "click_event" ) ) != null;
if ( !newClickEvent )
{
clickEvent = object.getAsJsonObject( "clickEvent" );
}
if ( clickEvent != null ) if ( clickEvent != null )
{ {
component.setClickEvent( new ClickEvent( ClickEvent.Action action = ClickEvent.Action.valueOf( clickEvent.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) );
ClickEvent.Action.valueOf( clickEvent.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) ), if ( newClickEvent )
( clickEvent.has( "value" ) ) ? clickEvent.get( "value" ).getAsString() : "" ) ); {
switch ( action )
{
case OPEN_URL:
component.setClickEvent( new ClickEvent( action, clickEvent.get( "url" ).getAsString() ) );
break;
case RUN_COMMAND:
case SUGGEST_COMMAND:
component.setClickEvent( new ClickEvent( action, clickEvent.get( "command" ).getAsString() ) );
break;
case CHANGE_PAGE:
int page = clickEvent.get( "page" ).getAsInt();
Preconditions.checkArgument( page >= 0, "Page number has to be positive" );
component.setClickEvent( new ClickEvent( action, Integer.toString( page ) ) );
break;
default:
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() : "" ) );
}
} }
JsonObject hoverEventJson = object.getAsJsonObject( "hoverEvent" );
JsonObject hoverEventJson;
boolean newHoverEvent = ( hoverEventJson = object.getAsJsonObject( "hover_event" ) ) != null;
if ( !newHoverEvent )
{
hoverEventJson = object.getAsJsonObject( "hoverEvent" );
}
if ( hoverEventJson != null ) if ( hoverEventJson != null )
{ {
HoverEvent hoverEvent = null; HoverEvent hoverEvent = null;
HoverEvent.Action action = HoverEvent.Action.valueOf( hoverEventJson.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) ); HoverEvent.Action action = HoverEvent.Action.valueOf( hoverEventJson.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) );
JsonElement value = hoverEventJson.get( "value" ); if ( newHoverEvent || hoverEventJson.has( "contents" ) )
if ( value != null )
{ {
// value is only used for text in >= 1.21.5 (its inlined now)
// Plugins previously had support to pass BaseComponent[] into any action. JsonElement contents = hoverEventJson.get( newHoverEvent ? "value" : "contents" );
// If the GSON is possible to be parsed as BaseComponent, attempt to parse as so. if ( contents != null || ( newHoverEvent && ( action == HoverEvent.Action.SHOW_ITEM || action == HoverEvent.Action.SHOW_ENTITY ) ) )
BaseComponent[] components;
if ( value.isJsonArray() )
{ {
components = context.deserialize( value, BaseComponent[].class ); if ( contents == null )
} else
{
components = new BaseComponent[]
{ {
context.deserialize( value, BaseComponent.class ) // this is the new inline for SHOW_ITEM and SHOW_ENTITY
}; contents = hoverEventJson;
} }
hoverEvent = new HoverEvent( action, components );
} else
{
JsonElement contents = hoverEventJson.get( "contents" );
if ( contents != null )
{
Content[] list; Content[] list;
if ( contents.isJsonArray() ) if ( contents.isJsonArray() )
{ {
@ -78,6 +101,27 @@ public class BaseComponentSerializer
}; };
} }
hoverEvent = new HoverEvent( action, new ArrayList<>( Arrays.asList( list ) ) ); hoverEvent = new HoverEvent( action, new ArrayList<>( Arrays.asList( list ) ) );
hoverEvent.setV1_21_5( newHoverEvent );
}
} else
{
JsonElement value = hoverEventJson.get( "value" );
if ( value != null )
{
// Plugins previously had support to pass BaseComponent[] into any action.
// If the GSON is possible to be parsed as BaseComponent, attempt to parse as so.
BaseComponent[] components;
if ( value.isJsonArray() )
{
components = context.deserialize( value, BaseComponent[].class );
} else
{
components = new BaseComponent[]
{
context.deserialize( value, BaseComponent.class )
};
}
hoverEvent = new HoverEvent( action, components );
} }
} }
@ -118,23 +162,65 @@ public class BaseComponentSerializer
if ( component.getClickEvent() != null ) if ( component.getClickEvent() != null )
{ {
JsonObject clickEvent = new JsonObject(); JsonObject clickEvent = new JsonObject();
clickEvent.addProperty( "action", component.getClickEvent().getAction().toString().toLowerCase( Locale.ROOT ) ); String actionName = component.getClickEvent().getAction().toString().toLowerCase( Locale.ROOT );
clickEvent.addProperty( "value", component.getClickEvent().getValue() ); clickEvent.addProperty( "action", actionName.toLowerCase( Locale.ROOT ) );
object.add( "clickEvent", clickEvent ); if ( component.getClickEvent().isV1_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 );
} else
{
clickEvent.addProperty( "value", component.getClickEvent().getValue() );
object.add( "clickEvent", clickEvent );
}
} }
if ( component.getHoverEvent() != null ) if ( component.getHoverEvent() != null )
{ {
JsonObject hoverEvent = new JsonObject(); JsonObject hoverEvent = new JsonObject();
hoverEvent.addProperty( "action", component.getHoverEvent().getAction().toString().toLowerCase( Locale.ROOT ) ); hoverEvent.addProperty( "action", component.getHoverEvent().getAction().toString().toLowerCase( Locale.ROOT ) );
boolean newFormat = component.getHoverEvent().isV1_21_5();
if ( component.getHoverEvent().isLegacy() ) if ( component.getHoverEvent().isLegacy() )
{ {
hoverEvent.add( "value", context.serialize( component.getHoverEvent().getContents().get( 0 ) ) ); hoverEvent.add( "value", context.serialize( component.getHoverEvent().getContents().get( 0 ) ) );
} else } else
{ {
hoverEvent.add( "contents", context.serialize( ( component.getHoverEvent().getContents().size() == 1 ) if ( newFormat )
? component.getHoverEvent().getContents().get( 0 ) : component.getHoverEvent().getContents() ) ); {
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() ) );
}
} else
{
hoverEvent.add( "contents", context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
? component.getHoverEvent().getContents().get( 0 ) : component.getHoverEvent().getContents() ) );
}
} }
object.add( "hoverEvent", hoverEvent ); object.add( newFormat ? "hover_event" : "hoverEvent", hoverEvent );
} }
if ( component.getExtra() != null ) if ( component.getExtra() != null )

View File

@ -50,6 +50,20 @@ public final class ChatComponentTransformer
next.getHoverEvent().getContents().clear(); next.getHoverEvent().getContents().clear();
next.getHoverEvent().getContents().add( exception ); 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; return next;