Mirror von
https://github.com/ViaVersion/ViaVersion.git
synchronisiert 2024-11-08 17:20:24 +01:00
Small refactors around ProtocolPipeline and concurrent collections
Make concurrency handling in ProtocolPipelineImpl more defensive, as generally the pipeline is expected to never be called from multiple threads. The only case to look out for is pipeline additions during protocol transformation in a base protocol
Dieser Commit ist enthalten in:
Ursprung
343b403cf1
Commit
4e1d4a75b2
@ -16,8 +16,6 @@ sourceSets {
|
||||
|
||||
dependencies {
|
||||
api(libs.fastutil)
|
||||
api(libs.flare)
|
||||
api(libs.flareFastutil)
|
||||
api(libs.vianbt)
|
||||
api(libs.gson)
|
||||
implementation(rootProject.libs.text) {
|
||||
|
@ -66,7 +66,7 @@ public interface ViaAPI<T> {
|
||||
* @return API version incremented with meaningful API changes
|
||||
*/
|
||||
default int apiVersion() {
|
||||
return 24;
|
||||
return 25;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,6 +23,7 @@
|
||||
package com.viaversion.viaversion.api.protocol;
|
||||
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.protocol.packet.Direction;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
@ -61,25 +62,34 @@ public interface ProtocolPipeline extends SimpleProtocol {
|
||||
* @param pipeClass protocol class
|
||||
* @param <P> protocol
|
||||
* @return protocol from class
|
||||
* @see #contains(Class)
|
||||
* @see ProtocolManager#getProtocol(Class) for a faster implementation
|
||||
* @deprecated use {@link ProtocolManager#getProtocol(Class)} and/or {@link #contains(Class)}
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable <P extends Protocol> P getProtocol(Class<P> pipeClass);
|
||||
|
||||
List<Protocol> pipes(@Nullable Class<? extends Protocol> protocolClass, boolean skipCurrentPipeline, Direction direction);
|
||||
|
||||
/**
|
||||
* Returns the list of protocols this pipeline contains.
|
||||
* Returns the list of protocols this pipeline contains, lead by base protocols.
|
||||
*
|
||||
* @return immutable list of protocols in this pipe
|
||||
*/
|
||||
List<Protocol> pipes();
|
||||
|
||||
/**
|
||||
* Returns the list of protocols this pipeline contains in reversed order.
|
||||
* Returns the list of protocols this pipeline contains in reversed order, although still lead by base protocols.
|
||||
*
|
||||
* @return immutable list of protocols in reversed direction
|
||||
*/
|
||||
List<Protocol> reversedPipes();
|
||||
|
||||
/**
|
||||
* Returns the number of base protocols in this pipeline.
|
||||
*
|
||||
* @return the number of base protocols in this pipeline
|
||||
*/
|
||||
int baseProtocolCount();
|
||||
|
||||
/**
|
||||
* Returns whether this pipe has protocols that are not base protocols, as given by {@link Protocol#isBaseProtocol()}.
|
||||
*
|
||||
|
@ -92,6 +92,7 @@ public interface PacketWrapper {
|
||||
* @param index The index of the part (relative to the type)
|
||||
* @return True if the type is at the index
|
||||
*/
|
||||
@Deprecated
|
||||
boolean is(Type type, int index);
|
||||
|
||||
/**
|
||||
@ -212,11 +213,11 @@ public interface PacketWrapper {
|
||||
* (Sends it after current)
|
||||
* Also returns the packets ChannelFuture
|
||||
*
|
||||
* @param packetProtocol The protocol version of the packet.
|
||||
* @return The packets ChannelFuture
|
||||
* @param protocolClass the protocol class to start from in the pipeline
|
||||
* @return new ChannelFuture for the write operation
|
||||
* @throws Exception if it fails to write
|
||||
*/
|
||||
ChannelFuture sendFuture(Class<? extends Protocol> packetProtocol) throws Exception;
|
||||
ChannelFuture sendFuture(Class<? extends Protocol> protocolClass) throws Exception;
|
||||
|
||||
/**
|
||||
* @deprecated misleading; use {@link #sendRaw()}. This method will be removed in 5.0.0
|
||||
@ -287,18 +288,24 @@ public interface PacketWrapper {
|
||||
*
|
||||
* @param direction protocol direction
|
||||
* @param state protocol state
|
||||
* @param index index to start from, will be reversed depending on the reverse parameter
|
||||
* @param pipeline protocol pipeline
|
||||
* @param reverse whether the array should be looped in reverse, will also reverse the given index
|
||||
* @return The current packetwrapper
|
||||
* @throws Exception If it fails to transform a packet, exception will be thrown
|
||||
*/
|
||||
void apply(Direction direction, State state, List<Protocol> pipeline) throws Exception;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #apply(Direction, State, List)}
|
||||
*/
|
||||
@Deprecated
|
||||
PacketWrapper apply(Direction direction, State state, int index, List<Protocol> pipeline, boolean reverse) throws Exception;
|
||||
|
||||
/**
|
||||
* @see #apply(Direction, State, int, List, boolean)
|
||||
* @deprecated use {@link #apply(Direction, State, List)}
|
||||
*/
|
||||
PacketWrapper apply(Direction direction, State state, int index, List<Protocol> pipeline) throws Exception;
|
||||
@Deprecated
|
||||
default PacketWrapper apply(Direction direction, State state, int index, List<Protocol> pipeline) throws Exception {
|
||||
return apply(direction, state, index, pipeline, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this packet is cancelled.
|
||||
|
@ -25,7 +25,6 @@ fun ShadowJar.configureRelocations() {
|
||||
relocate("com.google.gson", "com.viaversion.viaversion.libs.gson")
|
||||
relocate("com.github.steveice10.opennbt", "com.viaversion.viaversion.libs.opennbt")
|
||||
relocate("it.unimi.dsi.fastutil", "com.viaversion.viaversion.libs.fastutil")
|
||||
relocate("space.vectrix.flare", "com.viaversion.viaversion.libs.flare")
|
||||
relocate("net.lenni0451.mcstructs", "com.viaversion.viaversion.libs.mcstructs")
|
||||
}
|
||||
|
||||
@ -49,9 +48,4 @@ fun ShadowJar.configureExcludes() {
|
||||
exclude("it/unimi/dsi/fastutil/*/*Big*")
|
||||
exclude("it/unimi/dsi/fastutil/*/*Synchronized*")
|
||||
exclude("it/unimi/dsi/fastutil/*/*Unmodifiable*")
|
||||
// Flare - only need int maps
|
||||
exclude("space/vectrix/flare/fastutil/*Double*")
|
||||
exclude("space/vectrix/flare/fastutil/*Float*")
|
||||
exclude("space/vectrix/flare/fastutil/*Long*")
|
||||
exclude("space/vectrix/flare/fastutil/*Short*")
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.event.ServerConnectEvent;
|
||||
@ -194,18 +195,15 @@ public class BungeeServerHandler implements Listener {
|
||||
ProtocolPipeline pipeline = user.getProtocolInfo().getPipeline();
|
||||
user.clearStoredObjects(true);
|
||||
pipeline.cleanPipes();
|
||||
if (protocolPath == null) {
|
||||
if (protocolPath != null) {
|
||||
info.setServerProtocolVersion(serverProtocolVersion);
|
||||
pipeline.add(protocolPath.stream().map(ProtocolPathEntry::protocol).collect(Collectors.toList()));
|
||||
} else {
|
||||
// TODO Check Bungee Supported Protocols? *shrugs*
|
||||
serverProtocolVersion = info.protocolVersion();
|
||||
} else {
|
||||
List<Protocol> protocols = new ArrayList<>(protocolPath.size());
|
||||
for (ProtocolPathEntry entry : protocolPath) {
|
||||
protocols.add(entry.protocol());
|
||||
}
|
||||
pipeline.add(protocols);
|
||||
info.setServerProtocolVersion(serverProtocolVersion);
|
||||
}
|
||||
|
||||
info.setServerProtocolVersion(serverProtocolVersion);
|
||||
// Add version-specific base Protocol
|
||||
pipeline.add(Via.getManager().getProtocolManager().getBaseProtocol(serverProtocolVersion));
|
||||
|
||||
@ -254,11 +252,6 @@ public class BungeeServerHandler implements Listener {
|
||||
|
||||
user.setActive(protocolPath != null);
|
||||
|
||||
// Init all protocols TODO check if this can get moved up to the previous for loop, and doesn't require the pipeline to already exist.
|
||||
for (Protocol protocol : pipeline.pipes()) {
|
||||
protocol.init(user);
|
||||
}
|
||||
|
||||
ProxiedPlayer player = storage.getPlayer();
|
||||
EntityTracker1_9 newTracker = user.getEntityTracker(Protocol1_9To1_8.class);
|
||||
if (newTracker != null && Via.getConfig().isAutoTeam()) {
|
||||
|
@ -28,13 +28,13 @@ import com.viaversion.viaversion.api.data.entity.TrackedEntity;
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import space.vectrix.flare.fastutil.Int2ObjectSyncMap;
|
||||
|
||||
public class EntityTrackerBase implements EntityTracker, ClientEntityIdChangeListener {
|
||||
private final Int2ObjectMap<TrackedEntity> entities = Int2ObjectSyncMap.hashmap();
|
||||
private final Int2ObjectMap<TrackedEntity> entities = new Int2ObjectOpenHashMap<>();
|
||||
private final UserConnection connection;
|
||||
private final EntityType playerType;
|
||||
private int clientEntityId = -1;
|
||||
|
@ -18,6 +18,7 @@
|
||||
package com.viaversion.viaversion.protocol;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.connection.ProtocolInfo;
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.debug.DebugHandler;
|
||||
import com.viaversion.viaversion.api.protocol.AbstractSimpleProtocol;
|
||||
@ -31,19 +32,16 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.logging.Level;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class ProtocolPipelineImpl extends AbstractSimpleProtocol implements ProtocolPipeline {
|
||||
private final UserConnection userConnection;
|
||||
/**
|
||||
* Protocol list ordered from client to server transformation with the base protocols at the end.
|
||||
*/
|
||||
private final List<Protocol> protocolList = new CopyOnWriteArrayList<>();
|
||||
private final List<Protocol> protocolList = new ArrayList<>();
|
||||
private final Set<Class<? extends Protocol>> protocolSet = new HashSet<>();
|
||||
private List<Protocol> reversedProtocolList = new CopyOnWriteArrayList<>();
|
||||
private final UserConnection userConnection;
|
||||
private List<Protocol> reversedProtocolList = new ArrayList<>();
|
||||
private int baseProtocols;
|
||||
|
||||
public ProtocolPipelineImpl(UserConnection userConnection) {
|
||||
@ -53,13 +51,9 @@ public class ProtocolPipelineImpl extends AbstractSimpleProtocol implements Prot
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void registerPackets() {
|
||||
protected void registerPackets() {
|
||||
// This is a pipeline so we register basic pipes
|
||||
final Protocol<?, ?, ?, ?> baseProtocol = Via.getManager().getProtocolManager().getBaseProtocol();
|
||||
protocolList.add(baseProtocol);
|
||||
reversedProtocolList.add(baseProtocol);
|
||||
protocolSet.add(baseProtocol.getClass());
|
||||
baseProtocols++;
|
||||
this.add(Via.getManager().getProtocolManager().getBaseProtocol());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -68,15 +62,14 @@ public class ProtocolPipelineImpl extends AbstractSimpleProtocol implements Prot
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void add(final Protocol protocol) {
|
||||
public void add(final Protocol protocol) {
|
||||
reversedProtocolList.add(baseProtocols, protocol);
|
||||
if (protocol.isBaseProtocol()) {
|
||||
// Add base protocol on top of previous ones
|
||||
protocolList.add(baseProtocols, protocol);
|
||||
reversedProtocolList.add(baseProtocols, protocol);
|
||||
baseProtocols++;
|
||||
} else {
|
||||
protocolList.add(protocol);
|
||||
reversedProtocolList.add(0, protocol);
|
||||
}
|
||||
|
||||
protocolSet.add(protocol.getClass());
|
||||
@ -84,22 +77,32 @@ public class ProtocolPipelineImpl extends AbstractSimpleProtocol implements Prot
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void add(final Collection<Protocol> protocols) {
|
||||
protocolList.addAll(protocols);
|
||||
public void add(final Collection<Protocol> protocols) {
|
||||
for (final Protocol protocol : protocols) {
|
||||
if (protocol.isBaseProtocol()) {
|
||||
throw new UnsupportedOperationException("Base protocols cannot be added in bulk");
|
||||
}
|
||||
|
||||
protocol.init(userConnection);
|
||||
protocolSet.add(protocol.getClass());
|
||||
}
|
||||
protocolList.addAll(protocols);
|
||||
|
||||
refreshReversedList();
|
||||
}
|
||||
|
||||
private synchronized void refreshReversedList() {
|
||||
final List<Protocol> protocols = new ArrayList<>(protocolList.subList(0, this.baseProtocols));
|
||||
final List<Protocol> additionalProtocols = new ArrayList<>(protocolList.subList(this.baseProtocols, protocolList.size()));
|
||||
Collections.reverse(additionalProtocols);
|
||||
protocols.addAll(additionalProtocols);
|
||||
reversedProtocolList = new CopyOnWriteArrayList<>(protocols);
|
||||
private void refreshReversedList() {
|
||||
final List<Protocol> reversedProtocols = new ArrayList<>(protocolList.size());
|
||||
// Add base protocols in regular order first
|
||||
for (int i = 0; i < baseProtocols; i++) {
|
||||
reversedProtocols.add(protocolList.get(i));
|
||||
}
|
||||
|
||||
// Add non-base protocols in reverse order
|
||||
for (int i = protocolList.size() - 1; i >= baseProtocols; i--) {
|
||||
reversedProtocols.add(protocolList.get(i));
|
||||
}
|
||||
reversedProtocolList = reversedProtocols;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -113,7 +116,7 @@ public class ProtocolPipelineImpl extends AbstractSimpleProtocol implements Prot
|
||||
}
|
||||
|
||||
// Apply protocols
|
||||
packetWrapper.apply(direction, state, 0, protocolListFor(direction));
|
||||
packetWrapper.apply(direction, state, protocolListFor(direction));
|
||||
super.transform(direction, state, packetWrapper);
|
||||
|
||||
if (debug && debugHandler.logPostPacketTransform() && debugHandler.shouldLog(packetWrapper, direction)) {
|
||||
@ -126,20 +129,21 @@ public class ProtocolPipelineImpl extends AbstractSimpleProtocol implements Prot
|
||||
}
|
||||
|
||||
private void logPacket(Direction direction, State state, PacketWrapper packetWrapper, int originalID) {
|
||||
String actualUsername = packetWrapper.user().getProtocolInfo().getUsername();
|
||||
ProtocolInfo protocolInfo = userConnection.getProtocolInfo();
|
||||
String actualUsername = protocolInfo.getUsername();
|
||||
String username = actualUsername != null ? actualUsername + " " : "";
|
||||
Via.getPlatform().getLogger().log(Level.INFO, "{0}{1} {2}: {3} ({4}) -> {5} ({6}) [{7}] {8}",
|
||||
new Object[]{
|
||||
username,
|
||||
direction,
|
||||
state,
|
||||
originalID,
|
||||
AbstractSimpleProtocol.toNiceHex(originalID),
|
||||
packetWrapper.getId(),
|
||||
AbstractSimpleProtocol.toNiceHex(packetWrapper.getId()),
|
||||
userConnection.getProtocolInfo().protocolVersion().getName(),
|
||||
packetWrapper
|
||||
});
|
||||
new Object[]{
|
||||
username,
|
||||
direction,
|
||||
state,
|
||||
originalID,
|
||||
AbstractSimpleProtocol.toNiceHex(originalID),
|
||||
packetWrapper.getId(),
|
||||
AbstractSimpleProtocol.toNiceHex(packetWrapper.getId()),
|
||||
protocolInfo.protocolVersion().getName(),
|
||||
packetWrapper
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -157,6 +161,49 @@ public class ProtocolPipelineImpl extends AbstractSimpleProtocol implements Prot
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Protocol> pipes(@Nullable final Class<? extends Protocol> protocolClass, final boolean skipCurrentPipeline, final Direction direction) {
|
||||
final List<Protocol> protocolList = this.protocolListFor(direction);
|
||||
final int index = indexOf(protocolClass, skipCurrentPipeline, protocolList);
|
||||
|
||||
final List<Protocol> pipes = new ArrayList<>(baseProtocols + protocolList.size() - index);
|
||||
// Always add base protocols to the head
|
||||
for (int i = 0, size = Math.min(index, baseProtocols); i < size; i++) {
|
||||
pipes.add(protocolList.get(i));
|
||||
}
|
||||
|
||||
// Add remaining protocols on top
|
||||
for (int i = index, size = protocolList.size(); i < size; i++) {
|
||||
pipes.add(protocolList.get(i));
|
||||
}
|
||||
return pipes;
|
||||
}
|
||||
|
||||
private int indexOf(@Nullable Class<? extends Protocol> protocolClass, boolean skipCurrentPipeline, List<Protocol> protocolList) {
|
||||
if (protocolClass == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find the index of the given protocol
|
||||
int index = -1;
|
||||
for (int i = 0; i < protocolList.size(); i++) {
|
||||
if (protocolList.get(i).getClass() == protocolClass) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == -1) {
|
||||
// The given protocol is not in the pipeline
|
||||
throw new NoSuchElementException(protocolClass.getCanonicalName());
|
||||
}
|
||||
|
||||
if (skipCurrentPipeline) {
|
||||
index = Math.min(index + 1, protocolList.size());
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Protocol> pipes() {
|
||||
return Collections.unmodifiableList(protocolList);
|
||||
@ -168,17 +215,17 @@ public class ProtocolPipelineImpl extends AbstractSimpleProtocol implements Prot
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNonBaseProtocols() {
|
||||
for (Protocol protocol : protocolList) {
|
||||
if (!protocol.isBaseProtocol()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public int baseProtocolCount() {
|
||||
return baseProtocols;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void cleanPipes() {
|
||||
public boolean hasNonBaseProtocols() {
|
||||
return protocolList.size() > baseProtocols;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanPipes() {
|
||||
protocolList.clear();
|
||||
reversedProtocolList.clear();
|
||||
protocolSet.clear();
|
||||
@ -190,7 +237,7 @@ public class ProtocolPipelineImpl extends AbstractSimpleProtocol implements Prot
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProtocolPipelineImpl{" +
|
||||
"protocolList=" + protocolList +
|
||||
'}';
|
||||
"protocolList=" + protocolList +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -39,13 +39,10 @@ import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class PacketWrapperImpl implements PacketWrapper {
|
||||
private static final Protocol[] PROTOCOL_ARRAY = new Protocol[0];
|
||||
|
||||
private final Deque<PacketValue<?>> readableObjects = new ArrayDeque<>();
|
||||
private final List<PacketValue<?>> packetValues = new ArrayList<>();
|
||||
private final ByteBuf inputBuffer;
|
||||
@ -148,8 +145,8 @@ public class PacketWrapperImpl implements PacketWrapper {
|
||||
PacketValue readValue = readableObjects.poll();
|
||||
Type<?> readType = readValue.type();
|
||||
if (readType == type
|
||||
|| (type.getBaseClass() == readType.getBaseClass()
|
||||
&& type.getOutputClass() == readType.getOutputClass())) {
|
||||
|| (type.getBaseClass() == readType.getBaseClass()
|
||||
&& type.getOutputClass() == readType.getOutputClass())) {
|
||||
//noinspection unchecked
|
||||
return (T) readValue.value();
|
||||
} else {
|
||||
@ -210,25 +207,24 @@ public class PacketWrapperImpl implements PacketWrapper {
|
||||
readableObjects.clear();
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
for (final PacketValue<?> packetValue : packetValues) {
|
||||
for (int i = 0; i < packetValues.size(); i++) {
|
||||
PacketValue<?> packetValue = packetValues.get(i);
|
||||
try {
|
||||
packetValue.write(buffer);
|
||||
} catch (final Exception e) {
|
||||
throw createInformativeException(e, packetValue.type(), index);
|
||||
throw createInformativeException(e, packetValue.type(), i);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
writeRemaining(buffer);
|
||||
}
|
||||
|
||||
private InformativeException createInformativeException(final Exception cause, final Type<?> type, final int index) {
|
||||
return new InformativeException(cause)
|
||||
.set("Index", index)
|
||||
.set("Type", type.getTypeName())
|
||||
.set("Packet ID", this.id)
|
||||
.set("Packet Type", this.packetType)
|
||||
.set("Data", this.packetValues);
|
||||
.set("Index", index)
|
||||
.set("Type", type.getTypeName())
|
||||
.set("Packet ID", this.id)
|
||||
.set("Packet Type", this.packetType)
|
||||
.set("Data", this.packetValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -298,46 +294,17 @@ public class PacketWrapperImpl implements PacketWrapper {
|
||||
/**
|
||||
* Let the packet go through the protocol pipes and write it to ByteBuf
|
||||
*
|
||||
* @param packetProtocol The protocol version of the packet.
|
||||
* @param skipCurrentPipeline Skip the current pipeline
|
||||
* @return Packet buffer
|
||||
* @param protocolClass protocol class to send the packet from, or null to go through the full pipeline
|
||||
* @param skipCurrentPipeline whether to start from the next protocol in the pipeline, or the provided one
|
||||
* @return created packet buffer
|
||||
* @throws Exception if it fails to write
|
||||
*/
|
||||
private ByteBuf constructPacket(Class<? extends Protocol> packetProtocol, boolean skipCurrentPipeline, Direction direction) throws Exception {
|
||||
private ByteBuf constructPacket(@Nullable Class<? extends Protocol> protocolClass, boolean skipCurrentPipeline, Direction direction) throws Exception {
|
||||
resetReader(); // Reset reader before we start
|
||||
|
||||
final ProtocolInfo protocolInfo = user().getProtocolInfo();
|
||||
final List<Protocol> pipes = direction == Direction.SERVERBOUND ? protocolInfo.getPipeline().pipes() : protocolInfo.getPipeline().reversedPipes();
|
||||
final List<Protocol> protocols = new ArrayList<>();
|
||||
int index = -1;
|
||||
for (int i = 0; i < pipes.size(); i++) {
|
||||
// Always add base protocols to the head
|
||||
final Protocol protocol = pipes.get(i);
|
||||
if (protocol.isBaseProtocol()) {
|
||||
protocols.add(protocol);
|
||||
}
|
||||
|
||||
if (protocol.getClass() == packetProtocol) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == -1) {
|
||||
// The given protocol is not in the pipeline
|
||||
throw new NoSuchElementException(packetProtocol.getCanonicalName());
|
||||
}
|
||||
|
||||
if (skipCurrentPipeline) {
|
||||
index = Math.min(index + 1, pipes.size());
|
||||
}
|
||||
|
||||
// Add remaining protocols on top
|
||||
protocols.addAll(pipes.subList(index, pipes.size()));
|
||||
|
||||
// Reset reader before we start
|
||||
resetReader();
|
||||
|
||||
// Apply other protocols
|
||||
apply(direction, protocolInfo.getState(direction), 0, protocols);
|
||||
final List<Protocol> protocols = protocolInfo.getPipeline().pipes(protocolClass, skipCurrentPipeline, direction);
|
||||
apply(direction, protocolInfo.getState(direction), protocols);
|
||||
final ByteBuf output = inputBuffer == null ? user().getChannel().alloc().buffer() : inputBuffer.alloc().buffer();
|
||||
try {
|
||||
writeToBuffer(output);
|
||||
@ -348,9 +315,9 @@ public class PacketWrapperImpl implements PacketWrapper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture sendFuture(Class<? extends Protocol> packetProtocol) throws Exception {
|
||||
public ChannelFuture sendFuture(Class<? extends Protocol> protocolClass) throws Exception {
|
||||
if (!isCancelled()) {
|
||||
ByteBuf output = constructPacket(packetProtocol, true, Direction.CLIENTBOUND);
|
||||
ByteBuf output = constructPacket(protocolClass, true, Direction.CLIENTBOUND);
|
||||
return user().sendRawPacketFuture(output);
|
||||
}
|
||||
return user().getChannel().newFailedFuture(new Exception("Cancelled packet"));
|
||||
@ -397,33 +364,36 @@ public class PacketWrapperImpl implements PacketWrapper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketWrapperImpl apply(Direction direction, State state, int index, List<Protocol> pipeline, boolean reverse) throws Exception {
|
||||
Protocol[] array = pipeline.toArray(PROTOCOL_ARRAY);
|
||||
return apply(direction, state, reverse ? array.length - 1 : index, array, reverse); // Copy to prevent from removal
|
||||
public void apply(Direction direction, State state, List<Protocol> pipeline) throws Exception {
|
||||
// Indexed loop to allow additions to the tail
|
||||
for (int i = 0, size = pipeline.size(); i < size; i++) {
|
||||
Protocol<?, ?, ?, ?> protocol = pipeline.get(i);
|
||||
protocol.transform(direction, state, this);
|
||||
resetReader();
|
||||
if (this.packetType != null) {
|
||||
state = this.packetType.state();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketWrapperImpl apply(Direction direction, State state, int index, List<Protocol> pipeline) throws Exception {
|
||||
return apply(direction, state, index, pipeline.toArray(PROTOCOL_ARRAY), false);
|
||||
}
|
||||
|
||||
private PacketWrapperImpl apply(Direction direction, State state, int index, Protocol[] pipeline, boolean reverse) throws Exception {
|
||||
@Deprecated
|
||||
public PacketWrapperImpl apply(Direction direction, State state, int index, List<Protocol> pipeline, boolean reverse) throws Exception {
|
||||
// Reset the reader after every transformation for the packetWrapper, so it can be recycled across packets
|
||||
State updatedState = state; // The state might change while transforming, so we need to check for that
|
||||
if (reverse) {
|
||||
for (int i = index; i >= 0; i--) {
|
||||
pipeline[i].transform(direction, updatedState, this);
|
||||
pipeline.get(i).transform(direction, state, this);
|
||||
resetReader();
|
||||
if (this.packetType != null) {
|
||||
updatedState = this.packetType.state();
|
||||
state = this.packetType.state();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = index; i < pipeline.length; i++) {
|
||||
pipeline[i].transform(direction, updatedState, this);
|
||||
for (int i = index; i < pipeline.size(); i++) {
|
||||
pipeline.get(i).transform(direction, state, this);
|
||||
resetReader();
|
||||
if (this.packetType != null) {
|
||||
updatedState = this.packetType.state();
|
||||
state = this.packetType.state();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -447,7 +417,7 @@ public class PacketWrapperImpl implements PacketWrapper {
|
||||
|
||||
@Override
|
||||
public void resetReader() {
|
||||
// Move all packet values to the readable for next packet.
|
||||
// Move all packet values to the readable for next Protocol
|
||||
for (int i = packetValues.size() - 1; i >= 0; i--) {
|
||||
this.readableObjects.addFirst(this.packetValues.get(i));
|
||||
}
|
||||
@ -557,11 +527,11 @@ public class PacketWrapperImpl implements PacketWrapper {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PacketWrapper{" +
|
||||
"type=" + packetType +
|
||||
", id=" + id +
|
||||
", values=" + packetValues +
|
||||
", readable=" + readableObjects +
|
||||
'}';
|
||||
"type=" + packetType +
|
||||
", id=" + id +
|
||||
", values=" + packetValues +
|
||||
", readable=" + readableObjects +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static final class PacketValue<T> {
|
||||
|
@ -33,6 +33,7 @@ import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class VersionedPacketTransformerImpl<C extends ClientboundPacketType, S extends ServerboundPacketType> implements VersionedPacketTransformer<C, S> {
|
||||
@ -44,7 +45,7 @@ public class VersionedPacketTransformerImpl<C extends ClientboundPacketType, S e
|
||||
public VersionedPacketTransformerImpl(ProtocolVersion inputVersion, @Nullable Class<C> clientboundPacketsClass, @Nullable Class<S> serverboundPacketsClass) {
|
||||
Preconditions.checkNotNull(inputVersion);
|
||||
Preconditions.checkArgument(clientboundPacketsClass != null || serverboundPacketsClass != null,
|
||||
"Either the clientbound or serverbound packets class has to be non-null");
|
||||
"Either the clientbound or serverbound packets class has to be non-null");
|
||||
this.inputProtocolVersion = inputVersion;
|
||||
this.clientboundPacketsClass = clientboundPacketsClass;
|
||||
this.serverboundPacketsClass = serverboundPacketsClass;
|
||||
@ -108,7 +109,7 @@ public class VersionedPacketTransformerImpl<C extends ClientboundPacketType, S e
|
||||
}
|
||||
|
||||
Class<? extends PacketType> expectedPacketClass =
|
||||
packet.getPacketType().direction() == Direction.CLIENTBOUND ? clientboundPacketsClass : serverboundPacketsClass;
|
||||
packet.getPacketType().direction() == Direction.CLIENTBOUND ? clientboundPacketsClass : serverboundPacketsClass;
|
||||
if (packet.getPacketType().getClass() != expectedPacketClass) {
|
||||
throw new IllegalArgumentException("PacketWrapper packet type is of the wrong packet class");
|
||||
}
|
||||
@ -139,34 +140,40 @@ public class VersionedPacketTransformerImpl<C extends ClientboundPacketType, S e
|
||||
private void transformPacket(PacketWrapper packet) throws Exception {
|
||||
// If clientbound: Constructor given inputProtocolVersion → Client version
|
||||
// If serverbound: Constructor given inputProtocolVersion → Server version
|
||||
PacketType packetType = packet.getPacketType();
|
||||
UserConnection connection = packet.user();
|
||||
PacketType packetType = packet.getPacketType();
|
||||
boolean clientbound = packetType.direction() == Direction.CLIENTBOUND;
|
||||
ProtocolVersion serverProtocolVersion = clientbound ? this.inputProtocolVersion : connection.getProtocolInfo().serverProtocolVersion();
|
||||
ProtocolVersion clientProtocolVersion = clientbound ? connection.getProtocolInfo().protocolVersion() : this.inputProtocolVersion;
|
||||
|
||||
// Construct protocol pipeline
|
||||
List<ProtocolPathEntry> path = Via.getManager().getProtocolManager().getProtocolPath(clientProtocolVersion, serverProtocolVersion);
|
||||
List<Protocol> protocolList = null;
|
||||
if (path != null) {
|
||||
protocolList = new ArrayList<>(path.size());
|
||||
if (path == null) {
|
||||
if (serverProtocolVersion != clientProtocolVersion) {
|
||||
throw new RuntimeException("No protocol path between client version " + clientProtocolVersion + " and server version " + serverProtocolVersion);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Protocol> protocolList = new ArrayList<>(path.size());
|
||||
if (clientbound) {
|
||||
for (int i = path.size() - 1; i >= 0; i--) {
|
||||
protocolList.add(path.get(i).protocol());
|
||||
}
|
||||
} else {
|
||||
for (ProtocolPathEntry entry : path) {
|
||||
protocolList.add(entry.protocol());
|
||||
}
|
||||
} else if (serverProtocolVersion != clientProtocolVersion) {
|
||||
throw new RuntimeException("No protocol path between client version " + clientProtocolVersion + " and server version " + serverProtocolVersion);
|
||||
}
|
||||
|
||||
if (protocolList != null) {
|
||||
// Reset reader and apply pipeline
|
||||
packet.resetReader();
|
||||
// Reset reader and apply pipeline
|
||||
packet.resetReader();
|
||||
|
||||
try {
|
||||
packet.apply(packetType.direction(), State.PLAY, 0, protocolList, clientbound);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Exception trying to transform packet between client version " + clientProtocolVersion
|
||||
+ " and server version " + serverProtocolVersion + ". Are you sure you used the correct input version and packet write types?", e);
|
||||
}
|
||||
try {
|
||||
packet.apply(packetType.direction(), packetType.state(), protocolList);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Exception trying to transform packet between client version " + clientProtocolVersion
|
||||
+ " and server version " + serverProtocolVersion + ". Are you sure you used the correct input version and packet write types?", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ import com.viaversion.viaversion.api.connection.ProtocolInfo;
|
||||
import com.viaversion.viaversion.api.platform.providers.ViaProviders;
|
||||
import com.viaversion.viaversion.api.protocol.AbstractProtocol;
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.ProtocolManager;
|
||||
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
|
||||
import com.viaversion.viaversion.api.protocol.ProtocolPipeline;
|
||||
import com.viaversion.viaversion.api.protocol.packet.Direction;
|
||||
@ -72,18 +73,25 @@ public class BaseProtocol extends AbstractProtocol<BaseClientboundPacket, BaseCl
|
||||
List<ProtocolPathEntry> protocolPath = null;
|
||||
|
||||
// Only allow newer clients (or 1.9.2 on 1.9.4 server if the server supports it)
|
||||
ProtocolManager protocolManager = Via.getManager().getProtocolManager();
|
||||
if (info.protocolVersion().newerThanOrEqualTo(serverProtocol) || Via.getPlatform().isOldClientsAllowed()) {
|
||||
protocolPath = Via.getManager().getProtocolManager().getProtocolPath(info.protocolVersion(), serverProtocol);
|
||||
protocolPath = protocolManager.getProtocolPath(info.protocolVersion(), serverProtocol);
|
||||
}
|
||||
|
||||
ProtocolPipeline pipeline = wrapper.user().getProtocolInfo().getPipeline();
|
||||
// Add Base Protocol
|
||||
ProtocolPipeline pipeline = info.getPipeline();
|
||||
if (serverProtocol.getVersionType() != VersionType.SPECIAL) {
|
||||
pipeline.add(protocolManager.getBaseProtocol(serverProtocol));
|
||||
}
|
||||
|
||||
// Add other protocols
|
||||
if (protocolPath != null) {
|
||||
List<Protocol> protocols = new ArrayList<>(protocolPath.size());
|
||||
for (ProtocolPathEntry entry : protocolPath) {
|
||||
protocols.add(entry.protocol());
|
||||
|
||||
// Ensure mapping data has already been loaded
|
||||
Via.getManager().getProtocolManager().completeMappingDataLoading(entry.protocol().getClass());
|
||||
protocolManager.completeMappingDataLoading(entry.protocol().getClass());
|
||||
}
|
||||
|
||||
// Add protocols to pipeline
|
||||
@ -93,11 +101,6 @@ public class BaseProtocol extends AbstractProtocol<BaseClientboundPacket, BaseCl
|
||||
wrapper.set(Type.VAR_INT, 0, serverProtocol.getOriginalVersion());
|
||||
}
|
||||
|
||||
// Add Base Protocol
|
||||
if (!serverProtocol.getVersionType().equals(VersionType.SPECIAL)) {
|
||||
pipeline.add(Via.getManager().getProtocolManager().getBaseProtocol(serverProtocol));
|
||||
}
|
||||
|
||||
if (Via.getManager().isDebug()) {
|
||||
Via.getPlatform().getLogger().info("User connected with protocol: " + info.protocolVersion() + " and serverProtocol: " + info.serverProtocolVersion());
|
||||
Via.getPlatform().getLogger().info("Protocol pipeline: " + pipeline.pipes());
|
||||
|
@ -168,7 +168,7 @@ public class BaseProtocol1_7 extends AbstractProtocol<BaseClientboundPacket, Bas
|
||||
wrapper.cancel(); // cancel current
|
||||
|
||||
// Send and close
|
||||
ChannelFuture future = disconnectPacket.sendFuture(BaseProtocol.class);
|
||||
ChannelFuture future = disconnectPacket.sendFuture(null);
|
||||
future.addListener(f -> wrapper.user().getChannel().close());
|
||||
}
|
||||
});
|
||||
|
@ -20,11 +20,11 @@ package com.viaversion.viaversion.protocols.protocol1_11to1_10.storage;
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_11.EntityType;
|
||||
import com.viaversion.viaversion.data.entity.EntityTrackerBase;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import space.vectrix.flare.fastutil.Int2ObjectSyncMap;
|
||||
|
||||
public class EntityTracker1_11 extends EntityTrackerBase {
|
||||
private final IntSet holograms = Int2ObjectSyncMap.hashset();
|
||||
private final IntSet holograms = new IntOpenHashSet();
|
||||
|
||||
public EntityTracker1_11(UserConnection user) {
|
||||
super(user, EntityType.PLAYER);
|
||||
|
@ -21,13 +21,13 @@ import com.viaversion.viaversion.api.connection.StorableObject;
|
||||
import com.viaversion.viaversion.api.minecraft.Position;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import space.vectrix.flare.SyncMap;
|
||||
|
||||
// TODO Fix memory leak lolz (only a smol one tho)
|
||||
public class BlockStorage implements StorableObject {
|
||||
private static final IntSet WHITELIST = new IntOpenHashSet(46, .99F);
|
||||
private final Map<Position, ReplacementData> blocks = SyncMap.hashmap();
|
||||
private final Map<Position, ReplacementData> blocks = new HashMap<>();
|
||||
|
||||
static {
|
||||
// Flower pots
|
||||
|
@ -21,13 +21,13 @@ import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_14;
|
||||
import com.viaversion.viaversion.data.entity.EntityTrackerBase;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import space.vectrix.flare.fastutil.Int2ObjectSyncMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
public class EntityTracker1_14 extends EntityTrackerBase {
|
||||
private final Int2ObjectMap<Byte> insentientData = Int2ObjectSyncMap.hashmap();
|
||||
private final Int2ObjectMap<Byte> insentientData = new Int2ObjectOpenHashMap<>();
|
||||
// 0x1 = sleeping, 0x2 = riptide
|
||||
private final Int2ObjectMap<Byte> sleepingAndRiptideData = Int2ObjectSyncMap.hashmap();
|
||||
private final Int2ObjectMap<Byte> playerEntityFlags = Int2ObjectSyncMap.hashmap();
|
||||
private final Int2ObjectMap<Byte> sleepingAndRiptideData = new Int2ObjectOpenHashMap<>();
|
||||
private final Int2ObjectMap<Byte> playerEntityFlags = new Int2ObjectOpenHashMap<>();
|
||||
private int latestTradeWindowId;
|
||||
private boolean forceSendCenterChunk = true;
|
||||
private int chunkCenterX;
|
||||
|
@ -39,26 +39,28 @@ import com.viaversion.viaversion.protocols.protocol1_9to1_8.chat.GameMode;
|
||||
import com.viaversion.viaversion.protocols.protocol1_9to1_8.metadata.MetadataRewriter1_9To1_8;
|
||||
import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.BossBarProvider;
|
||||
import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.EntityIdProvider;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import space.vectrix.flare.fastutil.Int2ObjectSyncMap;
|
||||
|
||||
public class EntityTracker1_9 extends EntityTrackerBase {
|
||||
public static final String WITHER_TRANSLATABLE = "{\"translate\":\"entity.WitherBoss.name\"}";
|
||||
public static final String DRAGON_TRANSLATABLE = "{\"translate\":\"entity.EnderDragon.name\"}";
|
||||
private final Int2ObjectMap<UUID> uuidMap = Int2ObjectSyncMap.hashmap();
|
||||
private final Int2ObjectMap<List<Metadata>> metadataBuffer = Int2ObjectSyncMap.hashmap();
|
||||
private final Int2ObjectMap<Integer> vehicleMap = Int2ObjectSyncMap.hashmap();
|
||||
private final Int2ObjectMap<BossBar> bossBarMap = Int2ObjectSyncMap.hashmap();
|
||||
private final IntSet validBlocking = Int2ObjectSyncMap.hashset();
|
||||
private final Set<Integer> knownHolograms = Int2ObjectSyncMap.hashset();
|
||||
private final Int2ObjectMap<UUID> uuidMap = new Int2ObjectOpenHashMap<>();
|
||||
private final Int2ObjectMap<List<Metadata>> metadataBuffer = new Int2ObjectOpenHashMap<>();
|
||||
private final Int2IntMap vehicleMap = new Int2IntOpenHashMap();
|
||||
private final Int2ObjectMap<BossBar> bossBarMap = new Int2ObjectOpenHashMap<>();
|
||||
private final IntSet validBlocking = new IntOpenHashSet();
|
||||
private final IntSet knownHolograms = new IntOpenHashSet();
|
||||
private final Set<Position> blockInteractions = Collections.newSetFromMap(CacheBuilder.newBuilder()
|
||||
.maximumSize(1000)
|
||||
.expireAfterAccess(250, TimeUnit.MILLISECONDS)
|
||||
@ -368,27 +370,27 @@ public class EntityTracker1_9 extends EntityTrackerBase {
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Integer, UUID> getUuidMap() {
|
||||
public Int2ObjectMap<UUID> getUuidMap() {
|
||||
return uuidMap;
|
||||
}
|
||||
|
||||
public Map<Integer, List<Metadata>> getMetadataBuffer() {
|
||||
public Int2ObjectMap<List<Metadata>> getMetadataBuffer() {
|
||||
return metadataBuffer;
|
||||
}
|
||||
|
||||
public Map<Integer, Integer> getVehicleMap() {
|
||||
public Int2IntMap getVehicleMap() {
|
||||
return vehicleMap;
|
||||
}
|
||||
|
||||
public Map<Integer, BossBar> getBossBarMap() {
|
||||
public Int2ObjectMap<BossBar> getBossBarMap() {
|
||||
return bossBarMap;
|
||||
}
|
||||
|
||||
public Set<Integer> getValidBlocking() {
|
||||
public IntSet getValidBlocking() {
|
||||
return validBlocking;
|
||||
}
|
||||
|
||||
public Set<Integer> getKnownHolograms() {
|
||||
public IntSet getKnownHolograms() {
|
||||
return knownHolograms;
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ metadata.format.version = "1.1"
|
||||
|
||||
gson = "2.10.1"
|
||||
fastutil = "8.5.12"
|
||||
flare = "2.0.1"
|
||||
vianbt = "4.2.0"
|
||||
mcstructs = "2.4.2-SNAPSHOT"
|
||||
|
||||
@ -29,8 +28,6 @@ velocity = "3.1.1"
|
||||
|
||||
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
||||
fastutil = { group = "it.unimi.dsi", name = "fastutil", version.ref = "fastutil" }
|
||||
flare = { group = "space.vectrix.flare", name = "flare", version.ref = "flare" }
|
||||
flareFastutil = { group = "space.vectrix.flare", name = "flare-fastutil", version.ref = "flare" }
|
||||
vianbt = { group = "com.viaversion", name = "nbt", version.ref = "vianbt" }
|
||||
# Custom version that uses ViaNBT instead of its own inbuilt NBT library
|
||||
text = { group = "com.viaversion.mcstructs", name = "text", version.ref = "mcstructs" }
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren