Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-11-16 21:10:30 +01:00
Improve chat registry de/serialisation
now actually passes through the registry 1:1, magically making chat work properly on the server, woo!
Dieser Commit ist enthalten in:
Ursprung
5448f54380
Commit
fe54721ff3
@ -17,64 +17,37 @@
|
||||
|
||||
package com.velocitypowered.proxy.connection.registry;
|
||||
|
||||
/*
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.kyori.adventure.nbt.*;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.text.format.TextFormat;
|
||||
import net.kyori.adventure.translation.Translatable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
*/
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import net.kyori.adventure.nbt.BinaryTagTypes;
|
||||
import com.velocitypowered.proxy.connection.registry.chat.ChatTypeElement;
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.nbt.ListBinaryTag;
|
||||
import net.kyori.adventure.nbt.StringBinaryTag;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.text.format.TextFormat;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
// TODO Implement
|
||||
public class ChatData {
|
||||
|
||||
private static final ListBinaryTag EMPTY_LIST_TAG = ListBinaryTag.empty();
|
||||
private final String identifier;
|
||||
private final int id;
|
||||
@Nullable
|
||||
private final Decoration chatDecoration;
|
||||
@Nullable
|
||||
private final Priority narrationPriority;
|
||||
// TODO: move to own thing?
|
||||
@Nullable
|
||||
private final Decoration narrationDecoration;
|
||||
private final ChatTypeElement chatElement;
|
||||
private final ChatTypeElement overlayElement;
|
||||
private final ChatTypeElement narrationElement;
|
||||
|
||||
/**
|
||||
* Represents a ChatRegistry entry.
|
||||
*
|
||||
* @param id chat type id
|
||||
* @param identifier chat type identifier
|
||||
* @param chatDecoration chat decoration
|
||||
* @param narrationDecoration narration decoration
|
||||
* @param chatElement chat element
|
||||
* @param overlayElement overlay element
|
||||
* @param narrationElement narration element
|
||||
*/
|
||||
public ChatData(int id, String identifier, @Nullable Decoration chatDecoration, @Nullable Priority narrationPriority, @Nullable Decoration narrationDecoration) {
|
||||
public ChatData(int id, String identifier, @Nullable ChatTypeElement chatElement, ChatTypeElement overlayElement,
|
||||
@Nullable ChatTypeElement narrationElement) {
|
||||
this.id = id;
|
||||
this.identifier = identifier;
|
||||
this.chatDecoration = chatDecoration;
|
||||
this.narrationPriority = narrationPriority;
|
||||
this.narrationDecoration = narrationDecoration;
|
||||
this.chatElement = chatElement;
|
||||
this.overlayElement = overlayElement;
|
||||
this.narrationElement = narrationElement;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,32 +67,36 @@ public class ChatData {
|
||||
}
|
||||
|
||||
private ChatData annotateWith(Integer id, String registryIdentifier) {
|
||||
return new ChatData(id, registryIdentifier, this.chatDecoration, this.narrationPriority, this.narrationDecoration);
|
||||
return new ChatData(id, registryIdentifier, this.chatElement, this.overlayElement,
|
||||
this.narrationElement);
|
||||
}
|
||||
|
||||
private static ChatData decodeElementCompound(CompoundBinaryTag element, ProtocolVersion version) {
|
||||
Decoration chatDecoration = null;
|
||||
Decoration narrationDecoration = null;
|
||||
Priority narrationPriority = null;
|
||||
ChatTypeElement chatElement = null;
|
||||
ChatTypeElement overlayElement = null;
|
||||
ChatTypeElement narrationElement = null;
|
||||
|
||||
final CompoundBinaryTag chatCompound = element.getCompound("chat");
|
||||
final CompoundBinaryTag chatDecorationCompound = (CompoundBinaryTag) chatCompound.get("decoration");
|
||||
if (chatDecorationCompound != null) {
|
||||
chatDecoration = Decoration.decodeRegistryEntry(chatDecorationCompound);
|
||||
final BinaryTag chatCompound = element.get("chat");
|
||||
if (chatCompound != null) {
|
||||
chatElement =
|
||||
ChatTypeElement.decodeFromRegistry(ChatTypeElement.ElementType.CHAT, (CompoundBinaryTag) chatCompound,
|
||||
version);
|
||||
}
|
||||
|
||||
final CompoundBinaryTag narrationCompound = element.getCompound("narration");
|
||||
final String priorityString = narrationCompound.getString("priority");
|
||||
if (!priorityString.isEmpty()) {
|
||||
narrationPriority = Priority.valueOf(priorityString.toUpperCase(Locale.ROOT));
|
||||
final BinaryTag overlayCompound = element.get("overlay");
|
||||
if (overlayCompound != null) {
|
||||
overlayElement =
|
||||
ChatTypeElement.decodeFromRegistry(ChatTypeElement.ElementType.OVERLAY, (CompoundBinaryTag) overlayCompound,
|
||||
version);
|
||||
}
|
||||
|
||||
final CompoundBinaryTag narrationDecorationCompound = (CompoundBinaryTag) narrationCompound.get("decoration");
|
||||
if (narrationDecorationCompound != null) {
|
||||
narrationDecoration = Decoration.decodeRegistryEntry(narrationDecorationCompound);
|
||||
final BinaryTag narrationCompound = element.get("narration");
|
||||
if (narrationCompound != null) {
|
||||
narrationElement = ChatTypeElement.decodeFromRegistry(ChatTypeElement.ElementType.NARRATION,
|
||||
(CompoundBinaryTag) narrationCompound, version);
|
||||
}
|
||||
|
||||
return new ChatData(-1, "invalid", chatDecoration, narrationPriority, narrationDecoration);
|
||||
return new ChatData(-1, "invalid", chatElement, overlayElement, narrationElement);
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
@ -143,22 +120,16 @@ public class ChatData {
|
||||
|
||||
final CompoundBinaryTag.Builder elementCompound = CompoundBinaryTag.builder();
|
||||
|
||||
CompoundBinaryTag.@NotNull Builder chatCompound = CompoundBinaryTag.builder();
|
||||
if (chatDecoration != null) {
|
||||
chatCompound.put("decoration", chatDecoration.encodeRegistryEntry(version));
|
||||
elementCompound.put("chat", chatCompound.build());
|
||||
if (chatElement != null) {
|
||||
elementCompound.put("chat", chatElement.encodeForRegistry(version));
|
||||
}
|
||||
|
||||
final CompoundBinaryTag.Builder narrationCompoundBuilder = CompoundBinaryTag.builder();
|
||||
if (narrationPriority != null) {
|
||||
narrationCompoundBuilder.putString("priority", narrationPriority.name().toLowerCase(Locale.ROOT));
|
||||
if (overlayElement != null) {
|
||||
elementCompound.put("overlay", overlayElement.encodeForRegistry(version));
|
||||
}
|
||||
if (narrationDecoration != null) {
|
||||
narrationCompoundBuilder.put("decoration", narrationDecoration.encodeRegistryEntry(version));
|
||||
}
|
||||
final CompoundBinaryTag narrationCompound = narrationCompoundBuilder.build();
|
||||
if (!narrationCompound.equals(CompoundBinaryTag.empty())) {
|
||||
elementCompound.put("narration", narrationCompound);
|
||||
|
||||
if (narrationElement != null) {
|
||||
elementCompound.put("narration", narrationElement.encodeForRegistry(version));
|
||||
}
|
||||
|
||||
compound.put("element", elementCompound.build());
|
||||
@ -167,107 +138,6 @@ public class ChatData {
|
||||
}
|
||||
|
||||
|
||||
public static class Decoration {
|
||||
|
||||
private final List<String> parameters;
|
||||
private final List<TextFormat> style;
|
||||
@Nullable
|
||||
private final String translationKey;
|
||||
|
||||
public List<String> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public List<TextFormat> getStyle() {
|
||||
return style;
|
||||
}
|
||||
|
||||
public @Nullable String translationKey() {
|
||||
return translationKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Decoration with the associated data.
|
||||
*
|
||||
* @param parameters chat params
|
||||
* @param style chat style
|
||||
* @param translationKey translation key
|
||||
*/
|
||||
public Decoration(List<String> parameters, List<TextFormat> style, @Nullable String translationKey) {
|
||||
this.parameters = Preconditions.checkNotNull(parameters);
|
||||
this.style = Preconditions.checkNotNull(style);
|
||||
this.translationKey = translationKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a decoration entry.
|
||||
*
|
||||
* @param toDecode Compound Tag to decode
|
||||
* @return the parsed Decoration entry.
|
||||
*/
|
||||
public static Decoration decodeRegistryEntry(CompoundBinaryTag toDecode) {
|
||||
ImmutableList.Builder<String> parameters = ImmutableList.builder();
|
||||
ListBinaryTag paramList = toDecode.getList("parameters", EMPTY_LIST_TAG);
|
||||
if (paramList != EMPTY_LIST_TAG) {
|
||||
paramList.forEach(binaryTag -> parameters.add(((StringBinaryTag) binaryTag).value()));
|
||||
}
|
||||
|
||||
ImmutableList.Builder<TextFormat> style = ImmutableList.builder();
|
||||
CompoundBinaryTag styleList = toDecode.getCompound("style");
|
||||
for (String key : styleList.keySet()) {
|
||||
if ("color".equals(key)) {
|
||||
NamedTextColor color = Preconditions.checkNotNull(
|
||||
NamedTextColor.NAMES.value(styleList.getString(key)));
|
||||
style.add(color);
|
||||
} else {
|
||||
// Key is a Style option instead
|
||||
TextDecoration deco = TextDecoration.NAMES.value(key);
|
||||
// This wouldn't be here if it wasn't applied, but here it goes anyway:
|
||||
byte val = styleList.getByte(key);
|
||||
if (val != 0) {
|
||||
style.add(deco);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String translationKey = toDecode.getString("translation_key");
|
||||
|
||||
return new Decoration(parameters.build(), style.build(), translationKey.isEmpty() ? null : translationKey);
|
||||
}
|
||||
|
||||
public CompoundBinaryTag encodeRegistryEntry(ProtocolVersion version) {
|
||||
|
||||
CompoundBinaryTag.Builder compoundBinaryTag = CompoundBinaryTag.builder();
|
||||
|
||||
if (translationKey != null) {
|
||||
compoundBinaryTag.put("translation_key", StringBinaryTag.of(translationKey));
|
||||
}
|
||||
|
||||
final CompoundBinaryTag.Builder styleBuilder = CompoundBinaryTag.builder();
|
||||
style.forEach(styleEntry -> {
|
||||
if (styleEntry instanceof TextColor color) {
|
||||
styleBuilder.putString("color", color.toString());
|
||||
} else if (styleEntry instanceof TextDecoration decoration) {
|
||||
styleBuilder.putByte(decoration.name().toLowerCase(Locale.ROOT), (byte) 1); // This won't be here if not applied
|
||||
}
|
||||
});
|
||||
compoundBinaryTag.put("style", styleBuilder.build());
|
||||
|
||||
if (parameters.size() == 0) {
|
||||
compoundBinaryTag.put("parameters", EMPTY_LIST_TAG);
|
||||
} else {
|
||||
final ListBinaryTag.Builder<StringBinaryTag> parametersBuilder = ListBinaryTag.builder(BinaryTagTypes.STRING);
|
||||
parameters.forEach(param -> parametersBuilder.add(StringBinaryTag.of(param)));
|
||||
compoundBinaryTag.put("parameters", parametersBuilder.build());
|
||||
}
|
||||
|
||||
|
||||
|
||||
return compoundBinaryTag.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static enum Priority {
|
||||
SYSTEM,
|
||||
CHAT
|
||||
|
@ -46,31 +46,10 @@ public final class ChatRegistry {
|
||||
*/
|
||||
public static ImmutableList<ChatData> fromGameData(ListBinaryTag compound, ProtocolVersion version) {
|
||||
final ImmutableList.Builder<ChatData> builder = ImmutableList.builder();
|
||||
try {
|
||||
System.out.println(TagStringIO.builder().indent(2).build().asString(CompoundBinaryTag.empty().put("tag", compound)));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
for (BinaryTag binaryTag : compound) {
|
||||
if (binaryTag instanceof CompoundBinaryTag) {
|
||||
final ChatData chatData = ChatData.decodeRegistryEntry((CompoundBinaryTag) binaryTag, version);
|
||||
builder.add(chatData);
|
||||
System.out.println(chatData.encodeAsCompoundTag(version).equals(binaryTag));
|
||||
System.out.println("========");
|
||||
System.out.println("========");
|
||||
try {
|
||||
System.out.println(TagStringIO.builder().indent(2).build().asString((CompoundBinaryTag) binaryTag));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
System.out.println("========");
|
||||
try {
|
||||
System.out.println(TagStringIO.builder().indent(2).build().asString(chatData.encodeAsCompoundTag(version)));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
System.out.println("========");
|
||||
System.out.println("========");
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
|
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Velocity Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.velocitypowered.proxy.connection.registry.chat;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import net.kyori.adventure.nbt.BinaryTagTypes;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.nbt.ListBinaryTag;
|
||||
import net.kyori.adventure.nbt.StringBinaryTag;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.text.format.TextFormat;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class ChatDecoration {
|
||||
|
||||
private final List<String> parameters;
|
||||
private final List<TextFormat> style;
|
||||
@Nullable
|
||||
private final String translationKey;
|
||||
|
||||
public List<String> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public List<TextFormat> getStyle() {
|
||||
return style;
|
||||
}
|
||||
|
||||
public @Nullable String translationKey() {
|
||||
return translationKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Decoration with the associated data.
|
||||
*
|
||||
* @param parameters chat params
|
||||
* @param style chat style
|
||||
* @param translationKey translation key
|
||||
*/
|
||||
public ChatDecoration(List<String> parameters, List<TextFormat> style, @Nullable String translationKey) {
|
||||
this.parameters = Preconditions.checkNotNull(parameters);
|
||||
this.style = Preconditions.checkNotNull(style);
|
||||
this.translationKey = translationKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a decoration entry.
|
||||
*
|
||||
* @param toDecode Compound Tag to decode
|
||||
* @param version
|
||||
* @return the parsed Decoration entry.
|
||||
*/
|
||||
public static ChatDecoration decodeRegistryEntry(CompoundBinaryTag toDecode, ProtocolVersion version) {
|
||||
ImmutableList.Builder<String> parameters = ImmutableList.builder();
|
||||
ListBinaryTag paramList = toDecode.getList("parameters", ListBinaryTag.empty());
|
||||
if (paramList != ListBinaryTag.empty()) {
|
||||
paramList.forEach(binaryTag -> parameters.add(((StringBinaryTag) binaryTag).value()));
|
||||
}
|
||||
|
||||
ImmutableList.Builder<TextFormat> style = ImmutableList.builder();
|
||||
CompoundBinaryTag styleList = toDecode.getCompound("style");
|
||||
for (String key : styleList.keySet()) {
|
||||
if ("color".equals(key)) {
|
||||
final NamedTextColor value = NamedTextColor.NAMES.value(styleList.getString(key));
|
||||
if (value != null) {
|
||||
style.add(value);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unable to map color value: " + styleList.getString(key));
|
||||
}
|
||||
} else {
|
||||
// Key is a Style option instead
|
||||
TextDecoration deco = TextDecoration.NAMES.value(key);
|
||||
if (deco == null) {
|
||||
throw new IllegalArgumentException("Unable to map text style of: " + key);
|
||||
}
|
||||
// This wouldn't be here if it wasn't applied, but here it goes anyway:
|
||||
byte val = styleList.getByte(key);
|
||||
if (val != 0) {
|
||||
style.add(deco);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String translationKey = toDecode.getString("translation_key");
|
||||
|
||||
return new ChatDecoration(parameters.build(), style.build(), translationKey.isEmpty() ? null : translationKey);
|
||||
}
|
||||
|
||||
public CompoundBinaryTag encodeRegistryEntry(ProtocolVersion version) {
|
||||
|
||||
CompoundBinaryTag.Builder compoundBinaryTag = CompoundBinaryTag.builder();
|
||||
|
||||
if (translationKey != null) {
|
||||
compoundBinaryTag.put("translation_key", StringBinaryTag.of(translationKey));
|
||||
}
|
||||
|
||||
final CompoundBinaryTag.Builder styleBuilder = CompoundBinaryTag.builder();
|
||||
style.forEach(styleEntry -> {
|
||||
if (styleEntry instanceof TextColor color) {
|
||||
styleBuilder.putString("color", color.toString());
|
||||
} else if (styleEntry instanceof TextDecoration decoration) {
|
||||
styleBuilder.putByte(decoration.name().toLowerCase(Locale.ROOT), (byte) 1); // This won't be here if not applied
|
||||
}
|
||||
});
|
||||
compoundBinaryTag.put("style", styleBuilder.build());
|
||||
|
||||
if (parameters.size() == 0) {
|
||||
compoundBinaryTag.put("parameters", ListBinaryTag.empty());
|
||||
} else {
|
||||
final ListBinaryTag.Builder<StringBinaryTag> parametersBuilder = ListBinaryTag.builder(BinaryTagTypes.STRING);
|
||||
parameters.forEach(param -> parametersBuilder.add(StringBinaryTag.of(param)));
|
||||
compoundBinaryTag.put("parameters", parametersBuilder.build());
|
||||
}
|
||||
|
||||
|
||||
return compoundBinaryTag.build();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Velocity Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.velocitypowered.proxy.connection.registry.chat;
|
||||
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.connection.registry.ChatData;
|
||||
import java.util.Locale;
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.nbt.StringBinaryTag;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class ChatTypeElement {
|
||||
|
||||
|
||||
@Nullable
|
||||
private final ElementType type;
|
||||
@Nullable
|
||||
private final ChatDecoration decoration;
|
||||
private final ChatData.@Nullable Priority priority;
|
||||
|
||||
public ChatTypeElement(ElementType type, ChatDecoration decoration, ChatData.Priority priority) {
|
||||
this.type = type;
|
||||
this.decoration = decoration;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public static ChatTypeElement decodeFromRegistry(ElementType type, CompoundBinaryTag elementCompound, ProtocolVersion version) {
|
||||
ChatDecoration decoration = null;
|
||||
ChatData.Priority priority = null;
|
||||
final CompoundBinaryTag decorationCompound = (CompoundBinaryTag) elementCompound.get("decoration");
|
||||
if (decorationCompound != null) {
|
||||
decoration = ChatDecoration.decodeRegistryEntry(decorationCompound, version);
|
||||
}
|
||||
|
||||
if (elementCompound.get("priority") != null) {
|
||||
priority = ChatData.Priority.valueOf(elementCompound.getString("priority").toUpperCase(Locale.ROOT));
|
||||
}
|
||||
return new ChatTypeElement(type, decoration, priority);
|
||||
}
|
||||
|
||||
public CompoundBinaryTag encodeForRegistry(ProtocolVersion version) {
|
||||
final CompoundBinaryTag.Builder compoundBuilder = CompoundBinaryTag.builder();
|
||||
if (priority != null) {
|
||||
compoundBuilder.put("priority", StringBinaryTag.of(priority.name().toLowerCase(Locale.ROOT)));
|
||||
}
|
||||
|
||||
if (decoration != null) {
|
||||
compoundBuilder.put("decoration", decoration.encodeRegistryEntry(version));
|
||||
}
|
||||
|
||||
return compoundBuilder.build();
|
||||
}
|
||||
|
||||
public enum ElementType {
|
||||
CHAT,
|
||||
OVERLAY,
|
||||
NARRATION
|
||||
}
|
||||
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren