Skip to content

Commit

Permalink
#3774: Minecraft 25w04a chat component changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Outfluencer authored and md-5 committed Jan 29, 2025
1 parent 4fded98 commit 80bb237
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 39 deletions.
9 changes: 9 additions & 0 deletions chat/src/main/java/net/md_5/bungee/api/chat/ClickEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.jetbrains.annotations.ApiStatus;

@Getter
@ToString
Expand All @@ -23,6 +25,13 @@ 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
{

Expand Down
26 changes: 26 additions & 0 deletions chat/src/main/java/net/md_5/bungee/api/chat/HoverEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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 org.jetbrains.annotations.ApiStatus;

@Getter
@ToString
Expand All @@ -34,8 +35,33 @@ public final class HoverEvent
* Returns whether this hover event is prior to 1.16
*/
@Setter
@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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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
Expand All @@ -15,6 +16,18 @@
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.
*
Expand All @@ -35,6 +48,12 @@ 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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,33 @@ public Entity deserialize(JsonElement element, Type type, JsonDeserializationCon
{
JsonObject value = element.getAsJsonObject();

boolean newEntity = value.has( "uuid" );

String idString;
JsonElement id = value.get( "id" );
if ( id.isJsonArray() )
JsonElement uuid = value.get( newEntity ? "uuid" : "id" );
if ( uuid.isJsonArray() )
{
idString = parseUUID( context.deserialize( id, int[].class ) ).toString();
idString = parseUUID( context.deserialize( uuid, int[].class ) ).toString();
} else
{
idString = id.getAsString();
idString = uuid.getAsString();
}

return new Entity(
( value.has( "type" ) ) ? value.get( "type" ).getAsString() : null,
( value.has( newEntity ? "id" : "type" ) ) ? value.get( newEntity ? "id" : "type" ).getAsString() : null,
idString,
( value.has( "name" ) ) ? context.deserialize( value.get( "name" ), BaseComponent.class ) : null
( value.has( "name" ) ) ? context.deserialize( value.get( "name" ), BaseComponent.class ) : null,
newEntity
);
}

@Override
public JsonElement serialize(Entity content, Type type, JsonSerializationContext context)
{
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 )
{
object.add( "name", context.serialize( content.getName() ) );
Expand Down
148 changes: 117 additions & 31 deletions chat/src/main/java/net/md_5/bungee/chat/BaseComponentSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,42 +30,65 @@ protected void deserialize(JsonObject object, BaseComponent component, JsonDeser
}

//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 )
{
component.setClickEvent( new ClickEvent(
ClickEvent.Action.valueOf( clickEvent.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) ),
( clickEvent.has( "value" ) ) ? clickEvent.get( "value" ).getAsString() : "" ) );
ClickEvent.Action action = ClickEvent.Action.valueOf( clickEvent.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) );
if ( newClickEvent )
{
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;
boolean newHoverEvent = ( hoverEventJson = object.getAsJsonObject( "hover_event" ) ) != null;
if ( !newHoverEvent )
{
hoverEventJson = object.getAsJsonObject( "hoverEvent" );
}
JsonObject hoverEventJson = object.getAsJsonObject( "hoverEvent" );

if ( hoverEventJson != null )
{
HoverEvent hoverEvent = null;
HoverEvent.Action action = HoverEvent.Action.valueOf( hoverEventJson.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) );

JsonElement value = hoverEventJson.get( "value" );
if ( value != null )
if ( newHoverEvent || hoverEventJson.has( "contents" ) )
{

// 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
// value is only used for text in >= 1.21.5 (its inlined now)
JsonElement contents = hoverEventJson.get( newHoverEvent ? "value" : "contents" );
if ( contents != null || ( newHoverEvent && ( action == HoverEvent.Action.SHOW_ITEM || action == HoverEvent.Action.SHOW_ENTITY ) ) )
{
components = new BaseComponent[]
if ( contents == null )
{
context.deserialize( value, BaseComponent.class )
};
}
hoverEvent = new HoverEvent( action, components );
} else
{
JsonElement contents = hoverEventJson.get( "contents" );
if ( contents != null )
{
// this is the new inline for SHOW_ITEM and SHOW_ENTITY
contents = hoverEventJson;
}
Content[] list;
if ( contents.isJsonArray() )
{
Expand All @@ -78,6 +101,27 @@ protected void deserialize(JsonObject object, BaseComponent component, JsonDeser
};
}
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 );
}
}

Expand Down Expand Up @@ -118,23 +162,65 @@ protected void serialize(JsonObject object, BaseComponent component, JsonSeriali
if ( component.getClickEvent() != null )
{
JsonObject clickEvent = new JsonObject();
clickEvent.addProperty( "action", component.getClickEvent().getAction().toString().toLowerCase( Locale.ROOT ) );
clickEvent.addProperty( "value", component.getClickEvent().getValue() );
object.add( "clickEvent", clickEvent );
String actionName = component.getClickEvent().getAction().toString().toLowerCase( Locale.ROOT );
clickEvent.addProperty( "action", actionName.toLowerCase( Locale.ROOT ) );
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 )
{
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
{
hoverEvent.add( "contents", context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
? component.getHoverEvent().getContents().get( 0 ) : component.getHoverEvent().getContents() ) );
if ( newFormat )
{
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 )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ public BaseComponent legacyHoverTransform(ProxiedPlayer player, BaseComponent ne
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;
Expand Down

0 comments on commit 80bb237

Please sign in to comment.