#2866: Add support for contents in Hover Event
This commit is contained in:
parent
26f538d193
commit
1d40b8a88a
@ -1,8 +1,26 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
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.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@ -12,15 +30,335 @@ import lombok.ToString;
|
||||
public final class HoverEvent
|
||||
{
|
||||
|
||||
/**
|
||||
* The action of this event.
|
||||
*/
|
||||
private final Action action;
|
||||
private final BaseComponent[] value;
|
||||
/**
|
||||
* List of contents to provide for this event.
|
||||
*/
|
||||
private final List<Content> contents;
|
||||
/**
|
||||
* Returns whether this hover event is prior to 1.16
|
||||
*/
|
||||
@Setter
|
||||
private boolean legacy = false;
|
||||
|
||||
/**
|
||||
* Creates event with an action and a list of contents.
|
||||
*
|
||||
* @param action action of this event
|
||||
* @param contents array of contents, provide at least one
|
||||
*/
|
||||
public HoverEvent(Action action, Content... contents)
|
||||
{
|
||||
Preconditions.checkArgument( contents.length != 0, "Must contain at least one content" );
|
||||
this.action = action;
|
||||
this.contents = new ArrayList<>();
|
||||
for ( Content it : contents )
|
||||
{
|
||||
addContent( it );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy constructor to create hover event.
|
||||
*
|
||||
* @param action the action
|
||||
* @param value the value
|
||||
* @deprecated {@link #HoverEvent(Action, Content[])}
|
||||
*/
|
||||
@Deprecated
|
||||
public HoverEvent(Action action, BaseComponent[] value)
|
||||
{
|
||||
// Old plugins may have somehow hacked BaseComponent[] into
|
||||
// anything other than SHOW_TEXT action. Ideally continue support.
|
||||
this.action = action;
|
||||
this.contents = new ArrayList<>( Collections.singletonList( new ContentText( value ) ) );
|
||||
this.legacy = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a content to this hover event.
|
||||
*
|
||||
* @param content the content add
|
||||
* @throws IllegalArgumentException if is a legacy component and already has
|
||||
* a content
|
||||
* @throws UnsupportedOperationException if content action does not match
|
||||
* hover event action
|
||||
*/
|
||||
public void addContent(Content content) throws UnsupportedOperationException
|
||||
{
|
||||
Preconditions.checkArgument( !legacy || contents.size() == 0, "Legacy HoverEvent may not have more than one content" );
|
||||
content.assertAction( action );
|
||||
contents.add( content );
|
||||
}
|
||||
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
public abstract static class Content
|
||||
{
|
||||
|
||||
/**
|
||||
* Required action for this content type.
|
||||
*
|
||||
* @return action
|
||||
*/
|
||||
abstract Action requiredAction();
|
||||
|
||||
/**
|
||||
* Tests this content against an action
|
||||
*
|
||||
* @param input input to test
|
||||
* @throws UnsupportedOperationException if action incompatible
|
||||
*/
|
||||
void assertAction(Action input) throws UnsupportedOperationException
|
||||
{
|
||||
if ( input != requiredAction() )
|
||||
{
|
||||
throw new UnsupportedOperationException( "Action " + input + " not compatible! Expected " + requiredAction() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public static class ContentText extends Content
|
||||
{
|
||||
|
||||
/**
|
||||
* The value.
|
||||
*
|
||||
* May be a component or raw text depending on constructor used.
|
||||
*/
|
||||
private Object value;
|
||||
|
||||
public ContentText(BaseComponent[] value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public ContentText(String value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
Action requiredAction()
|
||||
{
|
||||
return Action.SHOW_TEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if ( value instanceof BaseComponent[] )
|
||||
{
|
||||
return o instanceof ContentText
|
||||
&& ( (ContentText) o ).value instanceof BaseComponent[]
|
||||
&& Arrays.equals( (BaseComponent[]) value, (BaseComponent[]) ( (ContentText) o ).value );
|
||||
} else
|
||||
{
|
||||
return value.equals( o );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return ( value instanceof BaseComponent[] ) ? Arrays.hashCode( (BaseComponent[]) value ) : value.hashCode();
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonSerializer<ContentText>, JsonDeserializer<ContentText>
|
||||
{
|
||||
|
||||
@Override
|
||||
public ContentText deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
if ( element.isJsonArray() )
|
||||
{
|
||||
return new ContentText( context.<BaseComponent[]>deserialize( element, BaseComponent[].class ) );
|
||||
} else if ( element.getAsJsonObject().isJsonPrimitive() )
|
||||
{
|
||||
return new ContentText( element.getAsJsonObject().getAsJsonPrimitive().getAsString() );
|
||||
} else
|
||||
{
|
||||
return new ContentText( new BaseComponent[]
|
||||
{
|
||||
context.deserialize( element, BaseComponent.class )
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(ContentText content, Type type, JsonSerializationContext context)
|
||||
{
|
||||
return context.serialize( content.getValue() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public static class ContentEntity extends Content
|
||||
{
|
||||
|
||||
/**
|
||||
* Namespaced entity ID.
|
||||
*
|
||||
* Will use 'minecraft:pig' if null.
|
||||
*/
|
||||
private String type;
|
||||
/**
|
||||
* Entity UUID in hyphenated hexadecimal format.
|
||||
*
|
||||
* Should be valid UUID. TODO : validate?
|
||||
*/
|
||||
@NonNull
|
||||
private String id;
|
||||
/**
|
||||
* Name to display as the entity.
|
||||
*
|
||||
* This is optional and will be hidden if null.
|
||||
*/
|
||||
private BaseComponent name;
|
||||
|
||||
@Override
|
||||
Action requiredAction()
|
||||
{
|
||||
return Action.SHOW_ENTITY;
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonSerializer<ContentEntity>, JsonDeserializer<ContentEntity>
|
||||
{
|
||||
|
||||
@Override
|
||||
public ContentEntity deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
JsonObject value = element.getAsJsonObject();
|
||||
|
||||
return new ContentEntity(
|
||||
( value.has( "type" ) ) ? value.get( "type" ).getAsString() : null,
|
||||
value.get( "id" ).getAsString(),
|
||||
( value.has( "name" ) ) ? context.deserialize( value.get( "name" ), BaseComponent.class ) : null
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(ContentEntity content, Type type, JsonSerializationContext context)
|
||||
{
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty( "type", ( content.getType() != null ) ? content.getType() : "minecraft:pig" );
|
||||
object.addProperty( "id", content.getId() );
|
||||
if ( content.getName() != null )
|
||||
{
|
||||
object.add( "name", context.serialize( content.getName() ) );
|
||||
}
|
||||
return object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public static class ContentItem extends Content
|
||||
{
|
||||
|
||||
/**
|
||||
* Namespaced item ID. Will use 'minecraft:air' if null.
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* Optional. Size of the item stack.
|
||||
*/
|
||||
private int count = -1;
|
||||
/**
|
||||
* Optional. Item tag.
|
||||
*/
|
||||
private ItemTag tag;
|
||||
|
||||
@Override
|
||||
Action requiredAction()
|
||||
{
|
||||
return Action.SHOW_ITEM;
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonSerializer<ContentItem>, JsonDeserializer<ContentItem>
|
||||
{
|
||||
|
||||
@Override
|
||||
public ContentItem deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
JsonObject value = element.getAsJsonObject();
|
||||
|
||||
return new ContentItem(
|
||||
( value.has( "id" ) ) ? value.get( "id" ).getAsString() : null,
|
||||
( value.has( "Count" ) ) ? value.get( "Count" ).getAsInt() : -1,
|
||||
( value.has( "tag" ) ) ? context.deserialize( value.get( "tag" ), ItemTag.class ) : null
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(ContentItem content, Type type, JsonSerializationContext context)
|
||||
{
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty( "id", ( content.getId() == null ) ? "minecraft:air" : content.getId() );
|
||||
if ( content.getCount() != -1 )
|
||||
{
|
||||
object.addProperty( "Count", content.getCount() );
|
||||
}
|
||||
if ( content.getTag() != null )
|
||||
{
|
||||
object.add( "tag", context.serialize( content.getTag() ) );
|
||||
}
|
||||
return object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action
|
||||
{
|
||||
|
||||
SHOW_TEXT,
|
||||
SHOW_ACHIEVEMENT,
|
||||
SHOW_ITEM,
|
||||
SHOW_ENTITY
|
||||
SHOW_ENTITY,
|
||||
/**
|
||||
* Removed since 1.12. Advancements instead simply use show_text. The ID
|
||||
* of an achievement or statistic to display. Example: new
|
||||
* ComponentText( "achievement.openInventory" )
|
||||
*/
|
||||
@Deprecated
|
||||
SHOW_ACHIEVEMENT,
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate {@link Content} class for an {@link Action} for the
|
||||
* GSON serialization
|
||||
*
|
||||
* @param action the action to get for
|
||||
* @param array if to return the arrayed class
|
||||
* @return the class
|
||||
*/
|
||||
public static Class<?> getClass(HoverEvent.Action action, boolean array)
|
||||
{
|
||||
Preconditions.checkArgument( action != null, "action" );
|
||||
|
||||
switch ( action )
|
||||
{
|
||||
case SHOW_TEXT:
|
||||
return ( array ) ? HoverEvent.ContentText[].class : HoverEvent.ContentText.class;
|
||||
case SHOW_ENTITY:
|
||||
return ( array ) ? HoverEvent.ContentEntity[].class : HoverEvent.ContentEntity.class;
|
||||
case SHOW_ITEM:
|
||||
return ( array ) ? HoverEvent.ContentItem[].class : HoverEvent.ContentItem.class;
|
||||
default:
|
||||
throw new UnsupportedOperationException( "Action '" + action.name() + " not supported" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
158
chat/src/main/java/net/md_5/bungee/api/chat/ItemTag.java
Normal file
158
chat/src/main/java/net/md_5/bungee/api/chat/ItemTag.java
Normal file
@ -0,0 +1,158 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
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.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.Singular;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Metadata for use in conjunction with {@link HoverEvent.Action#SHOW_ITEM}
|
||||
*/
|
||||
@ToString(callSuper = true)
|
||||
@Setter
|
||||
@Builder(builderClassName = "Builder", access = AccessLevel.PUBLIC)
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public final class ItemTag
|
||||
{
|
||||
|
||||
private BaseComponent name;
|
||||
@Singular("ench")
|
||||
private List<Enchantment> enchantments = new ArrayList<>();
|
||||
@Singular("lore")
|
||||
private List<BaseComponent[]> lore = new ArrayList<>();
|
||||
private Boolean unbreakable;
|
||||
|
||||
private ItemTag()
|
||||
{
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class Enchantment
|
||||
{
|
||||
|
||||
private final int level;
|
||||
private final int id;
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonSerializer<ItemTag>, JsonDeserializer<ItemTag>
|
||||
{
|
||||
|
||||
@Override
|
||||
public ItemTag deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
ItemTag itemTag = new ItemTag();
|
||||
JsonObject object = element.getAsJsonObject();
|
||||
if ( object.has( "ench" ) )
|
||||
{
|
||||
for ( JsonElement jsonElement : object.get( "ench" ).getAsJsonArray() )
|
||||
{
|
||||
JsonObject next = jsonElement.getAsJsonObject();
|
||||
itemTag.enchantments.add( new Enchantment( next.get( "id" ).getAsInt(), next.get( "lvl" ).getAsInt() ) );
|
||||
}
|
||||
}
|
||||
if ( object.has( "Unbreakable" ) )
|
||||
{
|
||||
int status = object.get( "Unbreakable" ).getAsInt();
|
||||
if ( status == 1 )
|
||||
{
|
||||
itemTag.unbreakable = true;
|
||||
} else if ( status == 0 )
|
||||
{
|
||||
itemTag.unbreakable = false;
|
||||
}
|
||||
}
|
||||
if ( object.has( "display" ) )
|
||||
{
|
||||
JsonObject display = object.get( "display" ).getAsJsonObject();
|
||||
if ( display.has( "Name" ) )
|
||||
{
|
||||
itemTag.name = context.deserialize( display.get( "Name" ).getAsJsonObject(), BaseComponent.class );
|
||||
}
|
||||
|
||||
if ( display.has( "Lore" ) )
|
||||
{
|
||||
JsonElement lore = display.get( "Lore" );
|
||||
if ( lore.isJsonArray() )
|
||||
{
|
||||
for ( JsonElement loreIt : lore.getAsJsonArray() )
|
||||
{
|
||||
if ( loreIt.isJsonArray() )
|
||||
{
|
||||
itemTag.lore.add( context.deserialize( loreIt, BaseComponent[].class ) );
|
||||
} else
|
||||
{
|
||||
itemTag.lore.add( new BaseComponent[]
|
||||
{
|
||||
context.deserialize( loreIt, BaseComponent.class )
|
||||
} );
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
itemTag.lore.add( context.deserialize( display.get( "Lore" ), BaseComponent[].class ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
return itemTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(ItemTag itemTag, Type type, JsonSerializationContext context)
|
||||
{
|
||||
JsonObject object = new JsonObject();
|
||||
|
||||
if ( !itemTag.enchantments.isEmpty() )
|
||||
{
|
||||
JsonArray enchArray = new JsonArray();
|
||||
for ( Enchantment ench : itemTag.enchantments )
|
||||
{
|
||||
JsonObject enchObj = new JsonObject();
|
||||
enchObj.addProperty( "id", ench.id );
|
||||
enchObj.addProperty( "lvl", ench.level );
|
||||
enchArray.add( enchObj );
|
||||
}
|
||||
object.add( "ench", enchArray );
|
||||
}
|
||||
|
||||
if ( itemTag.unbreakable != null )
|
||||
{
|
||||
object.addProperty( "Unbreakable", ( itemTag.unbreakable ) ? 1 : 0 );
|
||||
}
|
||||
|
||||
JsonObject display = new JsonObject();
|
||||
|
||||
if ( itemTag.name != null )
|
||||
{
|
||||
display.add( "Name", context.serialize( itemTag.name ) );
|
||||
}
|
||||
|
||||
if ( !itemTag.lore.isEmpty() )
|
||||
{
|
||||
display.add( "Lore", context.serialize( itemTag.lore ) );
|
||||
}
|
||||
|
||||
if ( display.size() != 0 )
|
||||
{
|
||||
object.add( "display", display );
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,8 +2,10 @@ package net.md_5.bungee.chat;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
@ -52,7 +54,7 @@ public class BaseComponentSerializer
|
||||
}
|
||||
if ( object.has( "extra" ) )
|
||||
{
|
||||
component.setExtra( Arrays.<BaseComponent>asList( context.<BaseComponent[]>deserialize( object.get( "extra" ), BaseComponent[].class ) ) );
|
||||
component.setExtra( Arrays.asList( context.<BaseComponent[]>deserialize( object.get( "extra" ), BaseComponent[].class ) ) );
|
||||
}
|
||||
|
||||
//Events
|
||||
@ -66,18 +68,34 @@ public class BaseComponentSerializer
|
||||
if ( object.has( "hoverEvent" ) )
|
||||
{
|
||||
JsonObject event = object.getAsJsonObject( "hoverEvent" );
|
||||
BaseComponent[] res;
|
||||
if ( event.get( "value" ).isJsonArray() )
|
||||
HoverEvent hoverEvent = null;
|
||||
HoverEvent.Action action = HoverEvent.Action.valueOf( event.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) );
|
||||
|
||||
if ( event.has( "value" ) )
|
||||
{
|
||||
res = context.deserialize( event.get( "value" ), BaseComponent[].class );
|
||||
} else
|
||||
{
|
||||
res = new BaseComponent[]
|
||||
HoverEvent.Content[] ret = new HoverEvent.Content[]
|
||||
{
|
||||
context.<BaseComponent>deserialize( event.get( "value" ), BaseComponent.class )
|
||||
context.deserialize( event.get( "value" ), HoverEvent.getClass( action, false ) )
|
||||
};
|
||||
hoverEvent = new HoverEvent( action, ret );
|
||||
} else if ( event.has( "contents" ) )
|
||||
{
|
||||
HoverEvent.Content[] list;
|
||||
JsonElement contents = event.get( "contents" );
|
||||
if ( contents.isJsonArray() )
|
||||
{
|
||||
list = context.deserialize( contents, HoverEvent.getClass( action, true ) );
|
||||
} else
|
||||
{
|
||||
list = new HoverEvent.Content[]
|
||||
{
|
||||
context.deserialize( contents, HoverEvent.getClass( action, false ) )
|
||||
};
|
||||
}
|
||||
|
||||
hoverEvent = new HoverEvent( action, new ArrayList<>( Arrays.asList( list ) ) );
|
||||
}
|
||||
component.setHoverEvent( new HoverEvent( HoverEvent.Action.valueOf( event.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) ), res ) );
|
||||
component.setHoverEvent( hoverEvent );
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +161,13 @@ public class BaseComponentSerializer
|
||||
{
|
||||
JsonObject hoverEvent = new JsonObject();
|
||||
hoverEvent.addProperty( "action", component.getHoverEvent().getAction().toString().toLowerCase( Locale.ROOT ) );
|
||||
hoverEvent.add( "value", context.serialize( component.getHoverEvent().getValue() ) );
|
||||
if ( component.getHoverEvent().isLegacy() )
|
||||
{
|
||||
hoverEvent.add( "value", context.serialize( component.getHoverEvent().getContents().get( 0 ) ) );
|
||||
} else
|
||||
{
|
||||
hoverEvent.add( "contents", context.serialize( component.getHoverEvent().getContents() ) );
|
||||
}
|
||||
object.add( "hoverEvent", hoverEvent );
|
||||
}
|
||||
} finally
|
||||
|
@ -11,6 +11,8 @@ import com.google.gson.JsonParser;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Set;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
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;
|
||||
@ -28,6 +30,10 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
|
||||
registerTypeAdapter( KeybindComponent.class, new KeybindComponentSerializer() ).
|
||||
registerTypeAdapter( ScoreComponent.class, new ScoreComponentSerializer() ).
|
||||
registerTypeAdapter( SelectorComponent.class, new SelectorComponentSerializer() ).
|
||||
registerTypeAdapter( HoverEvent.ContentEntity.class, new HoverEvent.ContentEntity.Serializer() ).
|
||||
registerTypeAdapter( HoverEvent.ContentText.class, new HoverEvent.ContentText.Serializer() ).
|
||||
registerTypeAdapter( HoverEvent.ContentItem.class, new HoverEvent.ContentItem.Serializer() ).
|
||||
registerTypeAdapter( ItemTag.class, new ItemTag.Serializer() ).
|
||||
create();
|
||||
|
||||
public static final ThreadLocal<Set<BaseComponent>> serializedComponents = new ThreadLocal<Set<BaseComponent>>();
|
||||
|
@ -30,11 +30,11 @@ public class TextComponentSerializer extends BaseComponentSerializer implements
|
||||
{
|
||||
List<BaseComponent> extra = src.getExtra();
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty( "text", src.getText() );
|
||||
if ( src.hasFormatting() || ( extra != null && !extra.isEmpty() ) )
|
||||
{
|
||||
serialize( object, src, context );
|
||||
}
|
||||
object.addProperty( "text", src.getText() );
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +124,70 @@ public class ComponentsTest
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testItemTag()
|
||||
{
|
||||
TextComponent component = new TextComponent( "Hello world" );
|
||||
HoverEvent.ContentItem content = new HoverEvent.ContentItem();
|
||||
content.setId( "minecraft:diamond_sword" );
|
||||
content.setCount( 1 );
|
||||
content.setTag( ItemTag.builder()
|
||||
.ench( new ItemTag.Enchantment( 5, 16 ) )
|
||||
.name( new TextComponent( "Sharp Sword" ) )
|
||||
.unbreakable( true )
|
||||
.lore( new ComponentBuilder( "Line1" ).create() )
|
||||
.lore( new ComponentBuilder( "Line2" ).create() )
|
||||
.build() );
|
||||
HoverEvent event = new HoverEvent( HoverEvent.Action.SHOW_ITEM, content );
|
||||
component.setHoverEvent( event );
|
||||
String serialised = ComponentSerializer.toString( component );
|
||||
BaseComponent[] deserialised = ComponentSerializer.parse( serialised );
|
||||
Assert.assertEquals( TextComponent.toLegacyText( deserialised ), TextComponent.toLegacyText( component ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModernShowAdvancement()
|
||||
{
|
||||
String advancement = "achievement.openInventory";
|
||||
// First do the text using the newer contents system
|
||||
HoverEvent hoverEvent = new HoverEvent(
|
||||
HoverEvent.Action.SHOW_TEXT,
|
||||
new HoverEvent.ContentText( advancement )
|
||||
);
|
||||
TextComponent component = new TextComponent( "test" );
|
||||
component.setHoverEvent( hoverEvent );
|
||||
Assert.assertEquals( component.getHoverEvent().getContents().size(), 1 );
|
||||
Assert.assertTrue( component.getHoverEvent().getContents().get( 0 ) instanceof HoverEvent.ContentText );
|
||||
Assert.assertEquals( ( (HoverEvent.ContentText) component.getHoverEvent().getContents().get( 0 ) ).getValue(), advancement );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHoverEventContents()
|
||||
{
|
||||
// First do the text using the newer contents system
|
||||
HoverEvent hoverEvent = new HoverEvent(
|
||||
HoverEvent.Action.SHOW_TEXT,
|
||||
new HoverEvent.ContentText( new ComponentBuilder( "First" ).create() ),
|
||||
new HoverEvent.ContentText( new ComponentBuilder( "Second" ).create() )
|
||||
);
|
||||
|
||||
TextComponent component = new TextComponent( "Sample text" );
|
||||
component.setHoverEvent( hoverEvent );
|
||||
Assert.assertEquals( hoverEvent.getContents().size(), 2 );
|
||||
Assert.assertFalse( hoverEvent.isLegacy() );
|
||||
String serialized = ComponentSerializer.toString( component );
|
||||
BaseComponent[] deserialized = ComponentSerializer.parse( serialized );
|
||||
Assert.assertEquals( component.getHoverEvent(), deserialized[0].getHoverEvent() );
|
||||
|
||||
// check the test still works with the value method
|
||||
hoverEvent = new HoverEvent( HoverEvent.Action.SHOW_TEXT, new ComponentBuilder( "Sample text" ).create() );
|
||||
Assert.assertEquals( hoverEvent.getContents().size(), 1 );
|
||||
Assert.assertTrue( hoverEvent.isLegacy() );
|
||||
serialized = ComponentSerializer.toString( component );
|
||||
deserialized = ComponentSerializer.parse( serialized );
|
||||
Assert.assertEquals( component.getHoverEvent(), deserialized[0].getHoverEvent() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatRetentionCopyFormatting()
|
||||
{
|
||||
|
@ -90,7 +90,6 @@ import net.md_5.bungee.module.ModuleManager;
|
||||
import net.md_5.bungee.netty.PipelineUtils;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
import net.md_5.bungee.protocol.ProtocolConstants;
|
||||
import net.md_5.bungee.protocol.packet.Chat;
|
||||
import net.md_5.bungee.protocol.packet.PluginMessage;
|
||||
import net.md_5.bungee.query.RemoteQuery;
|
||||
import net.md_5.bungee.scheduler.BungeeScheduler;
|
||||
@ -707,14 +706,20 @@ public class BungeeCord extends ProxyServer
|
||||
public void broadcast(BaseComponent... message)
|
||||
{
|
||||
getConsole().sendMessage( BaseComponent.toLegacyText( message ) );
|
||||
broadcast( new Chat( ComponentSerializer.toString( message ) ) );
|
||||
for ( ProxiedPlayer player : getPlayers() )
|
||||
{
|
||||
player.sendMessage( message );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcast(BaseComponent message)
|
||||
{
|
||||
getConsole().sendMessage( message.toLegacyText() );
|
||||
broadcast( new Chat( ComponentSerializer.toString( message ) ) );
|
||||
for ( ProxiedPlayer player : getPlayers() )
|
||||
{
|
||||
player.sendMessage( message );
|
||||
}
|
||||
}
|
||||
|
||||
public void addConnection(UserConnection con)
|
||||
|
@ -450,7 +450,7 @@ public final class UserConnection implements ProxiedPlayer
|
||||
public void sendMessage(ChatMessageType position, BaseComponent... message)
|
||||
{
|
||||
// transform score components
|
||||
message = ChatComponentTransformer.getInstance().transform( this, message );
|
||||
message = ChatComponentTransformer.getInstance().transform( this, true, message );
|
||||
|
||||
if ( position == ChatMessageType.ACTION_BAR )
|
||||
{
|
||||
@ -475,7 +475,7 @@ public final class UserConnection implements ProxiedPlayer
|
||||
@Override
|
||||
public void sendMessage(ChatMessageType position, BaseComponent message)
|
||||
{
|
||||
message = ChatComponentTransformer.getInstance().transform( this, message )[0];
|
||||
message = ChatComponentTransformer.getInstance().transform( this, true, message )[0];
|
||||
|
||||
// Action bar doesn't display the new JSON formattings, legacy works - send it using this for now
|
||||
if ( position == ChatMessageType.ACTION_BAR )
|
||||
@ -663,8 +663,8 @@ public final class UserConnection implements ProxiedPlayer
|
||||
@Override
|
||||
public void setTabHeader(BaseComponent header, BaseComponent footer)
|
||||
{
|
||||
header = ChatComponentTransformer.getInstance().transform( this, header )[0];
|
||||
footer = ChatComponentTransformer.getInstance().transform( this, footer )[0];
|
||||
header = ChatComponentTransformer.getInstance().transform( this, true, header )[0];
|
||||
footer = ChatComponentTransformer.getInstance().transform( this, true, footer )[0];
|
||||
|
||||
unsafe().sendPacket( new PlayerListHeaderFooter(
|
||||
ComponentSerializer.toString( header ),
|
||||
@ -675,8 +675,8 @@ public final class UserConnection implements ProxiedPlayer
|
||||
@Override
|
||||
public void setTabHeader(BaseComponent[] header, BaseComponent[] footer)
|
||||
{
|
||||
header = ChatComponentTransformer.getInstance().transform( this, header );
|
||||
footer = ChatComponentTransformer.getInstance().transform( this, footer );
|
||||
header = ChatComponentTransformer.getInstance().transform( this, true, header );
|
||||
footer = ChatComponentTransformer.getInstance().transform( this, true, footer );
|
||||
|
||||
unsafe().sendPacket( new PlayerListHeaderFooter(
|
||||
ComponentSerializer.toString( header ),
|
||||
|
@ -7,10 +7,12 @@ import java.util.regex.Pattern;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.ScoreComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.score.Score;
|
||||
import net.md_5.bungee.protocol.ProtocolConstants;
|
||||
|
||||
/**
|
||||
* This class transforms chat components by attempting to replace transformable
|
||||
@ -32,6 +34,32 @@ public final class ChatComponentTransformer
|
||||
*/
|
||||
private static final Pattern SELECTOR_PATTERN = Pattern.compile( "^@([pares])(?:\\[([^ ]*)\\])?$" );
|
||||
|
||||
public BaseComponent[] legacyHoverTransform(ProxiedPlayer player, BaseComponent... components)
|
||||
{
|
||||
if ( player.getPendingConnection().getVersion() < ProtocolConstants.MINECRAFT_1_16 )
|
||||
{
|
||||
for ( int i = 0; i < components.length; i++ )
|
||||
{
|
||||
BaseComponent next = components[i];
|
||||
if ( next.getHoverEvent().isLegacy() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
next = next.duplicate();
|
||||
next.getHoverEvent().setLegacy( true );
|
||||
if ( next.getHoverEvent().getContents().size() > 1 )
|
||||
{
|
||||
HoverEvent.Content exception = next.getHoverEvent().getContents().get( 0 );
|
||||
next.getHoverEvent().getContents().clear();
|
||||
next.getHoverEvent().getContents().add( exception );
|
||||
}
|
||||
components[i] = next;
|
||||
}
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
public static ChatComponentTransformer getInstance()
|
||||
{
|
||||
return INSTANCE;
|
||||
@ -44,14 +72,33 @@ public final class ChatComponentTransformer
|
||||
* {@link BaseComponent#getExtra()}).
|
||||
*
|
||||
* @param player player
|
||||
* @param component the component to transform
|
||||
* @param components the component to transform
|
||||
* @return the transformed component, or an array containing a single empty
|
||||
* TextComponent if the components are null or empty
|
||||
* @throws IllegalArgumentException if an entity selector pattern is present
|
||||
*/
|
||||
public BaseComponent[] transform(ProxiedPlayer player, BaseComponent... component)
|
||||
public BaseComponent[] transform(ProxiedPlayer player, BaseComponent... components)
|
||||
{
|
||||
if ( component == null || component.length < 1 || ( component.length == 1 && component[0] == null ) )
|
||||
return transform( player, false, components );
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a set of components, and attempt to transform the transformable
|
||||
* fields. Entity selectors <b>cannot</b> be evaluated. This will
|
||||
* recursively search for all extra components (see
|
||||
* {@link BaseComponent#getExtra()}).
|
||||
*
|
||||
* @param player player
|
||||
* @param transformHover if the hover event should replace contents with
|
||||
* value
|
||||
* @param components the component to transform
|
||||
* @return the transformed component, or an array containing a single empty
|
||||
* TextComponent if the components are null or empty
|
||||
* @throws IllegalArgumentException if an entity selector pattern is present
|
||||
*/
|
||||
public BaseComponent[] transform(ProxiedPlayer player, boolean transformHover, BaseComponent... components)
|
||||
{
|
||||
if ( components == null || components.length < 1 || ( components.length == 1 && components[0] == null ) )
|
||||
{
|
||||
return new BaseComponent[]
|
||||
{
|
||||
@ -59,7 +106,12 @@ public final class ChatComponentTransformer
|
||||
};
|
||||
}
|
||||
|
||||
for ( BaseComponent root : component )
|
||||
if ( transformHover )
|
||||
{
|
||||
components = legacyHoverTransform( player, components );
|
||||
}
|
||||
|
||||
for ( BaseComponent root : components )
|
||||
{
|
||||
if ( root.getExtra() != null && !root.getExtra().isEmpty() )
|
||||
{
|
||||
@ -72,7 +124,7 @@ public final class ChatComponentTransformer
|
||||
transformScoreComponent( player, (ScoreComponent) root );
|
||||
}
|
||||
}
|
||||
return component;
|
||||
return components;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user