503 lines
17 KiB
Java
503 lines
17 KiB
Java
package com.hypixel.hytale.server.core;
|
|
|
|
import com.hypixel.hytale.codec.Codec;
|
|
import com.hypixel.hytale.codec.EmptyExtraInfo;
|
|
import com.hypixel.hytale.codec.ExtraInfo;
|
|
import com.hypixel.hytale.codec.KeyedCodec;
|
|
import com.hypixel.hytale.codec.builder.BuilderCodec;
|
|
import com.hypixel.hytale.codec.codecs.array.ArrayCodec;
|
|
import com.hypixel.hytale.codec.codecs.map.MapCodec;
|
|
import com.hypixel.hytale.codec.function.FunctionCodec;
|
|
import com.hypixel.hytale.codec.schema.SchemaContext;
|
|
import com.hypixel.hytale.codec.schema.config.BooleanSchema;
|
|
import com.hypixel.hytale.codec.schema.config.IntegerSchema;
|
|
import com.hypixel.hytale.codec.schema.config.NullSchema;
|
|
import com.hypixel.hytale.codec.schema.config.NumberSchema;
|
|
import com.hypixel.hytale.codec.schema.config.Schema;
|
|
import com.hypixel.hytale.codec.schema.config.StringSchema;
|
|
import com.hypixel.hytale.codec.util.RawJsonReader;
|
|
import com.hypixel.hytale.common.util.ArrayUtil;
|
|
import com.hypixel.hytale.protocol.BoolParamValue;
|
|
import com.hypixel.hytale.protocol.DoubleParamValue;
|
|
import com.hypixel.hytale.protocol.FormattedMessage;
|
|
import com.hypixel.hytale.protocol.IntParamValue;
|
|
import com.hypixel.hytale.protocol.LongParamValue;
|
|
import com.hypixel.hytale.protocol.MaybeBool;
|
|
import com.hypixel.hytale.protocol.ParamValue;
|
|
import com.hypixel.hytale.protocol.StringParamValue;
|
|
import com.hypixel.hytale.server.core.asset.util.ColorParseUtil;
|
|
import com.hypixel.hytale.server.core.modules.i18n.I18nModule;
|
|
import com.hypixel.hytale.server.core.util.MessageUtil;
|
|
import com.hypixel.hytale.sneakythrow.SneakyThrow;
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
import java.awt.Color;
|
|
import java.io.IOException;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map.Entry;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
import org.bson.BsonBoolean;
|
|
import org.bson.BsonDouble;
|
|
import org.bson.BsonInt32;
|
|
import org.bson.BsonInt64;
|
|
import org.bson.BsonNull;
|
|
import org.bson.BsonString;
|
|
import org.bson.BsonValue;
|
|
import org.checkerframework.checker.nullness.compatqual.NonNullDecl;
|
|
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
|
|
|
|
public class Message {
|
|
private static final BuilderCodec.Builder<FormattedMessage> MESSAGE_CODEC_BUILDER = BuilderCodec.builder(FormattedMessage.class, FormattedMessage::new);
|
|
private static final BuilderCodec<FormattedMessage> MESSAGE_CODEC = MESSAGE_CODEC_BUILDER.build();
|
|
private static final Codec<ParamValue> PARAM_CODEC = new Message.ParamValueCodec();
|
|
private static final Codec<MaybeBool> MAYBE_BOOL_CODEC = new Message.MaybeBoolCodec();
|
|
public static final FunctionCodec<FormattedMessage, Message> CODEC = new FunctionCodec<>(MESSAGE_CODEC, Message::new, Message::getFormattedMessage);
|
|
private final FormattedMessage message;
|
|
|
|
protected Message(@Nonnull String message, boolean i18n) {
|
|
this();
|
|
if (i18n) {
|
|
this.message.messageId = message;
|
|
} else {
|
|
this.message.rawText = message;
|
|
}
|
|
}
|
|
|
|
protected Message() {
|
|
this.message = new FormattedMessage();
|
|
}
|
|
|
|
public Message(@Nonnull FormattedMessage message) {
|
|
this.message = message;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message param(@Nonnull String key, @Nonnull String value) {
|
|
if (this.message.params == null) {
|
|
this.message.params = new HashMap<>();
|
|
}
|
|
|
|
StringParamValue val = new StringParamValue();
|
|
val.value = value;
|
|
this.message.params.put(key, val);
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message param(@Nonnull String key, boolean value) {
|
|
if (this.message.params == null) {
|
|
this.message.params = new HashMap<>();
|
|
}
|
|
|
|
BoolParamValue val = new BoolParamValue();
|
|
val.value = value;
|
|
this.message.params.put(key, val);
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message param(@Nonnull String key, double value) {
|
|
if (this.message.params == null) {
|
|
this.message.params = new HashMap<>();
|
|
}
|
|
|
|
DoubleParamValue val = new DoubleParamValue();
|
|
val.value = value;
|
|
this.message.params.put(key, val);
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message param(@Nonnull String key, int value) {
|
|
if (this.message.params == null) {
|
|
this.message.params = new HashMap<>();
|
|
}
|
|
|
|
IntParamValue val = new IntParamValue();
|
|
val.value = value;
|
|
this.message.params.put(key, val);
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message param(@Nonnull String key, long value) {
|
|
if (this.message.params == null) {
|
|
this.message.params = new HashMap<>();
|
|
}
|
|
|
|
LongParamValue val = new LongParamValue();
|
|
val.value = value;
|
|
this.message.params.put(key, val);
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message param(@Nonnull String key, float value) {
|
|
if (this.message.params == null) {
|
|
this.message.params = new HashMap<>();
|
|
}
|
|
|
|
DoubleParamValue val = new DoubleParamValue();
|
|
val.value = value;
|
|
this.message.params.put(key, val);
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message param(@Nonnull String key, @Nonnull Message formattedMessage) {
|
|
if (this.message.messageParams == null) {
|
|
this.message.messageParams = new HashMap<>();
|
|
}
|
|
|
|
this.message.messageParams.put(key, formattedMessage.message);
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message bold(boolean bold) {
|
|
this.message.bold = bold ? MaybeBool.True : MaybeBool.False;
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message italic(boolean italic) {
|
|
this.message.italic = italic ? MaybeBool.True : MaybeBool.False;
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message monospace(boolean monospace) {
|
|
this.message.monospace = monospace ? MaybeBool.True : MaybeBool.False;
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message color(@Nonnull String color) {
|
|
this.message.color = color;
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message color(@Nonnull Color color) {
|
|
this.message.color = ColorParseUtil.colorToHex(color);
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message link(@Nonnull String url) {
|
|
this.message.link = url;
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message insert(@Nonnull Message formattedMessage) {
|
|
this.message.children = ArrayUtil.append(this.message.children, formattedMessage.message);
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message insert(@Nonnull String message) {
|
|
return this.insert(raw(message));
|
|
}
|
|
|
|
@Nonnull
|
|
public Message insertAll(@Nonnull Message... formattedMessages) {
|
|
int offset = 0;
|
|
if (this.message.children != null) {
|
|
offset = this.message.children.length;
|
|
this.message.children = Arrays.copyOf(this.message.children, this.message.children.length + formattedMessages.length);
|
|
} else {
|
|
this.message.children = new FormattedMessage[formattedMessages.length];
|
|
}
|
|
|
|
for (Message formattedMessage : formattedMessages) {
|
|
this.message.children[offset++] = formattedMessage.message;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Message insertAll(@Nonnull List<Message> formattedMessages) {
|
|
int offset = 0;
|
|
if (this.message.children != null) {
|
|
offset = this.message.children.length;
|
|
this.message.children = Arrays.copyOf(this.message.children, this.message.children.length + formattedMessages.size());
|
|
} else {
|
|
this.message.children = new FormattedMessage[formattedMessages.size()];
|
|
}
|
|
|
|
for (Message formattedMessage : formattedMessages) {
|
|
this.message.children[offset++] = formattedMessage.message;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
@Nullable
|
|
public String getRawText() {
|
|
return this.message.rawText;
|
|
}
|
|
|
|
@Nullable
|
|
public String getMessageId() {
|
|
return this.message.messageId;
|
|
}
|
|
|
|
@Nullable
|
|
public String getColor() {
|
|
return this.message.color;
|
|
}
|
|
|
|
@Nonnull
|
|
public List<Message> getChildren() {
|
|
if (this.message.children == null) {
|
|
return Collections.emptyList();
|
|
} else {
|
|
List<Message> children = new ObjectArrayList();
|
|
|
|
for (FormattedMessage value : this.message.children) {
|
|
children.add(new Message(value));
|
|
}
|
|
|
|
return children;
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
public String getAnsiMessage() {
|
|
String rawText = this.getRawText();
|
|
if (rawText != null) {
|
|
return rawText;
|
|
} else {
|
|
String messageId = this.getMessageId();
|
|
if (messageId == null) {
|
|
return "";
|
|
} else {
|
|
String message = I18nModule.get().getMessage("en-US", messageId);
|
|
if (message != null) {
|
|
return MessageUtil.formatText(message, this.message.params, this.message.messageParams);
|
|
} else {
|
|
StringBuilder rawMessage = new StringBuilder(messageId);
|
|
if (this.message.params != null) {
|
|
rawMessage.append(this.message.params);
|
|
}
|
|
|
|
if (this.message.messageParams != null) {
|
|
for (Entry<String, FormattedMessage> p : this.message.messageParams.entrySet()) {
|
|
rawMessage.append(p.getValue()).append("=").append(new Message(p.getValue()).getAnsiMessage());
|
|
}
|
|
}
|
|
|
|
return rawMessage.toString();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public FormattedMessage getFormattedMessage() {
|
|
return this.message;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return this.message.toString();
|
|
}
|
|
|
|
@Nonnull
|
|
public static Message empty() {
|
|
return new Message();
|
|
}
|
|
|
|
@Nonnull
|
|
public static Message translation(@Nonnull String messageId) {
|
|
return new Message(messageId, true);
|
|
}
|
|
|
|
@Nonnull
|
|
public static Message raw(@Nonnull String message) {
|
|
return new Message(message, false);
|
|
}
|
|
|
|
@Nonnull
|
|
public static Message parse(@Nonnull String message) {
|
|
try {
|
|
return CODEC.decodeJson(new RawJsonReader(message.toCharArray()), EmptyExtraInfo.EMPTY);
|
|
} catch (IOException var2) {
|
|
throw SneakyThrow.sneakyThrow(var2);
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
public static Message join(@Nonnull Message... messages) {
|
|
return new Message().insertAll(messages);
|
|
}
|
|
|
|
static {
|
|
MESSAGE_CODEC_BUILDER.appendInherited(new KeyedCodec<>("RawText", Codec.STRING), (o, v) -> o.rawText = v, o -> o.rawText, (o, p) -> o.rawText = p.rawText)
|
|
.add()
|
|
.appendInherited(new KeyedCodec<>("MessageId", Codec.STRING), (o, v) -> o.messageId = v, o -> o.messageId, (o, p) -> o.messageId = p.messageId)
|
|
.add()
|
|
.appendInherited(
|
|
new KeyedCodec<>("Params", new MapCodec<>(PARAM_CODEC, HashMap::new)), (o, v) -> o.params = v, o -> o.params, (o, p) -> o.params = p.params
|
|
)
|
|
.add()
|
|
.appendInherited(
|
|
new KeyedCodec<>("MessageParams", new MapCodec<>(MESSAGE_CODEC, HashMap::new)),
|
|
(o, v) -> o.messageParams = v,
|
|
o -> o.messageParams,
|
|
(o, p) -> o.messageParams = p.messageParams
|
|
)
|
|
.add()
|
|
.appendInherited(
|
|
new KeyedCodec<>("Children", new ArrayCodec<>(MESSAGE_CODEC, FormattedMessage[]::new)),
|
|
(o, v) -> o.children = v,
|
|
o -> o.children,
|
|
(o, p) -> o.children = p.children
|
|
)
|
|
.add()
|
|
.appendInherited(new KeyedCodec<>("Bold", MAYBE_BOOL_CODEC), (o, v) -> o.bold = v != null ? v : MaybeBool.Null, o -> o.bold, (o, p) -> o.bold = p.bold)
|
|
.add()
|
|
.appendInherited(
|
|
new KeyedCodec<>("Italic", MAYBE_BOOL_CODEC), (o, v) -> o.italic = v != null ? v : MaybeBool.Null, o -> o.italic, (o, p) -> o.italic = p.italic
|
|
)
|
|
.add()
|
|
.appendInherited(
|
|
new KeyedCodec<>("Monospace", MAYBE_BOOL_CODEC),
|
|
(o, v) -> o.monospace = v != null ? v : MaybeBool.Null,
|
|
o -> o.monospace,
|
|
(o, p) -> o.monospace = p.monospace
|
|
)
|
|
.add()
|
|
.appendInherited(
|
|
new KeyedCodec<>("Underline", MAYBE_BOOL_CODEC),
|
|
(o, v) -> o.underlined = v != null ? v : MaybeBool.Null,
|
|
o -> o.underlined,
|
|
(o, p) -> o.underlined = p.underlined
|
|
)
|
|
.add()
|
|
.appendInherited(new KeyedCodec<>("Color", Codec.STRING), (o, v) -> o.color = v, o -> o.color, (o, p) -> o.color = p.color)
|
|
.add()
|
|
.appendInherited(new KeyedCodec<>("Link", Codec.STRING), (o, v) -> o.link = v, o -> o.link, (o, p) -> o.link = p.link)
|
|
.add();
|
|
}
|
|
|
|
private static class MaybeBoolCodec implements Codec<MaybeBool> {
|
|
@Nullable
|
|
public MaybeBool decode(BsonValue bsonValue, ExtraInfo extraInfo) {
|
|
if (bsonValue.isNull()) {
|
|
return MaybeBool.Null;
|
|
} else {
|
|
return bsonValue.asBoolean().getValue() ? MaybeBool.True : MaybeBool.False;
|
|
}
|
|
}
|
|
|
|
public BsonValue encode(MaybeBool maybeBool, ExtraInfo extraInfo) {
|
|
return (BsonValue)(switch (maybeBool) {
|
|
case Null -> BsonNull.VALUE;
|
|
case False -> BsonBoolean.FALSE;
|
|
case True -> BsonBoolean.TRUE;
|
|
});
|
|
}
|
|
|
|
@NullableDecl
|
|
public MaybeBool decodeJson(@NonNullDecl RawJsonReader reader, ExtraInfo extraInfo) throws IOException {
|
|
if (reader.peekFor('n')) {
|
|
if (!reader.tryConsume("null")) {
|
|
throw new IllegalArgumentException("Invalid null value");
|
|
} else {
|
|
return MaybeBool.Null;
|
|
}
|
|
} else if (reader.peekFor('N')) {
|
|
if (!reader.tryConsume("NULL")) {
|
|
throw new IllegalArgumentException("Invalid null value");
|
|
} else {
|
|
return MaybeBool.Null;
|
|
}
|
|
} else {
|
|
return reader.readBooleanValue() ? MaybeBool.True : MaybeBool.False;
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public Schema toSchema(@Nonnull SchemaContext context) {
|
|
return Schema.anyOf(new BooleanSchema(), new NullSchema());
|
|
}
|
|
}
|
|
|
|
private static class ParamValueCodec implements Codec<ParamValue> {
|
|
@Nullable
|
|
public ParamValue decode(BsonValue bsonValue, ExtraInfo extraInfo) {
|
|
return (ParamValue)(switch (bsonValue.getBsonType()) {
|
|
case DOUBLE -> {
|
|
DoubleParamValue value = new DoubleParamValue();
|
|
value.value = bsonValue.asDouble().getValue();
|
|
yield value;
|
|
}
|
|
case STRING -> {
|
|
StringParamValue value = new StringParamValue();
|
|
value.value = bsonValue.asString().getValue();
|
|
yield value;
|
|
}
|
|
case BOOLEAN -> {
|
|
BoolParamValue value = new BoolParamValue();
|
|
value.value = bsonValue.asBoolean().getValue();
|
|
yield value;
|
|
}
|
|
case INT32 -> {
|
|
IntParamValue value = new IntParamValue();
|
|
value.value = bsonValue.asInt32().getValue();
|
|
yield value;
|
|
}
|
|
case INT64 -> {
|
|
LongParamValue value = new LongParamValue();
|
|
value.value = bsonValue.asInt64().getValue();
|
|
yield value;
|
|
}
|
|
default -> throw new IllegalArgumentException("Unsupported bson type: " + bsonValue.getBsonType());
|
|
});
|
|
}
|
|
|
|
public BsonValue encode(ParamValue paramValue, ExtraInfo extraInfo) {
|
|
return (BsonValue)(switch (paramValue) {
|
|
case StringParamValue s -> new BsonString(s.value);
|
|
case BoolParamValue b -> BsonBoolean.valueOf(b.value);
|
|
case DoubleParamValue d -> new BsonDouble(d.value);
|
|
case IntParamValue i -> new BsonInt32(i.value);
|
|
case LongParamValue l -> new BsonInt64(l.value);
|
|
default -> throw new IllegalArgumentException("Unknown ParamValue type: " + paramValue.getClass());
|
|
});
|
|
}
|
|
|
|
@Nullable
|
|
public ParamValue decodeJson(@Nonnull RawJsonReader reader, ExtraInfo extraInfo) throws IOException {
|
|
if (reader.peekFor('"')) {
|
|
StringParamValue param = new StringParamValue();
|
|
param.value = reader.readString();
|
|
return param;
|
|
} else if (!reader.peekFor('f') && !reader.peekFor('t')) {
|
|
double value = reader.readDoubleValue();
|
|
DoubleParamValue param = new DoubleParamValue();
|
|
param.value = value;
|
|
return param;
|
|
} else if (!reader.tryConsume("false")) {
|
|
throw new IllegalArgumentException("Invalid boolean value");
|
|
} else {
|
|
BoolParamValue param = new BoolParamValue();
|
|
param.value = reader.readBooleanValue();
|
|
return param;
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public Schema toSchema(@Nonnull SchemaContext context) {
|
|
return Schema.anyOf(new BooleanSchema(), new NumberSchema(), new IntegerSchema(), new StringSchema());
|
|
}
|
|
}
|
|
}
|