mirror of
https://github.com/ViaVersion/ViaVersion.git
synced 2026-11-05 01:10:12 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9a3a4493d | ||
|
|
0fe1b6d6db | ||
|
|
fbb92c0fac | ||
|
|
384cf91125 | ||
|
|
c6736b3532 | ||
|
|
8c4bbd1a2f | ||
|
|
386c607917 | ||
|
|
c30c787c63 | ||
|
|
6951dda36e | ||
|
|
c9d8610c05 | ||
|
|
68ad7c115b | ||
|
|
3c50cdf639 | ||
|
|
e211692ca1 | ||
|
|
8937bdd986 | ||
|
|
b49f995ef1 | ||
|
|
7ab825cc77 | ||
|
|
a00226f452 | ||
|
|
9370534b23 | ||
|
|
5fe54eb1e5 | ||
|
|
a090a909ee | ||
|
|
72084b2599 | ||
|
|
a3dcaa6f56 | ||
|
|
88f9c05be0 | ||
|
|
7b167b0eed | ||
|
|
21ff1758d5 | ||
|
|
9793ca2291 | ||
|
|
5768a5c0da | ||
|
|
decab8fe7f | ||
|
|
354a736e0c | ||
|
|
56d23661b0 | ||
|
|
ca616c74fe | ||
|
|
168d13e143 | ||
|
|
229d4fc6e1 | ||
|
|
33e426fddc | ||
|
|
0c6023e489 | ||
|
|
8ebe3a523c | ||
|
|
8239577c23 | ||
|
|
670fde5d0f | ||
|
|
d2a715862a | ||
|
|
6438f99600 | ||
|
|
1e31aafd83 |
No files matched your search
@@ -11,7 +11,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Gradle
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
cache-disabled: false
|
||||
cache-provider: basic
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@v5.5.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Gradle
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
cache-disabled: false
|
||||
cache-provider: basic
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@v5.5.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
|
||||
@@ -66,7 +66,7 @@ public interface ViaAPI<T> {
|
||||
* @return API version incremented with meaningful API changes
|
||||
*/
|
||||
default int apiVersion() {
|
||||
return 26;
|
||||
return 27;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -261,6 +261,8 @@ public interface ViaVersionConfig extends Config {
|
||||
*/
|
||||
boolean isChunkBorderFix();
|
||||
|
||||
boolean isMultiReddustColorFix();
|
||||
|
||||
/**
|
||||
* Should we make team colours based on the last colour in team prefix
|
||||
*
|
||||
|
||||
@@ -365,10 +365,10 @@ public interface UserConnection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a newly generated uuid that will let a packet be passed through without
|
||||
* Returns a newly generated token that will let a packet be passed through without
|
||||
* transforming its contents if used together with {@link PacketWrapper#PASSTHROUGH_ID}.
|
||||
*
|
||||
* @return generated passthrough token
|
||||
*/
|
||||
UUID generatePassthroughToken();
|
||||
long generatePassthroughToken();
|
||||
}
|
||||
@@ -22,88 +22,29 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.api.data;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.viaversion.viaversion.api.data.MappingDataLoader.IdentifiersPair;
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class FullIdentityMappings implements FullMappings {
|
||||
private static final String[] EMPTY_ARRAY = new String[0];
|
||||
private final Object2IntMap<String> stringToId;
|
||||
private final String[] idToString;
|
||||
private final Mappings mappings;
|
||||
public class FullIdentityMappings extends FullMappingsBase {
|
||||
|
||||
public FullIdentityMappings(final IdentifiersPair identifiersPair, final Mappings mappings) {
|
||||
Preconditions.checkNotNull(mappings, "Mappings cannot be null");
|
||||
this.mappings = mappings;
|
||||
this.stringToId = FullMappingsBase.toInverseMap(identifiersPair.unmapped());
|
||||
this.idToString = identifiersPair.unmapped().toArray(EMPTY_ARRAY);
|
||||
this(toInverseMap(identifiersPair.unmapped()), identifiersPair.unmapped().stream().map(Key::of).toArray(Key[]::new), mappings);
|
||||
}
|
||||
|
||||
private FullIdentityMappings(final Object2IntMap<String> stringToId, final String[] idToString, final Mappings mappings) {
|
||||
this.stringToId = stringToId;
|
||||
this.idToString = idToString;
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id(final String identifier) {
|
||||
return stringToId.getInt(Key.stripMinecraftNamespace(identifier));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int mappedId(final String mappedIdentifier) {
|
||||
return id(mappedIdentifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String identifier(final int id) {
|
||||
if (id < 0 || id >= idToString.length) {
|
||||
return null;
|
||||
}
|
||||
final String identifier = idToString[id];
|
||||
return Key.namespaced(identifier);
|
||||
private FullIdentityMappings(final Object2IntMap<String> stringToId, final Key[] idToKey, final Mappings mappings) {
|
||||
super(stringToId, stringToId, idToKey, idToKey, mappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String identifier(final String mappedIdentifier) {
|
||||
final int mappedId = mappedId(mappedIdentifier);
|
||||
if (mappedId == -1) {
|
||||
return null;
|
||||
}
|
||||
final int id = mappings.inverse().getNewId(mappedId);
|
||||
return id != -1 ? identifier(id) : null;
|
||||
return mappedIdentifier(mappedIdentifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String mappedIdentifier(final int mappedId) {
|
||||
return identifier(mappedId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String mappedIdentifier(final String identifier) {
|
||||
return identifier(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewId(int id) {
|
||||
return mappings.getNewId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNewId(int id, int mappedId) {
|
||||
mappings.setNewId(id, mappedId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return mappings.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int mappedSize() {
|
||||
return mappings.mappedSize();
|
||||
public @Nullable Key keyFromMappedKey(final String mappedIdentifier) {
|
||||
return mappedKeyFromKey(mappedIdentifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,7 +58,7 @@ public class FullIdentityMappings implements FullMappings {
|
||||
}
|
||||
|
||||
@Override
|
||||
public FullMappings inverse() {
|
||||
return new FullIdentityMappings(stringToId, idToString, mappings.inverse());
|
||||
public FullIdentityMappings inverse() {
|
||||
return new FullIdentityMappings(stringToId, idToKey, mappings.inverse());
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.api.data;
|
||||
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
@@ -51,7 +52,10 @@ public interface FullMappings extends BiMappings {
|
||||
* @param id unmapped id
|
||||
* @return unmapped string identifier, or null if out of bounds
|
||||
*/
|
||||
@Nullable String identifier(int id);
|
||||
default @Nullable String identifier(int id) {
|
||||
final Key key = keyFromId(id);
|
||||
return key != null ? key.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unmapped string identifier for the given mapped identifier.
|
||||
@@ -59,7 +63,10 @@ public interface FullMappings extends BiMappings {
|
||||
* @param mappedIdentifier mapped identifier
|
||||
* @return unmapped string identifier, or null if not found
|
||||
*/
|
||||
@Nullable String identifier(String mappedIdentifier);
|
||||
default @Nullable String identifier(String mappedIdentifier) {
|
||||
final Key key = keyFromMappedKey(mappedIdentifier);
|
||||
return key != null ? key.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mapped string identifier for the given mapped id.
|
||||
@@ -67,7 +74,10 @@ public interface FullMappings extends BiMappings {
|
||||
* @param mappedId mapped id
|
||||
* @return mapped string identifier, or null if out of bounds
|
||||
*/
|
||||
@Nullable String mappedIdentifier(int mappedId);
|
||||
default @Nullable String mappedIdentifier(int mappedId) {
|
||||
final Key key = mappedKeyFromMappedId(mappedId);
|
||||
return key != null ? key.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mapped string identifier for the given unmapped identifier.
|
||||
@@ -75,7 +85,42 @@ public interface FullMappings extends BiMappings {
|
||||
* @param identifier unmapped identifier
|
||||
* @return mapped string identifier, or null if not found
|
||||
*/
|
||||
@Nullable String mappedIdentifier(String identifier);
|
||||
default @Nullable String mappedIdentifier(String identifier) {
|
||||
final Key key = mappedKeyFromKey(identifier);
|
||||
return key != null ? key.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unmapped key for the given mapped id.
|
||||
*
|
||||
* @param id unmapped id
|
||||
* @return unmapped key, or null if out of bounds
|
||||
*/
|
||||
@Nullable Key keyFromId(int id);
|
||||
|
||||
/**
|
||||
* Returns the unmapped key for the given mapped identifier.
|
||||
*
|
||||
* @param mappedIdentifier mapped identifier
|
||||
* @return unmapped key, or null if not found
|
||||
*/
|
||||
@Nullable Key keyFromMappedKey(String mappedIdentifier);
|
||||
|
||||
/**
|
||||
* Returns the mapped key for the given mapped id.
|
||||
*
|
||||
* @param mappedId mapped id
|
||||
* @return mapped key, or null if out of bounds
|
||||
*/
|
||||
@Nullable Key mappedKeyFromMappedId(int mappedId);
|
||||
|
||||
/**
|
||||
* Returns the mapped key for the given unmapped identifier.
|
||||
*
|
||||
* @param identifier unmapped identifier
|
||||
* @return mapped key, or null if not found
|
||||
*/
|
||||
@Nullable Key mappedKeyFromKey(String identifier);
|
||||
|
||||
/**
|
||||
* Returns whether the int id mappings are identity mappings.
|
||||
|
||||
@@ -31,27 +31,26 @@ import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class FullMappingsBase implements FullMappings {
|
||||
private static final String[] EMPTY_ARRAY = new String[0];
|
||||
private final Object2IntMap<String> stringToId;
|
||||
private final Object2IntMap<String> mappedStringToId;
|
||||
private final String[] idToString;
|
||||
private final String[] mappedIdToString;
|
||||
private final Mappings mappings;
|
||||
protected final Object2IntMap<String> stringToId;
|
||||
protected final Object2IntMap<String> mappedStringToId;
|
||||
protected final Key[] idToKey;
|
||||
protected final Key[] mappedIdToKey;
|
||||
protected final Mappings mappings;
|
||||
|
||||
public FullMappingsBase(final IdentifiersPair identifiersPair, final Mappings mappings) {
|
||||
Preconditions.checkNotNull(mappings, "Mappings cannot be null");
|
||||
this.mappings = mappings;
|
||||
this.stringToId = toInverseMap(identifiersPair.unmapped());
|
||||
this.idToString = identifiersPair.unmapped().toArray(EMPTY_ARRAY);
|
||||
this.idToKey = identifiersPair.unmapped().stream().map(Key::of).toArray(Key[]::new);
|
||||
this.mappedStringToId = toInverseMap(identifiersPair.mapped());
|
||||
this.mappedIdToString = identifiersPair.mapped().toArray(EMPTY_ARRAY);
|
||||
this.mappedIdToKey = identifiersPair.mapped().stream().map(Key::of).toArray(Key[]::new);
|
||||
}
|
||||
|
||||
private FullMappingsBase(final Object2IntMap<String> stringToId, final Object2IntMap<String> mappedStringToId, final String[] idToString, final String[] mappedIdToString, final Mappings mappings) {
|
||||
protected FullMappingsBase(final Object2IntMap<String> stringToId, final Object2IntMap<String> mappedStringToId, final Key[] idToKey, final Key[] mappedIdToKey, final Mappings mappings) {
|
||||
this.stringToId = stringToId;
|
||||
this.mappedStringToId = mappedStringToId;
|
||||
this.idToString = idToString;
|
||||
this.mappedIdToString = mappedIdToString;
|
||||
this.idToKey = idToKey;
|
||||
this.mappedIdToKey = mappedIdToKey;
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
||||
@@ -79,45 +78,43 @@ public class FullMappingsBase implements FullMappings {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String identifier(final int id) {
|
||||
if (id < 0 || id >= idToString.length) {
|
||||
public @Nullable Key keyFromId(final int id) {
|
||||
if (id < 0 || id >= idToKey.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String identifier = idToString[id];
|
||||
return Key.namespaced(identifier);
|
||||
return idToKey[id];
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String identifier(final String mappedIdentifier) {
|
||||
public @Nullable Key keyFromMappedKey(final String mappedIdentifier) {
|
||||
final int mappedId = mappedId(mappedIdentifier);
|
||||
if (mappedId == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int id = mappings.inverse().getNewId(mappedId);
|
||||
return id != -1 ? identifier(id) : null;
|
||||
return id != -1 ? keyFromId(id) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String mappedIdentifier(final int mappedId) {
|
||||
if (mappedId < 0 || mappedId >= mappedIdToString.length) {
|
||||
public @Nullable Key mappedKeyFromMappedId(final int mappedId) {
|
||||
if (mappedId < 0 || mappedId >= mappedIdToKey.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String identifier = mappedIdToString[mappedId];
|
||||
return Key.namespaced(identifier);
|
||||
return mappedIdToKey[mappedId];
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String mappedIdentifier(final String identifier) {
|
||||
public @Nullable Key mappedKeyFromKey(final String identifier) {
|
||||
final int id = id(identifier);
|
||||
if (id == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int mappedId = mappings.getNewId(id);
|
||||
return mappedId != -1 ? mappedIdentifier(mappedId) : null;
|
||||
return mappedId != -1 ? mappedKeyFromMappedId(mappedId) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -147,6 +144,6 @@ public class FullMappingsBase implements FullMappings {
|
||||
|
||||
@Override
|
||||
public FullMappings inverse() {
|
||||
return new FullMappingsBase(mappedStringToId, stringToId, mappedIdToString, idToString, mappings.inverse());
|
||||
return new FullMappingsBase(mappedStringToId, stringToId, mappedIdToKey, idToKey, mappings.inverse());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
|
||||
* Copyright (C) 2016-2026 ViaVersion and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.viaversion.viaversion.api.data;
|
||||
|
||||
import com.viaversion.nbt.tag.ByteArrayTag;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
* Reads sets of non-negative ints stored as ranges, packed into a byte array of varints.
|
||||
* Each range is stored as an offset and length pair. The offset is the start of the first range and the
|
||||
* difference to the previous range's exclusive end; the length is the inclusive end minus the start.
|
||||
*/
|
||||
public final class IdRanges {
|
||||
|
||||
/**
|
||||
* Returns the encoded id set as an int set.
|
||||
*
|
||||
* @param rangesTag tag with the encoded ranges
|
||||
* @return decoded ids
|
||||
*/
|
||||
public static IntSet decode(final ByteArrayTag rangesTag) {
|
||||
final IntSet ids = new IntOpenHashSet();
|
||||
forEachId(rangesTag, ids::add);
|
||||
return ids;
|
||||
}
|
||||
|
||||
public static void forEachId(final ByteArrayTag rangesTag, final IntConsumer consumer) {
|
||||
final ByteBuf buf = Unpooled.wrappedBuffer(rangesTag.getValue());
|
||||
int prevEnd = 0;
|
||||
while (buf.isReadable()) {
|
||||
final int start = prevEnd + Types.VAR_INT.readPrimitive(buf);
|
||||
final int end = start + Types.VAR_INT.readPrimitive(buf);
|
||||
for (int id = start; id <= end; id++) {
|
||||
consumer.accept(id);
|
||||
}
|
||||
prevEnd = end + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
|
||||
* Copyright (C) 2016-2026 ViaVersion and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.viaversion.viaversion.api.data;
|
||||
|
||||
import com.viaversion.viaversion.api.data.MappingDataLoader.IdentifiersPair;
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Full mappings for versions where required identifiers hadn't yet followed the resource location format (i.e., pre-1.9.4 sounds).
|
||||
*/
|
||||
public class LegacyFullStringMappingsBase extends FullMappingsBase {
|
||||
private final String[] idToString;
|
||||
private final String[] mappedIdToString;
|
||||
|
||||
public LegacyFullStringMappingsBase(final IdentifiersPair identifiersPair, final Mappings mappings) {
|
||||
super(toInverseMap(identifiersPair.unmapped()), toInverseMap(identifiersPair.mapped()), new Key[0], new Key[0], mappings);
|
||||
this.idToString = identifiersPair.unmapped().toArray(new String[0]);
|
||||
this.mappedIdToString = identifiersPair.mapped().toArray(new String[0]);
|
||||
}
|
||||
|
||||
private LegacyFullStringMappingsBase(final Object2IntMap<String> stringToId, final Object2IntMap<String> mappedStringToId, final String[] idToKey, final String[] mappedIdToString, final Mappings mappings) {
|
||||
super(stringToId, mappedStringToId, new Key[0], new Key[0], mappings);
|
||||
this.idToString = idToKey;
|
||||
this.mappedIdToString = mappedIdToString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String identifier(final int id) {
|
||||
if (id < 0 || id >= idToString.length) {
|
||||
return null;
|
||||
}
|
||||
return Key.namespaced(idToString[id]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String identifier(final String mappedIdentifier) {
|
||||
final int mappedId = mappedId(mappedIdentifier);
|
||||
if (mappedId == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int id = mappings.inverse().getNewId(mappedId);
|
||||
return id != -1 ? identifier(id) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String mappedIdentifier(final int mappedId) {
|
||||
if (mappedId < 0 || mappedId >= mappedIdToString.length) {
|
||||
return null;
|
||||
}
|
||||
return Key.namespaced(mappedIdToString[mappedId]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String mappedIdentifier(final String identifier) {
|
||||
final int id = id(identifier);
|
||||
if (id == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int mappedId = mappings.getNewId(id);
|
||||
return mappedId != -1 ? mappedIdentifier(mappedId) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Key keyFromId(final int id) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Key keyFromMappedKey(final String mappedIdentifier) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Key mappedKeyFromMappedId(final int mappedId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Key mappedKeyFromKey(final String identifier) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FullMappings inverse() {
|
||||
return new LegacyFullStringMappingsBase(mappedStringToId, stringToId, mappedIdToString, idToString, mappings.inverse());
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import com.viaversion.viaversion.util.Key;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public interface MappingData {
|
||||
@@ -190,15 +191,23 @@ public interface MappingData {
|
||||
*/
|
||||
@Nullable IntSet changedBlocks();
|
||||
|
||||
@Nullable Set<String> changedEnvironmentAttributes();
|
||||
|
||||
/**
|
||||
* Type of mappings. Currently only relevant for ops writing of generic holder classes and expanded when needed.
|
||||
*/
|
||||
enum MappingType implements RegistryKey {
|
||||
ITEM, BLOCK, SOUND, ENTITY_TYPE;
|
||||
|
||||
private final Key key;
|
||||
|
||||
MappingType() {
|
||||
this.key = Key.of(name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key key() {
|
||||
return Key.of(name().toLowerCase(Locale.ROOT));
|
||||
public Key registry() {
|
||||
return this.key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,8 @@ package com.viaversion.viaversion.api.data;
|
||||
|
||||
import com.viaversion.nbt.tag.CompoundTag;
|
||||
import com.viaversion.nbt.tag.IntArrayTag;
|
||||
import com.viaversion.nbt.tag.ListTag;
|
||||
import com.viaversion.nbt.tag.StringTag;
|
||||
import com.viaversion.nbt.tag.Tag;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.data.MappingDataLoader.IdentifiersPair;
|
||||
@@ -35,7 +37,9 @@ import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class MappingDataBase implements MappingData {
|
||||
@@ -63,6 +67,7 @@ public class MappingDataBase implements MappingData {
|
||||
* Set of block ids that have had type or property changes.
|
||||
*/
|
||||
protected IntSet changedBlocks;
|
||||
protected Set<String> changedEnvironmentAttributes;
|
||||
|
||||
public MappingDataBase(final String unmappedVersion, final String mappedVersion) {
|
||||
this.unmappedVersion = unmappedVersion;
|
||||
@@ -118,6 +123,7 @@ public class MappingDataBase implements MappingData {
|
||||
loadTags(RegistryType.ITEM, tagsTag);
|
||||
loadTags(RegistryType.BLOCK, tagsTag);
|
||||
loadTags(RegistryType.ENTITY, tagsTag);
|
||||
loadTags(RegistryType.FLUID, tagsTag);
|
||||
}
|
||||
|
||||
final IntArrayTag changedBlocks = data.getIntArrayTag("changed_blocks");
|
||||
@@ -125,6 +131,11 @@ public class MappingDataBase implements MappingData {
|
||||
this.changedBlocks = new IntOpenHashSet(changedBlocks.getValue());
|
||||
}
|
||||
|
||||
final ListTag<StringTag> changedEnvironmentAttributes = data.getListTag("changed_environment_attributes", StringTag.class);
|
||||
if (changedEnvironmentAttributes != null) {
|
||||
this.changedEnvironmentAttributes = changedEnvironmentAttributes.stream().map(StringTag::getValue).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
loadExtras(data);
|
||||
}
|
||||
|
||||
@@ -167,6 +178,10 @@ public class MappingDataBase implements MappingData {
|
||||
if (mappings == null) {
|
||||
mappings = new IdentityMappings(identifiersPair.unmapped().size(), identifiersPair.mapped().size());
|
||||
}
|
||||
return createFullMappings(identifiersPair, mappings);
|
||||
}
|
||||
|
||||
protected FullMappings createFullMappings(final IdentifiersPair identifiersPair, final Mappings mappings) {
|
||||
return FullMappingsBase.of(identifiersPair, mappings);
|
||||
}
|
||||
|
||||
@@ -349,6 +364,11 @@ public class MappingDataBase implements MappingData {
|
||||
return changedBlocks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Set<String> changedEnvironmentAttributes() {
|
||||
return changedEnvironmentAttributes;
|
||||
}
|
||||
|
||||
protected Logger getLogger() {
|
||||
return Via.getPlatform().getLogger();
|
||||
}
|
||||
|
||||
@@ -31,13 +31,17 @@ import com.google.gson.JsonSyntaxException;
|
||||
import com.viaversion.nbt.io.NBTIO;
|
||||
import com.viaversion.nbt.io.TagReader;
|
||||
import com.viaversion.nbt.tag.CompoundTag;
|
||||
import com.viaversion.nbt.tag.IntArrayTag;
|
||||
import com.viaversion.nbt.tag.IntTag;
|
||||
import com.viaversion.nbt.tag.ListTag;
|
||||
import com.viaversion.nbt.tag.StringTag;
|
||||
import com.viaversion.nbt.tag.Tag;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.util.GsonUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import java.io.BufferedInputStream;
|
||||
@@ -51,6 +55,7 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
@@ -59,15 +64,16 @@ public class MappingDataLoader {
|
||||
public static final MappingDataLoader INSTANCE = new MappingDataLoader(MappingDataLoader.class, "assets/viaversion/data/");
|
||||
public static final TagReader<CompoundTag> MAPPINGS_READER = NBTIO.reader(CompoundTag.class).named();
|
||||
private static final Map<String, String[]> GLOBAL_IDENTIFIER_INDEXES = new HashMap<>();
|
||||
private static final int VERSION = 2;
|
||||
private static final byte DIRECT_ID = 0;
|
||||
private static final byte SHIFTS_ID = 1;
|
||||
private static final byte CHANGES_ID = 2;
|
||||
private static final byte IDENTITY_ID = 3;
|
||||
|
||||
private final Map<String, CompoundTag> mappingsCache = new HashMap<>();
|
||||
private final Map<String, CompoundTag> mappingsCache = new ConcurrentHashMap<>();
|
||||
private final Class<?> dataLoaderClass;
|
||||
private final String dataPath;
|
||||
private boolean cacheValid = true;
|
||||
private volatile boolean cacheValid = true;
|
||||
|
||||
public MappingDataLoader(final Class<?> dataLoaderClass, final String dataPath) {
|
||||
this.dataLoaderClass = dataLoaderClass;
|
||||
@@ -110,8 +116,8 @@ public class MappingDataLoader {
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
mappingsCache.clear();
|
||||
cacheValid = false;
|
||||
mappingsCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,17 +166,14 @@ public class MappingDataLoader {
|
||||
return loadNBTFromFile(name);
|
||||
}
|
||||
|
||||
CompoundTag data = mappingsCache.get(name);
|
||||
if (data != null) {
|
||||
return data;
|
||||
if (cache) {
|
||||
// Also blocks concurrent loaders of the same file until the first one has finished
|
||||
return mappingsCache.computeIfAbsent(name, this::loadNBTFromFile);
|
||||
}
|
||||
|
||||
data = loadNBTFromFile(name);
|
||||
|
||||
if (cache && data != null) {
|
||||
mappingsCache.put(name, data);
|
||||
}
|
||||
return data;
|
||||
// Still do a lookup, but don't store it
|
||||
final CompoundTag data = mappingsCache.get(name);
|
||||
return data != null ? data : loadNBTFromFile(name);
|
||||
}
|
||||
|
||||
public @Nullable CompoundTag loadNBT(final String name) {
|
||||
@@ -211,15 +214,29 @@ public class MappingDataLoader {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int version = mappingsTag.getInt("version", -1);
|
||||
if (version != VERSION) {
|
||||
throw new IllegalArgumentException("Invalid version: " + version);
|
||||
}
|
||||
|
||||
final int mappedSize = tag.getInt("mappedSize", -1);
|
||||
final byte strategy = tag.getByteTag("id").asByte();
|
||||
final V mappings;
|
||||
if (strategy == DIRECT_ID) {
|
||||
final IntArrayTag valuesTag = tag.getIntArrayTag("val");
|
||||
return IntArrayMappings.of(valuesTag.getValue(), mappedSize);
|
||||
// Zigzag varints of the difference to the previous mapped id
|
||||
final int size = tag.getIntTag("size").asInt();
|
||||
final ByteBuf buf = Unpooled.wrappedBuffer(tag.getByteArrayTag("val").getValue());
|
||||
final int[] values = new int[size];
|
||||
int prev = 0;
|
||||
for (int i = 0; i < size; i++) {
|
||||
prev += readZigZagVarInt(buf);
|
||||
values[i] = prev;
|
||||
}
|
||||
return IntArrayMappings.of(values, mappedSize);
|
||||
} else if (strategy == SHIFTS_ID) {
|
||||
final int[] shiftsAt = tag.getIntArrayTag("at").getValue();
|
||||
final int[] shiftsTo = tag.getIntArrayTag("to").getValue();
|
||||
final ValuePairs pairs = readAtValuePairs(tag);
|
||||
final int[] shiftsAt = pairs.first;
|
||||
final int[] shiftsTo = pairs.second;
|
||||
final int size = tag.getIntTag("size").asInt();
|
||||
mappings = holderSupplier.get(size);
|
||||
|
||||
@@ -242,8 +259,9 @@ public class MappingDataLoader {
|
||||
}
|
||||
}
|
||||
} else if (strategy == CHANGES_ID) {
|
||||
final int[] changesAt = tag.getIntArrayTag("at").getValue();
|
||||
final int[] values = tag.getIntArrayTag("val").getValue();
|
||||
final ValuePairs pairs = readAtValuePairs(tag);
|
||||
final int[] changesAt = pairs.first;
|
||||
final int[] values = pairs.second;
|
||||
final int size = tag.getIntTag("size").asInt();
|
||||
final boolean fillBetween = tag.get("nofill") == null;
|
||||
mappings = holderSupplier.get(size);
|
||||
@@ -278,6 +296,37 @@ public class MappingDataLoader {
|
||||
return mappingsSupplier.create(mappings, mappedSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads alternating id and value pairs from varints. The id as the difference to the previous id,
|
||||
* the value as the zigzag difference to the previous value.
|
||||
*
|
||||
* @param tag tag to read the packed values from
|
||||
* @return array of ids and array of values
|
||||
*/
|
||||
private static ValuePairs readAtValuePairs(final CompoundTag tag) {
|
||||
final ByteBuf buf = Unpooled.wrappedBuffer(tag.getByteArrayTag("val").getValue());
|
||||
final IntList at = new IntArrayList();
|
||||
final IntList values = new IntArrayList();
|
||||
int prevAt = -1;
|
||||
int prevValue = 0;
|
||||
while (buf.isReadable()) {
|
||||
prevAt = prevAt + 1 + Types.VAR_INT.readPrimitive(buf);
|
||||
prevValue += readZigZagVarInt(buf);
|
||||
at.add(prevAt);
|
||||
values.add(prevValue);
|
||||
}
|
||||
return new ValuePairs(at.toIntArray(), values.toIntArray());
|
||||
}
|
||||
|
||||
private static int readZigZagVarInt(final ByteBuf buf) {
|
||||
// https://lemire.me/blog/2022/11/25/making-all-your-integers-positive-with-zigzag-encoding/
|
||||
// In short, using deltas to previous ids makes the numbers small, and using zigzag encoding means they're all positive.
|
||||
// Being all positive means we can use varints to make most of the data much shorter than 8 bytes.
|
||||
// All of that combined ends up having a decent effect when compressed in the jar file as well.
|
||||
final int value = Types.VAR_INT.readPrimitive(buf);
|
||||
return (value >>> 1) ^ -(value & 1);
|
||||
}
|
||||
|
||||
public @Nullable List<String> identifiersFromGlobalIds(final CompoundTag mappingsTag, final String key) {
|
||||
final Mappings mappings = loadMappings(mappingsTag, key);
|
||||
return mappings != null ? identifiers(mappings, key) : null;
|
||||
@@ -334,8 +383,9 @@ public class MappingDataLoader {
|
||||
}
|
||||
|
||||
private List<String> identifiers(final Mappings mappings, final String key) {
|
||||
final List<String> identifiers = new ArrayList<>(mappings.size());
|
||||
for (int i = 0; i < mappings.size(); i++) {
|
||||
final int size = mappings.size();
|
||||
final List<String> identifiers = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
final int globalId = mappings.getNewId(i);
|
||||
identifiers.add(identifierFromGlobalId(key, globalId));
|
||||
}
|
||||
@@ -412,4 +462,7 @@ public class MappingDataLoader {
|
||||
this(identifiers, identifiers, true);
|
||||
}
|
||||
}
|
||||
|
||||
private record ValuePairs(int[] first, int[] second) {
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,9 @@
|
||||
package com.viaversion.viaversion.api.data.entity;
|
||||
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.minecraft.chunks.Chunk;
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
|
||||
import com.viaversion.viaversion.api.type.Type;
|
||||
import com.viaversion.viaversion.util.KeyMappings;
|
||||
import java.util.Map;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
@@ -43,8 +45,9 @@ public interface EntityTracker {
|
||||
*
|
||||
* @param id entity id
|
||||
* @param type entity type
|
||||
* @return tracked entity
|
||||
*/
|
||||
void addEntity(int id, EntityType type);
|
||||
TrackedEntity addEntity(int id, EntityType type);
|
||||
|
||||
/**
|
||||
* Returns whether the entity is currently tracked.
|
||||
@@ -75,8 +78,9 @@ public interface EntityTracker {
|
||||
* Untracks an entity.
|
||||
*
|
||||
* @param id entity id
|
||||
* @return previously tracked entity
|
||||
*/
|
||||
void removeEntity(int id);
|
||||
@Nullable TrackedEntity removeEntity(int id);
|
||||
|
||||
/**
|
||||
* Clears stored entity types and data, only leaving behind the client entity.
|
||||
@@ -88,25 +92,6 @@ public interface EntityTracker {
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Returns the stored entity data if an entity with the id is tracked, else null.
|
||||
* If no data has been initialized yet, it will be done and returned by this method.
|
||||
*
|
||||
* @param id entity id
|
||||
* @return stored entity data if an entity with the id is tracked, else null
|
||||
* @throws IllegalArgumentException if entity data storage has not been enabled via the implementation
|
||||
*/
|
||||
@Nullable StoredEntityData entityData(int id);
|
||||
|
||||
/**
|
||||
* Returns stored entity data if it has previously been initialized by {@link #entityData(int)}, else null.
|
||||
*
|
||||
* @param id entity id
|
||||
* @return stored entity data if it has previously been initialized by {@link #entityData(int)}
|
||||
* @throws IllegalArgumentException if entity data storage has not been enabled via the implementation
|
||||
*/
|
||||
@Nullable StoredEntityData entityDataIfPresent(int id);
|
||||
|
||||
/**
|
||||
* Returns whether the client entity id has been set.
|
||||
*
|
||||
@@ -204,6 +189,24 @@ public interface EntityTracker {
|
||||
|
||||
void setBiomesSent(int biomesSent);
|
||||
|
||||
/**
|
||||
* Returns the cached chunk type for the current dimension if set.
|
||||
*
|
||||
* @param mapped whether to return the mapped chunk type
|
||||
* @return cached chunk type
|
||||
*/
|
||||
@Nullable Type<Chunk> chunkType(boolean mapped);
|
||||
|
||||
/**
|
||||
* Caches the chunk type for the current dimension.
|
||||
* <p>
|
||||
* Invalidates when the world section height or the number of biomes change.
|
||||
*
|
||||
* @param mapped whether to set the mapped chunk type
|
||||
* @param chunkType chunk type
|
||||
*/
|
||||
void setChunkType(boolean mapped, @Nullable Type<Chunk> chunkType);
|
||||
|
||||
EntityType playerType();
|
||||
|
||||
@Nullable DimensionData dimensionData(String dimension);
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
|
||||
* Copyright (C) 2016-2026 ViaVersion and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.viaversion.viaversion.api.data.entity;
|
||||
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public interface StoredEntityData {
|
||||
|
||||
/**
|
||||
* Returns the entity type of the stored entity.
|
||||
*
|
||||
* @return entity type
|
||||
*/
|
||||
EntityType type();
|
||||
|
||||
/**
|
||||
* Checks if the storage contains an object of the given type.
|
||||
*
|
||||
* @param objectClass object class to check
|
||||
* @return whether an object of the given type is in the storage
|
||||
*/
|
||||
boolean has(Class<?> objectClass);
|
||||
|
||||
/**
|
||||
* Returns an object from the storage if present.
|
||||
*
|
||||
* @param objectClass class of the object to get
|
||||
* @param <T> object type
|
||||
* @return object if present
|
||||
*/
|
||||
@Nullable <T> T get(Class<T> objectClass);
|
||||
|
||||
/**
|
||||
* Removes and returns an object from the storage if present.
|
||||
*
|
||||
* @param objectClass class of the object to remove
|
||||
* @param <T> object type
|
||||
* @return removed object if present
|
||||
*/
|
||||
@Nullable <T> T remove(Class<T> objectClass);
|
||||
|
||||
/**
|
||||
* Stores an object based on its class.
|
||||
*
|
||||
* @param object object to store
|
||||
*/
|
||||
void put(Object object);
|
||||
}
|
||||
@@ -22,7 +22,9 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.api.data.entity;
|
||||
|
||||
import com.viaversion.viaversion.api.connection.StorableObject;
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public interface TrackedEntity {
|
||||
|
||||
@@ -34,18 +36,37 @@ public interface TrackedEntity {
|
||||
EntityType entityType();
|
||||
|
||||
/**
|
||||
* Object to hold arbitrary additional data.
|
||||
* Checks if the storage contains an object of the given type.
|
||||
*
|
||||
* @return entity data
|
||||
* @param objectClass object class to check
|
||||
* @return whether an object of the given type is in the storage
|
||||
*/
|
||||
StoredEntityData data();
|
||||
boolean has(Class<? extends StorableObject> objectClass);
|
||||
|
||||
/**
|
||||
* Returns whether the stored entity currently has any additional data.
|
||||
* Returns an object from the storage if present.
|
||||
*
|
||||
* @return whether the stored entity currently has additional data
|
||||
* @param objectClass class of the object to get
|
||||
* @param <T> object type
|
||||
* @return object if present
|
||||
*/
|
||||
boolean hasData();
|
||||
@Nullable <T extends StorableObject> T get(Class<T> objectClass);
|
||||
|
||||
/**
|
||||
* Removes and returns an object from the storage if present.
|
||||
*
|
||||
* @param objectClass class of the object to remove
|
||||
* @param <T> object type
|
||||
* @return removed object if present
|
||||
*/
|
||||
@Nullable <T extends StorableObject> T remove(Class<T> objectClass);
|
||||
|
||||
/**
|
||||
* Stores an object based on its class.
|
||||
*
|
||||
* @param object object to store
|
||||
*/
|
||||
void put(StorableObject object);
|
||||
|
||||
/**
|
||||
* Returns whether entity data has already been sent at least once for this entity.
|
||||
|
||||
@@ -34,7 +34,7 @@ import com.viaversion.viaversion.util.Key;
|
||||
*/
|
||||
public interface RegistryKey {
|
||||
|
||||
Key key();
|
||||
Key registry();
|
||||
|
||||
static RegistryKey of(final String key) {
|
||||
return new SimpleRegistryKey(Key.of(key));
|
||||
|
||||
@@ -43,7 +43,7 @@ public enum RegistryType implements RegistryKey {
|
||||
|
||||
static {
|
||||
for (RegistryType type : getValues()) {
|
||||
MAP.put(type.identifier, type);
|
||||
MAP.put(type.identifier(), type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,18 +55,18 @@ public enum RegistryType implements RegistryKey {
|
||||
return MAP.get(identifier);
|
||||
}
|
||||
|
||||
private final String identifier;
|
||||
private final Key key;
|
||||
|
||||
RegistryType(final String identifier) {
|
||||
this.identifier = identifier;
|
||||
this.key = Key.of(identifier);
|
||||
}
|
||||
|
||||
public String identifier() {
|
||||
return identifier;
|
||||
return key.path();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key key() {
|
||||
return Key.of(identifier);
|
||||
public Key registry() {
|
||||
return this.key;
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,10 @@ package com.viaversion.viaversion.api.minecraft;
|
||||
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
|
||||
record SimpleRegistryKey(Key key) implements RegistryKey {
|
||||
record SimpleRegistryKey(Key registry) implements RegistryKey {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return key.toString();
|
||||
return registry.toString();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.api.minecraft.chunks;
|
||||
|
||||
import com.viaversion.viaversion.util.CompactArrayUtil;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.IntPredicate;
|
||||
import java.util.function.IntUnaryOperator;
|
||||
|
||||
public interface DataPalette {
|
||||
@@ -146,6 +149,27 @@ public interface DataPalette {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs over all section coordinate indices that match the given predicate.
|
||||
*
|
||||
* @param idPredicate predicate to test ids
|
||||
* @param coordinateConsumer consumer to run over applicable coordinate indices (not ids!)
|
||||
* @see #idAt(int)
|
||||
*/
|
||||
void forEachMatchingCoordinate(IntPredicate idPredicate, IntConsumer coordinateConsumer);
|
||||
|
||||
/**
|
||||
* Packs the section values into a compact long array as used by the protocol.
|
||||
*
|
||||
* @param bitsPerValue bits used per value
|
||||
* @param entries number of values
|
||||
* @param rawIds whether to pack raw ids instead of palette indexes (direct palette)
|
||||
* @return compact long array of the values
|
||||
*/
|
||||
default long[] createPackedValues(final int bitsPerValue, final int entries, final boolean rawIds) {
|
||||
return CompactArrayUtil.createCompactArrayWithPadding(bitsPerValue, entries, rawIds ? this::idAt : this::paletteIndexAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the palette.
|
||||
*
|
||||
|
||||
+179
-11
@@ -22,9 +22,13 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.api.minecraft.chunks;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.viaversion.viaversion.util.CompactArrayUtil;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.IntPredicate;
|
||||
import java.util.function.IntUnaryOperator;
|
||||
|
||||
public final class DataPaletteImpl implements DataPalette {
|
||||
@@ -32,8 +36,8 @@ public final class DataPaletteImpl implements DataPalette {
|
||||
private static final int DEFAULT_INITIAL_SIZE = 16;
|
||||
|
||||
private final IntArrayList palette;
|
||||
private final Int2IntMap inversePalette;
|
||||
private final int sizeBits;
|
||||
private Int2IntMap inversePalette;
|
||||
private ChunkData values;
|
||||
|
||||
public DataPaletteImpl(final int valuesLength) {
|
||||
@@ -43,11 +47,30 @@ public final class DataPaletteImpl implements DataPalette {
|
||||
public DataPaletteImpl(final int valuesLength, final int initialSize) {
|
||||
values = new SingleChunkData(valuesLength);
|
||||
sizeBits = Integer.numberOfTrailingZeros(valuesLength) / 3;
|
||||
// Pre-size the palette array/map
|
||||
// Pre-size the palette array
|
||||
palette = new IntArrayList(initialSize);
|
||||
// To get an initial table size of initialSize, need to scale it down by load factor.
|
||||
inversePalette = new Int2IntOpenHashMap((int) (initialSize * Int2IntOpenHashMap.DEFAULT_LOAD_FACTOR));
|
||||
inversePalette.defaultReturnValue(-1);
|
||||
}
|
||||
|
||||
private Int2IntMap inversePalette() {
|
||||
// Lazily build the inverse palette. Most palettes never need it
|
||||
if (inversePalette == null) {
|
||||
inversePalette = new Int2IntOpenHashMap(palette.size());
|
||||
inversePalette.defaultReturnValue(-1);
|
||||
for (int i = 0; i < palette.size(); i++) {
|
||||
inversePalette.put(palette.getInt(i), i);
|
||||
}
|
||||
}
|
||||
return inversePalette;
|
||||
}
|
||||
|
||||
private Int2IntMap emptyInversePalette() {
|
||||
if (inversePalette == null) {
|
||||
inversePalette = new Int2IntOpenHashMap(palette.size());
|
||||
inversePalette.defaultReturnValue(-1);
|
||||
} else {
|
||||
inversePalette.clear();
|
||||
}
|
||||
return inversePalette;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,7 +86,7 @@ public final class DataPaletteImpl implements DataPalette {
|
||||
|
||||
@Override
|
||||
public void setIdAt(final int sectionCoordinate, final int id) {
|
||||
int index = inversePalette.get(id);
|
||||
int index = inversePalette().get(id);
|
||||
if (index == -1) {
|
||||
index = palette.size();
|
||||
palette.add(id);
|
||||
@@ -83,6 +106,38 @@ public final class DataPaletteImpl implements DataPalette {
|
||||
values.set(sectionCoordinate, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores palette indexes from a compact long array, replacing all current values.
|
||||
* The data is kept packed unless written to; reads extract bits the slow way, since almost all work will use replaceIds or will only have a few reads.
|
||||
* <p>
|
||||
* Only valid for local palettes (bitsPerValue <= 8).
|
||||
*
|
||||
* @param data compact long array
|
||||
* @param bitsPerValue bits per value
|
||||
* @param valuesPerLong packked values per long
|
||||
*/
|
||||
public void setPaletteIndexes(final long[] data, final int bitsPerValue, final int valuesPerLong) {
|
||||
Preconditions.checkArgument(bitsPerValue <= 8, "Cannot pack global palette");
|
||||
values = new PackedChunkData(data, bitsPerValue, valuesPerLong, values.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] createPackedValues(final int bitsPerValue, final int entries, final boolean rawIds) {
|
||||
if (rawIds) {
|
||||
return CompactArrayUtil.createCompactArrayWithPadding(bitsPerValue, entries, this::idAt);
|
||||
}
|
||||
|
||||
if (values instanceof final PackedChunkData packed && packed.bitsPerValue == bitsPerValue) {
|
||||
// Values were never modified and the width still matches - write them back as-is
|
||||
return packed.data;
|
||||
}
|
||||
|
||||
final int valuesPerLong = (char) (64 / bitsPerValue);
|
||||
final long[] data = new long[(entries + valuesPerLong - 1) / valuesPerLong];
|
||||
values.packInto(data, bitsPerValue);
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return palette.size();
|
||||
@@ -96,7 +151,7 @@ public final class DataPaletteImpl implements DataPalette {
|
||||
@Override
|
||||
public void setIdByIndex(final int index, final int id) {
|
||||
final int oldId = palette.set(index, id);
|
||||
if (oldId == id) return;
|
||||
if (oldId == id || inversePalette == null) return;
|
||||
|
||||
inversePalette.put(id, index);
|
||||
if (inversePalette.get(oldId) == index) {
|
||||
@@ -112,7 +167,7 @@ public final class DataPaletteImpl implements DataPalette {
|
||||
|
||||
@Override
|
||||
public void replaceId(final int oldId, final int newId) {
|
||||
final int index = inversePalette.remove(oldId);
|
||||
final int index = inversePalette().remove(oldId);
|
||||
if (index == -1) return;
|
||||
|
||||
inversePalette.put(newId, index);
|
||||
@@ -130,12 +185,12 @@ public final class DataPaletteImpl implements DataPalette {
|
||||
return;
|
||||
}
|
||||
|
||||
inversePalette.clear();
|
||||
int writeIndex = 0;
|
||||
int[] indexRemap = null;
|
||||
int firstCollision = 0;
|
||||
|
||||
final int[] palette = this.palette.elements();
|
||||
final Int2IntMap inversePalette = emptyInversePalette();
|
||||
for (int readIndex = 0; readIndex < oldSize; readIndex++) {
|
||||
final int mappedId = mapper.applyAsInt(palette[readIndex]);
|
||||
final int existingIndex = inversePalette.putIfAbsent(mappedId, writeIndex);
|
||||
@@ -172,14 +227,51 @@ public final class DataPaletteImpl implements DataPalette {
|
||||
|
||||
@Override
|
||||
public void addId(final int id) {
|
||||
inversePalette.put(id, palette.size());
|
||||
if (inversePalette != null) {
|
||||
inversePalette.put(id, palette.size());
|
||||
}
|
||||
palette.add(id);
|
||||
}
|
||||
|
||||
private static final ThreadLocal<boolean[]> MATCHING_INDEXES = ThreadLocal.withInitial(() -> new boolean[ChunkSection.SIZE]);
|
||||
|
||||
@Override
|
||||
public void forEachMatchingCoordinate(final IntPredicate idPredicate, final IntConsumer coordinateConsumer) {
|
||||
final int size = size();
|
||||
if (size > ChunkSection.SIZE) { // Shouldn't ever happen normally, but is technically possible...
|
||||
for (int idx = 0, len = this.values.size(); idx < len; idx++) {
|
||||
if (idPredicate.test(idAt(idx))) {
|
||||
coordinateConsumer.accept(idx);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Instead of going through every single coordinate value (4096 for block sections), check the palette ids first.
|
||||
// Reuse a thread-local array for quick checking against palette indexes.
|
||||
final boolean[] matching = MATCHING_INDEXES.get();
|
||||
boolean any = false;
|
||||
for (int i = 0; i < size; i++) {
|
||||
final boolean match = idPredicate.test(idByIndex(i));
|
||||
matching[i] = match;
|
||||
any |= match;
|
||||
}
|
||||
|
||||
if (!any) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int idx = 0, len = this.values.size(); idx < len; idx++) {
|
||||
if (matching[paletteIndexAt(idx)]) {
|
||||
coordinateConsumer.accept(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
palette.clear();
|
||||
inversePalette.clear();
|
||||
inversePalette = null;
|
||||
}
|
||||
|
||||
interface ChunkData {
|
||||
@@ -188,6 +280,11 @@ public final class DataPaletteImpl implements DataPalette {
|
||||
void set(int idx, int val);
|
||||
|
||||
int size();
|
||||
|
||||
/**
|
||||
* Packs all values into the given compact long array.
|
||||
*/
|
||||
void packInto(long[] data, int bitsPerValue);
|
||||
}
|
||||
|
||||
private final class SingleChunkData implements ChunkData {
|
||||
@@ -215,6 +312,11 @@ public final class DataPaletteImpl implements DataPalette {
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packInto(final long[] data, final int bitsPerValue) {
|
||||
// All values are 0, nothing to write
|
||||
}
|
||||
}
|
||||
|
||||
private final class ByteChunkData implements ChunkData {
|
||||
@@ -224,6 +326,10 @@ public final class DataPaletteImpl implements DataPalette {
|
||||
this.data = new byte[size];
|
||||
}
|
||||
|
||||
public ByteChunkData(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get(int idx) {
|
||||
return data[idx] & 0xFF;
|
||||
@@ -244,6 +350,11 @@ public final class DataPaletteImpl implements DataPalette {
|
||||
public int size() {
|
||||
return data.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packInto(final long[] out, final int bitsPerValue) {
|
||||
CompactArrayUtil.packWithPadding(data, out, bitsPerValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ShortChunkData implements ChunkData {
|
||||
@@ -270,6 +381,63 @@ public final class DataPaletteImpl implements DataPalette {
|
||||
public int size() {
|
||||
return data.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packInto(final long[] out, final int bitsPerValue) {
|
||||
CompactArrayUtil.packWithPadding(data, out, bitsPerValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw chunk data as read from the buffer.
|
||||
*/
|
||||
private final class PackedChunkData implements ChunkData {
|
||||
private final long[] data;
|
||||
private final int bitsPerValue;
|
||||
private final int valuesPerLong;
|
||||
private final long maxEntryValue;
|
||||
private final int size;
|
||||
|
||||
private PackedChunkData(final long[] data, final int bitsPerValue, final int valuesPerLong, final int size) {
|
||||
this.data = data;
|
||||
this.bitsPerValue = bitsPerValue;
|
||||
this.valuesPerLong = valuesPerLong;
|
||||
this.size = size;
|
||||
this.maxEntryValue = (1L << bitsPerValue) - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get(final int idx) {
|
||||
// Called so rarely that the cheap storage outweighs the more complex getter
|
||||
final int cellIndex = idx / valuesPerLong;
|
||||
final int bitIndex = (idx - cellIndex * valuesPerLong) * bitsPerValue;
|
||||
return (int) (data[cellIndex] >>> bitIndex & maxEntryValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(final int idx, final int val) {
|
||||
// First write access, actually read out for fast value access
|
||||
final ChunkData byteChunkData = extractData();
|
||||
values = byteChunkData;
|
||||
byteChunkData.set(idx, val);
|
||||
}
|
||||
|
||||
private ChunkData extractData() {
|
||||
final byte[] unpackedData = new byte[size];
|
||||
CompactArrayUtil.unpackWithPadding(this.data, unpackedData, bitsPerValue);
|
||||
return new ByteChunkData(unpackedData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packInto(final long[] out, final int newBitsPerValue) {
|
||||
// Always called with bitsPerValue != newBitsPerValue, which should only really happen if the server sends an unncessarily high bit width,
|
||||
// or if the number of ids shrunk in replaceIds. Rare enough to not need its own special method
|
||||
extractData().packInto(out, newBitsPerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-8
@@ -39,8 +39,8 @@ final class RegistryAccessImpl implements CodecContext.RegistryAccess {
|
||||
private final boolean mapped;
|
||||
|
||||
RegistryAccessImpl(final Protocol<?, ?, ?, ?> protocol, final UserConnection connection) {
|
||||
this.mappingData = Preconditions.checkNotNull(protocol.getMappingData());
|
||||
this.entityTracker = Preconditions.checkNotNull(protocol.getEntityRewriter().tracker(connection));
|
||||
this.mappingData = Preconditions.checkNotNull(protocol.getMappingData(), "No mapping data present for protocol");
|
||||
this.entityTracker = Preconditions.checkNotNull(protocol.getEntityRewriter().tracker(connection), "Entity tracker has to be added first");
|
||||
this.connection = connection;
|
||||
this.mapped = false;
|
||||
}
|
||||
@@ -102,17 +102,16 @@ final class RegistryAccessImpl implements CodecContext.RegistryAccess {
|
||||
public Key registryKey(final String registry, final int id) {
|
||||
final KeyMappings mappings = entityTracker.registryKeys(registry);
|
||||
Preconditions.checkNotNull(mappings, "No registry mappings for registry: %s", registry);
|
||||
final String identifier = id >= 0 && id < mappings.size() ? mappings.idToKey(id) : null;
|
||||
return key(identifier, id);
|
||||
return keyOrUnknown(mappings.key(id), id);
|
||||
}
|
||||
|
||||
private Key key(final FullMappings mappings, final int id) {
|
||||
final String identifier = mapped ? mappings.mappedIdentifier(id) : mappings.identifier(id);
|
||||
return key(identifier, id);
|
||||
final Key key = mapped ? mappings.mappedKeyFromMappedId(id) : mappings.keyFromId(id);
|
||||
return keyOrUnknown(key, id);
|
||||
}
|
||||
|
||||
private Key key(@Nullable final String identifier, final int id) {
|
||||
return identifier != null ? Key.of(identifier) : Key.of("viaversion", "unknown/" + id);
|
||||
private Key keyOrUnknown(@Nullable final Key key, final int id) {
|
||||
return key != null ? key : Key.of("viaversion", "unknown/" + id);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -270,7 +270,7 @@ public final class StructuredDataContainer implements Copyable {
|
||||
|
||||
@Override
|
||||
public StructuredDataContainer copy() {
|
||||
final Reference2ObjectOpenHashMap<StructuredDataKey<?>, StructuredData<?>> map = new Reference2ObjectOpenHashMap<>();
|
||||
final Reference2ObjectOpenHashMap<StructuredDataKey<?>, StructuredData<?>> map = new Reference2ObjectOpenHashMap<>(data.size());
|
||||
for (final StructuredData<?> value : data.values()) {
|
||||
map.put(value.key(), value.copy());
|
||||
}
|
||||
|
||||
+3
-1
@@ -23,10 +23,12 @@
|
||||
package com.viaversion.viaversion.api.minecraft.item.data;
|
||||
|
||||
import com.viaversion.viaversion.api.minecraft.HolderSet;
|
||||
import com.viaversion.viaversion.api.minecraft.RegistryKey;
|
||||
import com.viaversion.viaversion.api.minecraft.codec.Ops;
|
||||
import com.viaversion.viaversion.api.type.TransformingType;
|
||||
import com.viaversion.viaversion.api.type.Type;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.api.type.types.misc.HolderSetType;
|
||||
|
||||
public record DamageResistant26_1(HolderSet types) {
|
||||
|
||||
@@ -34,7 +36,7 @@ public record DamageResistant26_1(HolderSet types) {
|
||||
|
||||
@Override
|
||||
public void write(final Ops ops, final DamageResistant26_1 value) {
|
||||
ops.writeMap(map -> map.write("types", Types.HOLDER_SET, value.types));
|
||||
ops.writeMap(map -> map.write("types", new HolderSetType(RegistryKey.of("damage_type")), value.types));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -22,7 +22,6 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.api.type.types;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.viaversion.viaversion.api.minecraft.codec.Ops;
|
||||
import com.viaversion.viaversion.api.type.OptionalType;
|
||||
import com.viaversion.viaversion.api.type.Type;
|
||||
@@ -35,6 +34,7 @@ public class StringType extends Type<String> {
|
||||
private static final int MAX_CHAR_UTF_8_LENGTH = Character.toString(Character.MAX_VALUE)
|
||||
.getBytes(StandardCharsets.UTF_8).length;
|
||||
private final int maxLength;
|
||||
private final int maxByteLength;
|
||||
|
||||
public StringType() {
|
||||
this(Short.MAX_VALUE);
|
||||
@@ -43,21 +43,22 @@ public class StringType extends Type<String> {
|
||||
public StringType(int maxLength) {
|
||||
super(String.class);
|
||||
this.maxLength = maxLength;
|
||||
this.maxByteLength = maxLength * MAX_CHAR_UTF_8_LENGTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String read(ByteBuf buffer) {
|
||||
int len = Types.VAR_INT.readPrimitive(buffer);
|
||||
|
||||
Preconditions.checkArgument(len <= maxLength * MAX_CHAR_UTF_8_LENGTH,
|
||||
"Cannot receive string longer than " + maxLength + " * " + MAX_CHAR_UTF_8_LENGTH + " bytes (got %s bytes)", len);
|
||||
if (len > maxByteLength) {
|
||||
throw new IllegalArgumentException("Cannot receive string longer than " + maxLength + " * " + MAX_CHAR_UTF_8_LENGTH + " bytes (got " + len + " bytes)");
|
||||
}
|
||||
|
||||
String string = buffer.toString(buffer.readerIndex(), len, StandardCharsets.UTF_8);
|
||||
buffer.skipBytes(len);
|
||||
|
||||
Preconditions.checkArgument(string.length() <= maxLength,
|
||||
"Cannot receive string longer than " + maxLength + " characters (got %s bytes)", string.length());
|
||||
|
||||
if (string.length() > maxLength) {
|
||||
throw new IllegalArgumentException("Cannot receive string longer than " + maxLength + " characters (got " + string.length() + ")");
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
|
||||
+6
-7
@@ -83,8 +83,11 @@ public class PaletteType1_18 extends PaletteTypeBase {
|
||||
final int valuesPerLong = (char) (64 / bitsPerValue);
|
||||
final int expectedLength = (type.size() + valuesPerLong - 1) / valuesPerLong;
|
||||
if (values.length == expectedLength) {
|
||||
CompactArrayUtil.iterateCompactArrayWithPadding(bitsPerValue, type.size(), values,
|
||||
bitsPerValue == globalPaletteBits ? palette::setIdAt : palette::setPaletteIndexAt);
|
||||
if (bitsPerValue == globalPaletteBits) {
|
||||
CompactArrayUtil.iterateCompactArrayWithPadding(bitsPerValue, type.size(), values, palette::setIdAt);
|
||||
} else {
|
||||
palette.setPaletteIndexes(values, bitsPerValue, valuesPerLong);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,11 +123,7 @@ public class PaletteType1_18 extends PaletteTypeBase {
|
||||
return;
|
||||
}
|
||||
|
||||
final long[] values = CompactArrayUtil.createCompactArrayWithPadding(
|
||||
bitsPerValue,
|
||||
type.size(),
|
||||
bitsPerValue == globalPaletteBits ? palette::idAt : palette::paletteIndexAt
|
||||
);
|
||||
final long[] values = palette.createPackedValues(bitsPerValue, type.size(), bitsPerValue == globalPaletteBits);
|
||||
Types.LONG_ARRAY_PRIMITIVE.write(buffer, values);
|
||||
}
|
||||
|
||||
|
||||
+6
-11
@@ -46,12 +46,11 @@ public final class PaletteType1_21_5 extends PaletteType1_18 {
|
||||
final int expectedLength = (type.size() + valuesPerLong - 1) / valuesPerLong;
|
||||
final long[] values = LongArrayType.readFixedLength(buffer, expectedLength);
|
||||
if (values.length != 0) {
|
||||
CompactArrayUtil.iterateCompactArrayWithPadding(
|
||||
bitsPerValue,
|
||||
type.size(),
|
||||
values,
|
||||
bitsPerValue == globalPaletteBits ? palette::setIdAt : palette::setPaletteIndexAt
|
||||
);
|
||||
if (bitsPerValue == globalPaletteBits) {
|
||||
CompactArrayUtil.iterateCompactArrayWithPadding(bitsPerValue, type.size(), values, palette::setIdAt);
|
||||
} else {
|
||||
palette.setPaletteIndexes(values, bitsPerValue, valuesPerLong);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,11 +60,7 @@ public final class PaletteType1_21_5 extends PaletteType1_18 {
|
||||
return;
|
||||
}
|
||||
|
||||
final long[] values = CompactArrayUtil.createCompactArrayWithPadding(
|
||||
bitsPerValue,
|
||||
type.size(),
|
||||
bitsPerValue == globalPaletteBits ? palette::idAt : palette::paletteIndexAt
|
||||
);
|
||||
final long[] values = palette.createPackedValues(bitsPerValue, type.size(), bitsPerValue == globalPaletteBits);
|
||||
LongArrayType.writeFixedLength(buffer, values);
|
||||
}
|
||||
|
||||
|
||||
@@ -133,8 +133,7 @@ public class ItemType1_20_5 extends Type<Item> {
|
||||
}
|
||||
|
||||
map.writeMap("components", components -> {
|
||||
for (final Map.Entry<StructuredDataKey<?>, StructuredData<?>> entry : item.dataContainer().data().entrySet()) {
|
||||
final StructuredData<?> data = entry.getValue();
|
||||
for (final StructuredData<?> data : item.dataContainer().data().values()) {
|
||||
String key = Key.namespaced(data.key().identifier());
|
||||
if (data.isEmpty()) {
|
||||
key = "!" + key;
|
||||
|
||||
@@ -100,9 +100,9 @@ public class HolderSetType extends Type<HolderSet> {
|
||||
if (registryKey instanceof final MappingData.MappingType mappingType) {
|
||||
return ops.context().registryAccess().key(mappingType, id);
|
||||
} else if (registryKey instanceof final RegistryValueType registryValueType) {
|
||||
return Key.of(registryValueType.byId(id));
|
||||
return registryValueType.byId(id);
|
||||
} else {
|
||||
return ops.context().registryAccess().registryKey(registryKey.key().toString(), id);
|
||||
return ops.context().registryAccess().registryKey(registryKey.registry().toString(), id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-14
@@ -30,32 +30,32 @@ import com.viaversion.viaversion.util.Key;
|
||||
|
||||
public final class RegistryValueType extends VarIntType implements RegistryKey {
|
||||
|
||||
private final Key key;
|
||||
private final String[] names;
|
||||
private final Key registry;
|
||||
private final Key[] keys;
|
||||
|
||||
public RegistryValueType(final Key key, final String... names) {
|
||||
this.key = key;
|
||||
this.names = names;
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
names[i] = Key.namespaced(names[i]);
|
||||
public RegistryValueType(final Key registry, final String... keys) {
|
||||
this.registry = registry;
|
||||
this.keys = new Key[keys.length];
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
this.keys[i] = Key.of(keys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final Ops ops, final Integer value) {
|
||||
Types.IDENTIFIER.write(ops, Key.of(names[value]));
|
||||
Types.IDENTIFIER.write(ops, keys[value]);
|
||||
}
|
||||
|
||||
public String[] names() {
|
||||
return names;
|
||||
public Key[] keys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
public String byId(final int id) {
|
||||
return names[id];
|
||||
public Key byId(final int id) {
|
||||
return keys[id];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key key() {
|
||||
return key;
|
||||
public Key registry() {
|
||||
return registry;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -87,4 +87,4 @@ public final class ResolvableProfileType extends Type<ResolvableProfile> {
|
||||
.writeOptional("model", Types.OPTIONAL_STRING, modelType)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
+1
-1
@@ -42,7 +42,7 @@ public final class SynchronizedRegistryEitherType extends EitherType<Integer, St
|
||||
@Override
|
||||
public void write(final Ops ops, final Either<Integer, String> value) {
|
||||
if (value.isLeft()) {
|
||||
final Key key = ops.context().registryAccess().registryKey(this.registryKey.key().toString(), value.left());
|
||||
final Key key = ops.context().registryAccess().registryKey(this.registryKey.registry().toString(), value.left());
|
||||
Types.IDENTIFIER.write(ops, key);
|
||||
} else {
|
||||
Types.IDENTIFIER.write(ops, Key.of(value.right()));
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ public final class SynchronizedRegistryValueType extends VarIntType {
|
||||
|
||||
@Override
|
||||
public void write(final Ops ops, final Integer value) {
|
||||
final Key key = ops.context().registryAccess().registryKey(this.registryKey.key().toString(), value);
|
||||
final Key key = ops.context().registryAccess().registryKey(this.registryKey.registry().toString(), value);
|
||||
Types.IDENTIFIER.write(ops, key);
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,67 @@ public final class CompactArrayUtil {
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Utility methods for packing/unpacking raw data at once and without indirection.
|
||||
// They go value-first instead of cell-first
|
||||
|
||||
public static void packWithPadding(final byte[] values, final long[] out, final int bitsPerEntry) {
|
||||
final long maxEntryValue = (1L << bitsPerEntry) - 1;
|
||||
long cell = 0L;
|
||||
int bitIndex = 0;
|
||||
int outIndex = 0;
|
||||
for (final byte value : values) {
|
||||
cell |= (value & maxEntryValue) << bitIndex;
|
||||
bitIndex += bitsPerEntry;
|
||||
if (bitIndex + bitsPerEntry > 64) {
|
||||
out[outIndex++] = cell;
|
||||
cell = 0L;
|
||||
bitIndex = 0;
|
||||
}
|
||||
}
|
||||
if (bitIndex != 0) {
|
||||
out[outIndex] = cell;
|
||||
}
|
||||
}
|
||||
|
||||
public static void packWithPadding(final short[] values, final long[] out, final int bitsPerEntry) {
|
||||
final long maxEntryValue = (1L << bitsPerEntry) - 1;
|
||||
long cell = 0L;
|
||||
int bitIndex = 0;
|
||||
int outIndex = 0;
|
||||
for (final short value : values) {
|
||||
cell |= (value & maxEntryValue) << bitIndex;
|
||||
bitIndex += bitsPerEntry;
|
||||
if (bitIndex + bitsPerEntry > 64) {
|
||||
out[outIndex++] = cell;
|
||||
cell = 0L;
|
||||
bitIndex = 0;
|
||||
}
|
||||
}
|
||||
if (bitIndex != 0) {
|
||||
out[outIndex] = cell;
|
||||
}
|
||||
}
|
||||
|
||||
public static void unpackWithPadding(final long[] data, final byte[] out, final int bitsPerEntry) {
|
||||
final long maxEntryValue = (1L << bitsPerEntry) - 1;
|
||||
final int valuesPerLong = 64 / bitsPerEntry;
|
||||
long cell = 0L;
|
||||
int cellIndex = 0;
|
||||
int remaining = 0;
|
||||
for (int i = 0; i < out.length; i++) {
|
||||
if (remaining == 0) {
|
||||
cell = data[cellIndex++];
|
||||
remaining = valuesPerLong;
|
||||
}
|
||||
out[i] = (byte) (cell & maxEntryValue);
|
||||
cell >>>= bitsPerEntry;
|
||||
remaining--;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
|
||||
public static long[] createCompactArray(int bitsPerEntry, int entries, IntToLongFunction valueGetter) {
|
||||
long maxEntryValue = (1L << bitsPerEntry) - 1;
|
||||
long[] data = new long[(int) Math.ceil(entries * bitsPerEntry / 64.0)];
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
|
||||
* Copyright (C) 2016-2026 ViaVersion and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.viaversion.viaversion.util;
|
||||
|
||||
final class CustomKey extends Key {
|
||||
|
||||
private final String original;
|
||||
private final String namespace;
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Non-Minecraft key.
|
||||
*
|
||||
* @param original full key including the ':' separator
|
||||
* @param namespace non-Minecraft namespace
|
||||
* @param path path
|
||||
*/
|
||||
CustomKey(final String original, final String namespace, final String path) {
|
||||
if (!MinecraftKey.isValidNamespace(namespace)) {
|
||||
throw new IllegalArgumentException("Invalid namespace: " + namespace);
|
||||
}
|
||||
if (!MinecraftKey.isValidPath(path)) {
|
||||
throw new IllegalArgumentException("Invalid path: " + path);
|
||||
}
|
||||
this.original = original;
|
||||
this.namespace = namespace;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String namespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String original() {
|
||||
return original;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String minimized() {
|
||||
return this.original;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMinecraftNamespace() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof final CustomKey key)) return false;
|
||||
return this.original.equals(key.original);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.original.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.original;
|
||||
}
|
||||
}
|
||||
@@ -24,25 +24,17 @@ package com.viaversion.viaversion.util;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public final class Key {
|
||||
import static com.viaversion.viaversion.util.MinecraftKey.MINECRAFT_NAMESPACE;
|
||||
import static com.viaversion.viaversion.util.MinecraftKey.MINECRAFT_NAMESPACE_LENGTH;
|
||||
import static com.viaversion.viaversion.util.MinecraftKey.MINECRAFT_NAMESPACE_WITH_COLON;
|
||||
|
||||
private static final String MINECRAFT_NAMESPACE = "minecraft";
|
||||
private static final int MINECRAFT_NAMESPACE_LENGTH = MINECRAFT_NAMESPACE.length();
|
||||
private final String original;
|
||||
private final String namespace;
|
||||
private final String path;
|
||||
|
||||
private Key(final String original, final String namespace, final String path) {
|
||||
if (!isValidNamespace(namespace)) {
|
||||
throw new IllegalArgumentException("Invalid namespace: " + namespace);
|
||||
}
|
||||
if (!isValidPath(path)) {
|
||||
throw new IllegalArgumentException("Invalid path: " + path);
|
||||
}
|
||||
this.original = original; // assume this is correct; also saves whether the namespace was explitly set
|
||||
this.namespace = namespace;
|
||||
this.path = path;
|
||||
}
|
||||
/**
|
||||
* Represents a Minecraft Identifier/ResourceLocation.
|
||||
* <p>
|
||||
* Internally split into {@link CustomKey} and {@link MinecraftKey} to reduce data held
|
||||
* and keep the runtime/allocation cost of {@link #original()} and {@link #toString()} minimal.
|
||||
*/
|
||||
public abstract sealed class Key permits MinecraftKey, CustomKey {
|
||||
|
||||
/**
|
||||
* Creates a new key with the given namespace and path.
|
||||
@@ -52,7 +44,10 @@ public final class Key {
|
||||
* @return a new key with the given namespace and path
|
||||
*/
|
||||
public static Key of(final String namespace, final String path) {
|
||||
return new Key(namespace + ':' + path, namespace, path);
|
||||
if (namespace.equals(MINECRAFT_NAMESPACE) || namespace.isEmpty()) {
|
||||
return ofPath(path); // ends up compacting it
|
||||
}
|
||||
return new CustomKey(namespace + ':' + path, namespace, path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +57,7 @@ public final class Key {
|
||||
* @return a new key with the given path and the default namespace
|
||||
*/
|
||||
public static Key ofPath(final String path) {
|
||||
return new Key(path, MINECRAFT_NAMESPACE, path);
|
||||
return new MinecraftKey.CompactMinecraftKey(path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,9 +72,17 @@ public final class Key {
|
||||
return ofPath(identifier);
|
||||
}
|
||||
|
||||
final String namespace = separatorIndex == 0 ? MINECRAFT_NAMESPACE : identifier.substring(0, separatorIndex);
|
||||
final String path = identifier.substring(separatorIndex + 1);
|
||||
return new Key(identifier, namespace, path);
|
||||
if (separatorIndex == 0) {
|
||||
return new MinecraftKey.ColonPrefixedMinecraftKey(identifier, path);
|
||||
}
|
||||
|
||||
final String namespace = identifier.substring(0, separatorIndex);
|
||||
if (namespace.equals(MINECRAFT_NAMESPACE)) {
|
||||
return new MinecraftKey.FullMinecraftKey(identifier, path);
|
||||
}
|
||||
|
||||
return new CustomKey(identifier, namespace, path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,6 +99,38 @@ public final class Key {
|
||||
}
|
||||
}
|
||||
|
||||
public abstract String namespace();
|
||||
|
||||
public abstract String path();
|
||||
|
||||
/**
|
||||
* Returns the unmodified original identifier, possbily without an explicit namespace.
|
||||
*
|
||||
* @return the original identifier, possibly without an explicit namespace
|
||||
*/
|
||||
public abstract String original();
|
||||
|
||||
/**
|
||||
* Returns the identifier in a minimized form. If the namespace is "minecraft", it will return just the path.
|
||||
*
|
||||
* @return the identifier in a minimized form
|
||||
*/
|
||||
public abstract String minimized();
|
||||
|
||||
public abstract boolean hasMinecraftNamespace();
|
||||
|
||||
public Key withNamespace(final String namespace) {
|
||||
return of(namespace, this.path());
|
||||
}
|
||||
|
||||
public Key withPath(final String path) {
|
||||
return of(this.namespace(), path);
|
||||
}
|
||||
|
||||
public final boolean equals(final String identifier) {
|
||||
return this.equals(Key.of(identifier));
|
||||
}
|
||||
|
||||
public static String stripNamespace(final String identifier) {
|
||||
int index = identifier.indexOf(':');
|
||||
if (index == -1) {
|
||||
@@ -106,16 +141,14 @@ public final class Key {
|
||||
|
||||
public static String namespace(final String identifier) {
|
||||
final int index = identifier.indexOf(':');
|
||||
if (index == -1) {
|
||||
return MINECRAFT_NAMESPACE;
|
||||
} else if (index == 0) {
|
||||
if (index == -1 || index == 0) {
|
||||
return MINECRAFT_NAMESPACE;
|
||||
}
|
||||
return identifier.substring(0, index);
|
||||
}
|
||||
|
||||
public static String stripMinecraftNamespace(final String identifier) {
|
||||
if (identifier.startsWith("minecraft:")) {
|
||||
if (identifier.startsWith(MINECRAFT_NAMESPACE_WITH_COLON)) {
|
||||
return identifier.substring(MINECRAFT_NAMESPACE_LENGTH + 1);
|
||||
} else if (!identifier.isEmpty() && identifier.charAt(0) == ':') {
|
||||
return identifier.substring(1);
|
||||
@@ -130,7 +163,7 @@ public final class Key {
|
||||
public static String namespaced(final String identifier) {
|
||||
final int index = identifier.indexOf(':');
|
||||
if (index == -1) {
|
||||
return "minecraft:" + identifier;
|
||||
return MINECRAFT_NAMESPACE_WITH_COLON + identifier;
|
||||
} else if (index == 0) {
|
||||
return MINECRAFT_NAMESPACE + identifier;
|
||||
}
|
||||
@@ -140,98 +173,13 @@ public final class Key {
|
||||
public static boolean isValid(final String identifier) {
|
||||
final int separatorIndex = identifier.indexOf(':');
|
||||
if (separatorIndex == -1) {
|
||||
return isValidPath(identifier);
|
||||
return MinecraftKey.isValidPath(identifier);
|
||||
} else if (separatorIndex == 0) {
|
||||
return isValidPath(identifier.substring(1));
|
||||
return MinecraftKey.isValidPath(identifier.substring(1));
|
||||
}
|
||||
|
||||
final String namespace = identifier.substring(0, separatorIndex);
|
||||
final String path = identifier.substring(separatorIndex + 1);
|
||||
return isValidNamespace(namespace) && isValidPath(path);
|
||||
}
|
||||
|
||||
public String namespace() {
|
||||
return this.namespace;
|
||||
}
|
||||
|
||||
public String path() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unmodified original identifier, possbily without an explicit namespace.
|
||||
*
|
||||
* @return the original identifier, possibly without an explicit namespace
|
||||
*/
|
||||
public String original() {
|
||||
return this.original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier in a minimized form. If the namespace is "minecraft", it will return just the path.
|
||||
*
|
||||
* @return the identifier in a minimized form
|
||||
*/
|
||||
public String minimized() {
|
||||
return this.hasMinecraftNamespace() ? this.path : this.toString();
|
||||
}
|
||||
|
||||
public boolean hasMinecraftNamespace() {
|
||||
return this.namespace.equals(MINECRAFT_NAMESPACE);
|
||||
}
|
||||
|
||||
public Key withNamespace(final String namespace) {
|
||||
return of(namespace, this.path);
|
||||
}
|
||||
|
||||
public Key withPath(final String path) {
|
||||
return of(this.namespace, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof final Key key)) return false;
|
||||
return this.namespace.equals(key.namespace) && this.path.equals(key.path);
|
||||
}
|
||||
|
||||
public final boolean equals(final String identifier) {
|
||||
return this.equals(Key.of(identifier));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.namespace.hashCode();
|
||||
result = 31 * result + this.path.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.namespace + ':' + this.path;
|
||||
}
|
||||
|
||||
private static boolean isValidNamespace(final String namespace) {
|
||||
//noinspection StringEquality - a quick way out
|
||||
if (namespace == MINECRAFT_NAMESPACE) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0, length = namespace.length(); i < length; i++) {
|
||||
final char c = namespace.charAt(i);
|
||||
if (!(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '_' || c == '-' || c == '.')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isValidPath(final String path) {
|
||||
for (int i = 0, length = path.length(); i < length; i++) {
|
||||
final char c = path.charAt(i);
|
||||
if (!(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '_' || c == '-' || c == '.' || c == '/')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return MinecraftKey.isValidNamespace(namespace) && MinecraftKey.isValidPath(path);
|
||||
}
|
||||
}
|
||||
@@ -26,25 +26,25 @@ import com.viaversion.nbt.tag.ListTag;
|
||||
import com.viaversion.nbt.tag.StringTag;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import java.util.Collection;
|
||||
import java.util.Arrays;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public final class KeyMappings {
|
||||
|
||||
private final Object2IntMap<String> keyToId;
|
||||
private final String[] keys;
|
||||
private final Key[] keys;
|
||||
|
||||
public KeyMappings(final String... keys) {
|
||||
public KeyMappings(final Key... keys) {
|
||||
this.keys = keys;
|
||||
keyToId = new Object2IntOpenHashMap<>(keys.length);
|
||||
keyToId.defaultReturnValue(-1);
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
keyToId.put(keys[i], i);
|
||||
keyToId.put(keys[i].minimized(), i);
|
||||
}
|
||||
}
|
||||
|
||||
public KeyMappings(final Collection<String> keys) {
|
||||
this(keys.toArray(new String[0]));
|
||||
public KeyMappings(final String... keys) {
|
||||
this(Arrays.stream(keys).map(Key::of).toArray(Key[]::new));
|
||||
}
|
||||
|
||||
public KeyMappings(final ListTag<StringTag> keys) {
|
||||
@@ -52,6 +52,13 @@ public final class KeyMappings {
|
||||
}
|
||||
|
||||
public @Nullable String idToKey(final int id) {
|
||||
if (id < 0 || id >= keys.length) {
|
||||
return null;
|
||||
}
|
||||
return keys[id].minimized();
|
||||
}
|
||||
|
||||
public @Nullable Key key(final int id) {
|
||||
if (id < 0 || id >= keys.length) {
|
||||
return null;
|
||||
}
|
||||
@@ -62,7 +69,7 @@ public final class KeyMappings {
|
||||
return keyToId.getInt(Key.stripMinecraftNamespace(identifier));
|
||||
}
|
||||
|
||||
public String[] keys() {
|
||||
public Key[] keys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
|
||||
* Copyright (C) 2016-2026 ViaVersion and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.viaversion.viaversion.util;
|
||||
|
||||
|
||||
abstract sealed class MinecraftKey extends Key {
|
||||
|
||||
static final String MINECRAFT_NAMESPACE = "minecraft";
|
||||
static final String MINECRAFT_NAMESPACE_WITH_COLON = "minecraft:";
|
||||
static final int MINECRAFT_NAMESPACE_LENGTH = MINECRAFT_NAMESPACE.length();
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Minecraft key with a custom path.
|
||||
*
|
||||
* @param path path
|
||||
*/
|
||||
protected MinecraftKey(final String path) {
|
||||
if (!isValidPath(path)) {
|
||||
throw new IllegalArgumentException("Invalid path: " + path);
|
||||
}
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String namespace() {
|
||||
return MINECRAFT_NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String minimized() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMinecraftNamespace() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof final MinecraftKey key)) return false;
|
||||
return this.path.equals(key.path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return path.hashCode();
|
||||
}
|
||||
|
||||
static boolean isValidNamespace(final String namespace) {
|
||||
for (int i = 0, length = namespace.length(); i < length; i++) {
|
||||
final char c = namespace.charAt(i);
|
||||
if (!(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '_' || c == '-' || c == '.')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean isValidPath(final String path) {
|
||||
// Effectively the same as !(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '_' || c == '-' || c == '.' || c == '/')
|
||||
// but with less branches, making use of char being unsigned and the fact the separation chars are right after the numbers
|
||||
for (int i = 0, length = path.length(); i < length; i++) {
|
||||
final char c = path.charAt(i);
|
||||
if ((char) (c - 'a') > 25 // not a-z
|
||||
&& (char) (c - '-') > 12 // not - . / 0-9
|
||||
&& c != '_') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact Minecraft key, e.g. {@code "dirt"}
|
||||
*/
|
||||
static final class CompactMinecraftKey extends MinecraftKey {
|
||||
|
||||
private String identifier;
|
||||
|
||||
CompactMinecraftKey(final String path) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String original() {
|
||||
return path();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (identifier == null) {
|
||||
identifier = MINECRAFT_NAMESPACE_WITH_COLON + this.path();
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Colon-prefixed Minecraft keys, e.g. {@code ":dirt"}
|
||||
*/
|
||||
static final class ColonPrefixedMinecraftKey extends MinecraftKey {
|
||||
|
||||
private final String original;
|
||||
private String identifier;
|
||||
|
||||
ColonPrefixedMinecraftKey(final String original, final String path) {
|
||||
super(path);
|
||||
this.original = original;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String original() {
|
||||
return original;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (identifier == null) {
|
||||
identifier = MINECRAFT_NAMESPACE_WITH_COLON + this.path();
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Full Minecraft keys, e.g. {@code "minecraft:dirt"}
|
||||
*/
|
||||
static final class FullMinecraftKey extends MinecraftKey {
|
||||
|
||||
private final String identifier;
|
||||
|
||||
FullMinecraftKey(final String identifier, final String path) {
|
||||
super(path);
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String original() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
package com.viaversion.viaversion.util;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public final class UUIDUtil {
|
||||
@@ -49,4 +50,20 @@ public final class UUIDUtil {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random UUID, similar to {@link UUID#randomUUID()}, but without using {@link java.security.SecureRandom} and its synchronization.
|
||||
* <p>
|
||||
* Should not be used where a UUID should be unpredictable.
|
||||
*
|
||||
* @return random UUID
|
||||
*/
|
||||
public static UUID randomUUID() {
|
||||
final ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
long most = random.nextLong();
|
||||
long least = random.nextLong();
|
||||
most = (most & 0xffffffffffff0fffL) | 0x0000000000004000L; // version 4
|
||||
least = (least & 0x3fffffffffffffffL) | 0x8000000000000000L; // IETF variant
|
||||
return new UUID(most, least);
|
||||
}
|
||||
}
|
||||
@@ -245,7 +245,8 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaPlatform<Player>
|
||||
final String className = Bukkit.getServer().getClass().getName();
|
||||
final String craftBukkitPackage = className.substring(0, className.lastIndexOf('.'));
|
||||
Class.forName(craftBukkitPackage + ".entity.CraftPlayer").getMethod("addChannel", String.class).invoke(player, channel);
|
||||
} catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) {
|
||||
} catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException |
|
||||
ClassNotFoundException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to register custom payload channel " + channel + " for player " + player.getName(), e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,8 +51,6 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ViaManagerImpl implements ViaManager {
|
||||
private final ProtocolManagerImpl protocolManager = new ProtocolManagerImpl();
|
||||
@@ -245,28 +243,13 @@ public class ViaManagerImpl implements ViaManager {
|
||||
platform.getLogger().info("ViaVersion has been disabled; uninjected the platform and shut down the scheduler.");
|
||||
}
|
||||
|
||||
private void checkJavaVersion() { // Stolen from Paper
|
||||
String javaVersion = System.getProperty("java.version");
|
||||
Matcher matcher = Pattern.compile("(?:1\\.)?(\\d+)").matcher(javaVersion);
|
||||
if (!matcher.find()) {
|
||||
platform.getLogger().warning("Failed to determine Java version; could not parse: " + javaVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
String versionString = matcher.group(1);
|
||||
int version;
|
||||
try {
|
||||
version = Integer.parseInt(versionString);
|
||||
} catch (NumberFormatException e) {
|
||||
platform.getLogger().log(Level.WARNING, "Failed to determine Java version; could not parse: " + versionString, e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (version < 17) {
|
||||
private void checkJavaVersion() {
|
||||
int javaVersion = Runtime.version().feature();
|
||||
if (javaVersion < 17) {
|
||||
platform.getLogger().warning("You are running an outdated Java version, please update it to at least Java 21 (your version is " + javaVersion + ").");
|
||||
platform.getLogger().warning("ViaVersion no longer officially supports this version of Java, only offering unsupported compatibility builds.");
|
||||
platform.getLogger().warning("See https://github.com/ViaVersion/ViaVersion/releases/tag/5.0.0 for more information.");
|
||||
} else if (version < 21) {
|
||||
} else if (javaVersion < 21) {
|
||||
platform.getLogger().warning("Please update your Java runtime to at least Java 21 (your version is " + javaVersion + ").");
|
||||
platform.getLogger().warning("At some point in the future, ViaVersion will no longer be compatible with this version of Java.");
|
||||
}
|
||||
|
||||
+3
-1
@@ -17,7 +17,9 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.codec.hash;
|
||||
|
||||
final class CRC32C implements HashFunction {
|
||||
// Required so long as Java 8 downgraded VV jars are offered
|
||||
@Deprecated
|
||||
final class FallbackCRC32C implements HashFunction {
|
||||
|
||||
private static final int[] CRC32C_TABLE = new int[256];
|
||||
|
||||
@@ -20,7 +20,9 @@ package com.viaversion.viaversion.codec.hash;
|
||||
@FunctionalInterface
|
||||
public interface HashFunction {
|
||||
|
||||
HashFunction CRC32C = new CRC32C();
|
||||
static HashFunction crc32c() {
|
||||
return Runtime.version().feature() < 9 ? new FallbackCRC32C() : new JavaCRC32C();
|
||||
}
|
||||
|
||||
int hashBytes(byte[] data, int length);
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ public class HashOps extends OpsBase implements Hasher {
|
||||
|
||||
@Override
|
||||
public void writeString(final CharSequence sequence) {
|
||||
hashBuilder.preSize(sequence.length() + Byte.BYTES + Integer.BYTES)
|
||||
hashBuilder.preSize((sequence.length() * Character.BYTES) + Byte.BYTES + Integer.BYTES)
|
||||
.writeByte(TAG_STRING)
|
||||
.writeInt(sequence.length())
|
||||
.writeString(sequence);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
|
||||
* Copyright (C) 2016-2026 ViaVersion and 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.viaversion.viaversion.codec.hash;
|
||||
|
||||
import java.util.zip.CRC32C;
|
||||
|
||||
final class JavaCRC32C implements HashFunction {
|
||||
|
||||
private final CRC32C crc = new CRC32C();
|
||||
|
||||
@Override
|
||||
public int hashBytes(final byte[] data, final int length) {
|
||||
crc.reset();
|
||||
crc.update(data, 0, length);
|
||||
return (int) crc.getValue();
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,7 @@ public class AbstractViaConfig extends Config implements ViaVersionConfig {
|
||||
private String reloadDisconnectMessage;
|
||||
private boolean logOtherConversionErrors;
|
||||
private boolean logTextComponentConversionErrors;
|
||||
private boolean multiReddustColorFix;
|
||||
private boolean disable1_13TabComplete;
|
||||
private boolean teamColourFix;
|
||||
private boolean serversideBlockConnections;
|
||||
@@ -146,6 +147,7 @@ public class AbstractViaConfig extends Config implements ViaVersionConfig {
|
||||
blockedDisconnectMessage = getString("block-disconnect-msg", "You are using an unsupported Minecraft version!");
|
||||
reloadDisconnectMessage = getString("reload-disconnect-msg", "Server reload, please rejoin!");
|
||||
teamColourFix = getBoolean("team-colour-fix", true);
|
||||
multiReddustColorFix = getBoolean("multi-reddust-color-fix", false);
|
||||
disable1_13TabComplete = getBoolean("disable-1_13-auto-complete", false);
|
||||
serversideBlockConnections = getBoolean("serverside-blockconnections", true);
|
||||
reduceBlockStorageMemory = getBoolean("reduce-blockstorage-memory", false);
|
||||
@@ -475,6 +477,11 @@ public class AbstractViaConfig extends Config implements ViaVersionConfig {
|
||||
return reloadDisconnectMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMultiReddustColorFix() {
|
||||
return multiReddustColorFix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean is1_13TeamColourFix() {
|
||||
return teamColourFix;
|
||||
|
||||
+2
-1
@@ -35,6 +35,7 @@ public class ConnectionManagerImpl implements ConnectionManager {
|
||||
protected final Map<UUID, UserConnection> serverConnections = new ConcurrentHashMap<>();
|
||||
protected final Map<UUID, UserConnection> clientConnections = new ConcurrentHashMap<>();
|
||||
protected final Set<UserConnection> connections = ConcurrentHashMap.newKeySet();
|
||||
private final Set<UserConnection> connectionsView = Collections.unmodifiableSet(connections);
|
||||
|
||||
@Override
|
||||
public void onLoginSuccess(UserConnection connection) {
|
||||
@@ -120,7 +121,7 @@ public class ConnectionManagerImpl implements ConnectionManager {
|
||||
|
||||
@Override
|
||||
public Set<UserConnection> getConnections() {
|
||||
return Collections.unmodifiableSet(connections);
|
||||
return connectionsView;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.connection;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.connection.ProtocolInfo;
|
||||
import com.viaversion.viaversion.api.connection.StorableObject;
|
||||
@@ -43,30 +42,28 @@ import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.handler.codec.CodecException;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.SplittableRandom;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Function;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class UserConnectionImpl implements UserConnection {
|
||||
private static final int PASSTHROUGH_DATA_BYTES = Long.BYTES * 2 + 2;
|
||||
private static final int PASSTHROUGH_DATA_BYTES = Long.BYTES + 2;
|
||||
private static final AtomicLong IDS = new AtomicLong();
|
||||
private final long id = IDS.incrementAndGet();
|
||||
private final SplittableRandom random = new SplittableRandom();
|
||||
private final Map<Class<?>, StorableObject> storedObjects = new ConcurrentHashMap<>();
|
||||
private final Map<Class<? extends Protocol>, EntityTracker> entityTrackers = new Reference2ObjectOpenHashMap<>();
|
||||
private final Map<Class<? extends Protocol>, ItemHasher> itemHashers = new Reference2ObjectOpenHashMap<>();
|
||||
private final Map<Class<? extends Protocol>, ClientWorld> clientWorlds = new Reference2ObjectOpenHashMap<>();
|
||||
private final PacketTracker packetTracker = new PacketTracker(this);
|
||||
private final Set<UUID> passthroughTokens = Collections.newSetFromMap(CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(10, TimeUnit.SECONDS)
|
||||
.<UUID, Boolean>build().asMap());
|
||||
private final LongSet passthroughTokens = new LongOpenHashSet();
|
||||
private final ProtocolInfo protocolInfo = new ProtocolInfoImpl();
|
||||
private final Channel channel;
|
||||
private final boolean clientSide;
|
||||
@@ -261,7 +258,7 @@ public class UserConnectionImpl implements UserConnection {
|
||||
if (shouldTransformPacket()) {
|
||||
// Bypass serverbound packet decoder transforming
|
||||
Types.VAR_INT.writePrimitive(buf, PacketWrapper.PASSTHROUGH_ID);
|
||||
Types.UUID.write(buf, generatePassthroughToken());
|
||||
Types.LONG.writePrimitive(buf, generatePassthroughToken());
|
||||
}
|
||||
|
||||
buf.writeBytes(packet);
|
||||
@@ -346,11 +343,13 @@ public class UserConnectionImpl implements UserConnection {
|
||||
}
|
||||
|
||||
final int id = Types.VAR_INT.readPrimitive(buf);
|
||||
if (id == PacketWrapper.PASSTHROUGH_ID) {
|
||||
if (!passthroughTokens.remove(Types.UUID.read(buf))) {
|
||||
throw new IllegalArgumentException("Invalid token");
|
||||
if (id == PacketWrapper.PASSTHROUGH_ID && buf.readableBytes() >= Long.BYTES) {
|
||||
if (removePassthroughToken(buf.readLong())) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
|
||||
// Move reader back and treat it like a regular packet
|
||||
buf.readerIndex(buf.readerIndex() - Long.BYTES);
|
||||
}
|
||||
|
||||
final int valuesReaderIndex = buf.readerIndex();
|
||||
@@ -443,10 +442,22 @@ public class UserConnectionImpl implements UserConnection {
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID generatePassthroughToken() {
|
||||
UUID token = UUID.randomUUID();
|
||||
passthroughTokens.add(token);
|
||||
return token;
|
||||
public long generatePassthroughToken() {
|
||||
// Don't use TLR so it's not entirely predictable; use SplittableRandom over Random since it actually has a 64 bit seed
|
||||
while (true) {
|
||||
synchronized (passthroughTokens) {
|
||||
final long token = random.nextLong();
|
||||
if (passthroughTokens.add(token)) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean removePassthroughToken(final long token) {
|
||||
synchronized (passthroughTokens) {
|
||||
return passthroughTokens.remove(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,9 +23,10 @@ import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.data.entity.ClientEntityIdChangeListener;
|
||||
import com.viaversion.viaversion.api.data.entity.DimensionData;
|
||||
import com.viaversion.viaversion.api.data.entity.EntityTracker;
|
||||
import com.viaversion.viaversion.api.data.entity.StoredEntityData;
|
||||
import com.viaversion.viaversion.api.data.entity.TrackedEntity;
|
||||
import com.viaversion.viaversion.api.minecraft.chunks.Chunk;
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
|
||||
import com.viaversion.viaversion.api.type.Type;
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
import com.viaversion.viaversion.util.KeyMappings;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
@@ -48,6 +49,8 @@ public class EntityTrackerBase implements EntityTracker, ClientEntityIdChangeLis
|
||||
private int biomesSent = -1;
|
||||
private Map<String, DimensionData> dimensions = Collections.emptyMap();
|
||||
private boolean instaBuild;
|
||||
private Type<Chunk> chunkType;
|
||||
private Type<Chunk> mappedChunkType;
|
||||
|
||||
public EntityTrackerBase(UserConnection connection, @Nullable EntityType playerType) {
|
||||
this.connection = connection;
|
||||
@@ -60,8 +63,10 @@ public class EntityTrackerBase implements EntityTracker, ClientEntityIdChangeLis
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntity(int id, EntityType type) {
|
||||
entities.put(id, new TrackedEntityImpl(type));
|
||||
public TrackedEntity addEntity(int id, EntityType type) {
|
||||
final TrackedEntityImpl entity = new TrackedEntityImpl(type);
|
||||
entities.put(id, entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,20 +86,8 @@ public class EntityTrackerBase implements EntityTracker, ClientEntityIdChangeLis
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable StoredEntityData entityData(int id) {
|
||||
final TrackedEntity entity = entities.get(id);
|
||||
return entity != null ? entity.data() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable StoredEntityData entityDataIfPresent(int id) {
|
||||
final TrackedEntity entity = entities.get(id);
|
||||
return entity != null && entity.hasData() ? entity.data() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntity(int id) {
|
||||
entities.remove(id);
|
||||
public TrackedEntity removeEntity(int id) {
|
||||
return entities.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,6 +157,10 @@ public class EntityTrackerBase implements EntityTracker, ClientEntityIdChangeLis
|
||||
|
||||
@Override
|
||||
public void setCurrentWorldSectionHeight(int currentWorldSectionHeight) {
|
||||
if (this.currentWorldSectionHeight != currentWorldSectionHeight) {
|
||||
this.chunkType = null;
|
||||
this.mappedChunkType = null;
|
||||
}
|
||||
this.currentWorldSectionHeight = currentWorldSectionHeight;
|
||||
}
|
||||
|
||||
@@ -204,9 +201,27 @@ public class EntityTrackerBase implements EntityTracker, ClientEntityIdChangeLis
|
||||
|
||||
@Override
|
||||
public void setBiomesSent(int biomesSent) {
|
||||
if (this.biomesSent != biomesSent) {
|
||||
this.chunkType = null;
|
||||
this.mappedChunkType = null;
|
||||
}
|
||||
this.biomesSent = biomesSent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Type<Chunk> chunkType(final boolean mapped) {
|
||||
return mapped ? mappedChunkType : chunkType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChunkType(final boolean mapped, @Nullable final Type<Chunk> chunkType) {
|
||||
if (mapped) {
|
||||
this.mappedChunkType = chunkType;
|
||||
} else {
|
||||
this.chunkType = chunkType;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityType playerType() {
|
||||
return playerType;
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
|
||||
* Copyright (C) 2016-2026 ViaVersion and 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.viaversion.viaversion.data.entity;
|
||||
|
||||
import com.viaversion.viaversion.api.data.entity.StoredEntityData;
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public final class StoredEntityDataImpl implements StoredEntityData {
|
||||
private final Map<Class<?>, Object> storedObjects = new HashMap<>();
|
||||
private final EntityType type;
|
||||
|
||||
public StoredEntityDataImpl(EntityType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityType type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable <T> T get(Class<T> objectClass) {
|
||||
//noinspection unchecked
|
||||
return (T) storedObjects.get(objectClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @Nullable T remove(Class<T> objectClass) {
|
||||
//noinspection unchecked
|
||||
return (T) storedObjects.remove(objectClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean has(Class<?> objectClass) {
|
||||
return storedObjects.containsKey(objectClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(Object object) {
|
||||
storedObjects.put(object.getClass(), object);
|
||||
}
|
||||
}
|
||||
@@ -17,13 +17,16 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.data.entity;
|
||||
|
||||
import com.viaversion.viaversion.api.data.entity.StoredEntityData;
|
||||
import com.viaversion.viaversion.api.connection.StorableObject;
|
||||
import com.viaversion.viaversion.api.data.entity.TrackedEntity;
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
|
||||
import java.util.Map;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public final class TrackedEntityImpl implements TrackedEntity {
|
||||
private final EntityType entityType;
|
||||
private StoredEntityData data;
|
||||
private Map<Class<?>, StorableObject> storedObjects;
|
||||
private boolean sentEntityData;
|
||||
|
||||
public TrackedEntityImpl(final EntityType entityType) {
|
||||
@@ -36,16 +39,32 @@ public final class TrackedEntityImpl implements TrackedEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredEntityData data() {
|
||||
if (data == null) {
|
||||
data = new StoredEntityDataImpl(entityType);
|
||||
}
|
||||
return data;
|
||||
public boolean has(final Class<? extends StorableObject> objectClass) {
|
||||
return storedObjects != null && storedObjects.containsKey(objectClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasData() {
|
||||
return data != null;
|
||||
public @Nullable <T extends StorableObject> T get(final Class<T> objectClass) {
|
||||
//noinspection unchecked
|
||||
return storedObjects != null ? (T) storedObjects.get(objectClass) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(final StorableObject object) {
|
||||
storedObjects().put(object.getClass(), object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends StorableObject> @Nullable T remove(final Class<T> objectClass) {
|
||||
//noinspection unchecked
|
||||
return storedObjects != null ? (T) storedObjects.remove(objectClass) : null;
|
||||
}
|
||||
|
||||
private Map<Class<?>, StorableObject> storedObjects() {
|
||||
if (storedObjects == null) {
|
||||
storedObjects = new Reference2ObjectArrayMap<>(); // Only ever contains 1-2 entries
|
||||
}
|
||||
return storedObjects;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,7 +81,7 @@ public final class TrackedEntityImpl implements TrackedEntity {
|
||||
public String toString() {
|
||||
return "TrackedEntityImpl{" +
|
||||
"entityType=" + entityType +
|
||||
", data=" + data +
|
||||
", storedObjects=" + storedObjects +
|
||||
", sentEntityData=" + sentEntityData +
|
||||
'}';
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public class ItemHasherBase implements ItemHasher {
|
||||
* @return the hashed item
|
||||
*/
|
||||
public HashedItem toHashedItem(final Item item, final boolean mapped) {
|
||||
return toHashedItem(new HashOps(mapped ? mappedContext : context, HashFunction.CRC32C), item);
|
||||
return toHashedItem(new HashOps(mapped ? mappedContext : context, HashFunction.crc32c()), item);
|
||||
}
|
||||
|
||||
public static HashedItem toHashedItem(final Hasher hasher, final Item item) {
|
||||
@@ -137,7 +137,7 @@ public class ItemHasherBase implements ItemHasher {
|
||||
}
|
||||
|
||||
private int hashTag(final CompoundTag tag) {
|
||||
final HashOps hasher = new HashOps(context, HashFunction.CRC32C);
|
||||
final HashOps hasher = new HashOps(context, HashFunction.crc32c());
|
||||
Types.TAG.write(hasher, tag);
|
||||
return hasher.hash();
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_8to1_9.Protocol1_8To1_9;
|
||||
import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ClientboundPackets1_9;
|
||||
import com.viaversion.viaversion.util.ComponentUtil;
|
||||
import com.viaversion.viaversion.util.UUIDUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
@@ -54,7 +55,7 @@ public class CommonBoss implements BossBar {
|
||||
Preconditions.checkNotNull(title, "Title cannot be null");
|
||||
Preconditions.checkArgument((health >= 0 && health <= 1), "Health must be between 0 and 1. Input: " + health);
|
||||
|
||||
this.uuid = UUID.randomUUID();
|
||||
this.uuid = UUIDUtil.randomUUID();
|
||||
this.title = title;
|
||||
this.health = health;
|
||||
this.color = color == null ? BossColor.PURPLE : color;
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.viaversion.viaversion.api.platform.ViaChannelHandler;
|
||||
import com.viaversion.viaversion.exception.CancelCodecException;
|
||||
import com.viaversion.viaversion.exception.CancelDecoderException;
|
||||
import com.viaversion.viaversion.exception.CancelEncoderException;
|
||||
import com.viaversion.viaversion.util.ByteBufUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
@@ -57,7 +58,7 @@ public class ViaCodecHandler extends ByteToMessageCodec<ByteBuf> implements ViaC
|
||||
throw CancelDecoderException.generate(null);
|
||||
}
|
||||
|
||||
final ByteBuf transformedBuf = ctx.alloc().buffer().writeBytes(in);
|
||||
final ByteBuf transformedBuf = ByteBufUtil.copy(ctx.alloc(), in);
|
||||
try {
|
||||
if (this.connection.shouldTransformPacket()) {
|
||||
this.connection.transformIncoming(transformedBuf, CancelDecoderException::generate);
|
||||
|
||||
@@ -94,15 +94,15 @@ import com.viaversion.viaversion.protocols.v26_1to26_2.Protocol26_1To26_2;
|
||||
import com.viaversion.viaversion.util.MathUtil;
|
||||
import com.viaversion.viaversion.util.Pair;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectSortedMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -323,17 +323,13 @@ public class ProtocolManagerImpl implements ProtocolManager {
|
||||
}
|
||||
|
||||
// Calculate path
|
||||
Object2ObjectSortedMap<ProtocolVersion, Protocol> outputPath = getProtocolPath(new Object2ObjectLinkedOpenHashMap<>(), clientVersion, serverVersion);
|
||||
if (outputPath == null) {
|
||||
List<ProtocolPathEntry> path = calculateProtocolPath(clientVersion, serverVersion);
|
||||
if (path == null) {
|
||||
// Also cache that there is no path
|
||||
pathCache.put(protocolKey, List.of());
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ProtocolPathEntry> path = new ArrayList<>(outputPath.size());
|
||||
for (Map.Entry<ProtocolVersion, Protocol> entry : outputPath.entrySet()) {
|
||||
path.add(new ProtocolPathEntryImpl(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
pathCache.put(protocolKey, path);
|
||||
return path;
|
||||
}
|
||||
@@ -349,56 +345,70 @@ public class ProtocolManagerImpl implements ProtocolManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a path to get from an input protocol to the server's protocol.
|
||||
* Calculates the shortest path to get from an input protocol to the server's protocol using a breadth-first search.
|
||||
*
|
||||
* @param current current items in the path
|
||||
* @param clientVersion current input version
|
||||
* @param clientVersion input version
|
||||
* @param serverVersion desired output version
|
||||
* @return path that has been generated, null if failed
|
||||
* @return shortest path to the server version, or null if none exists
|
||||
*/
|
||||
private @Nullable Object2ObjectSortedMap<ProtocolVersion, Protocol> getProtocolPath(Object2ObjectSortedMap<ProtocolVersion, Protocol> current, ProtocolVersion clientVersion, ProtocolVersion serverVersion) {
|
||||
if (current.size() > maxProtocolPathSize) return null; // Fail-safe, protocol too complicated.
|
||||
private @Nullable List<ProtocolPathEntry> calculateProtocolPath(ProtocolVersion clientVersion, ProtocolVersion serverVersion) {
|
||||
Set<ProtocolVersion> visited = new HashSet<>();
|
||||
visited.add(clientVersion);
|
||||
|
||||
// First, check if there is any protocols for this
|
||||
Object2ObjectMap<ProtocolVersion, Protocol> toServerProtocolMap = registryMap.get(clientVersion);
|
||||
if (toServerProtocolMap == null) {
|
||||
return null; // Not supported
|
||||
}
|
||||
Deque<PathNode> queue = new ArrayDeque<>();
|
||||
queue.add(new PathNode(null, clientVersion, null, 0));
|
||||
|
||||
// Next, check if there is a direct, single Protocol path
|
||||
Protocol protocol = toServerProtocolMap.get(serverVersion);
|
||||
if (protocol != null) {
|
||||
current.put(serverVersion, protocol);
|
||||
return current; // Easy solution
|
||||
}
|
||||
PathNode node;
|
||||
while ((node = queue.poll()) != null) {
|
||||
if (node.depth() > maxProtocolPathSize){
|
||||
continue; // Fail-safe, protocol too complicated
|
||||
}
|
||||
|
||||
// There might be a more advanced solution... So we'll see if any of the others can get us there
|
||||
Object2ObjectSortedMap<ProtocolVersion, Protocol> shortest = null;
|
||||
for (Map.Entry<ProtocolVersion, Protocol> entry : toServerProtocolMap.entrySet()) {
|
||||
// Ensure we don't go back to already contained versions
|
||||
ProtocolVersion translatedToVersion = entry.getKey();
|
||||
if (current.containsKey(translatedToVersion)) continue;
|
||||
Object2ObjectMap<ProtocolVersion, Protocol> toServerProtocolMap = registryMap.get(node.version());
|
||||
if (toServerProtocolMap == null) {
|
||||
continue; // Not supported
|
||||
}
|
||||
|
||||
// Check if the new version is farther away than the current client version
|
||||
if (maxPathDeltaIncrease != -1 && translatedToVersion.getVersionType() == clientVersion.getVersionType()) {
|
||||
final int delta = Math.abs(serverVersion.getVersion() - translatedToVersion.getVersion()) - Math.abs(serverVersion.getVersion() - clientVersion.getVersion());
|
||||
if (delta > maxPathDeltaIncrease) {
|
||||
// Check if there is a direct protocol path; always preferred over paths through other versions,
|
||||
// even if it first moves farther away from the server version
|
||||
Protocol protocol = toServerProtocolMap.get(serverVersion);
|
||||
if (protocol != null) {
|
||||
return reconstructPath(new PathNode(node, serverVersion, protocol, node.depth() + 1));
|
||||
}
|
||||
|
||||
for (Map.Entry<ProtocolVersion, Protocol> entry : toServerProtocolMap.entrySet()) {
|
||||
// Ensure we don't go back to already visited versions
|
||||
ProtocolVersion translatedToVersion = entry.getKey();
|
||||
if (visited.contains(translatedToVersion)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a copy
|
||||
Object2ObjectSortedMap<ProtocolVersion, Protocol> newCurrent = new Object2ObjectLinkedOpenHashMap<>(current);
|
||||
newCurrent.put(translatedToVersion, entry.getValue());
|
||||
// Check if the new version is farther away than the current client version
|
||||
if (maxPathDeltaIncrease != -1 && translatedToVersion.getVersionType() == node.version().getVersionType()) {
|
||||
final int delta = Math.abs(serverVersion.getVersion() - translatedToVersion.getVersion()) - Math.abs(serverVersion.getVersion() - node.version().getVersion());
|
||||
if (delta > maxPathDeltaIncrease) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the rest of the protocol starting from translatedToVersion and take the shortest
|
||||
newCurrent = getProtocolPath(newCurrent, translatedToVersion, serverVersion);
|
||||
if (newCurrent != null && (shortest == null || newCurrent.size() < shortest.size())) {
|
||||
shortest = newCurrent;
|
||||
// Go on to the next node
|
||||
visited.add(translatedToVersion);
|
||||
queue.add(new PathNode(node, translatedToVersion, entry.getValue(), node.depth() + 1));
|
||||
}
|
||||
}
|
||||
return null; // No path found
|
||||
}
|
||||
|
||||
return shortest; // null if none found
|
||||
private List<ProtocolPathEntry> reconstructPath(PathNode node) {
|
||||
// Go back the full path again to build the final list
|
||||
final ProtocolPathEntry[] path = new ProtocolPathEntry[node.depth()];
|
||||
for (PathNode current = node; current.protocol() != null; current = current.parent()) {
|
||||
path[current.depth() - 1] = new ProtocolPathEntryImpl(current.version(), current.protocol());
|
||||
}
|
||||
return List.of(path);
|
||||
}
|
||||
|
||||
private record PathNode(@Nullable PathNode parent, ProtocolVersion version, @Nullable Protocol protocol, int depth) {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ public final class SharedRegistrations {
|
||||
}
|
||||
|
||||
for (final VersionedTemplateGroup group : groups) {
|
||||
if (group.min().newerThan(version)) { // TODO binary search?
|
||||
if (group.min().newerThan(version)) {
|
||||
// Sorted by the minimum, so we can stop early if we're out
|
||||
break;
|
||||
}
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ final class BlockRegistrations {
|
||||
ctx.clientbound(ClientboundPackets1_13.CHUNK_BLOCKS_UPDATE, br::registerChunkBlocksUpdate);
|
||||
}
|
||||
|
||||
private static <CU extends ClientboundPacketType> void common1_13(RegistrationContext<CU, ?> ctx, BlockRewriter<CU> br) {
|
||||
private static <CU extends ClientboundPacketType> void common1_13(final RegistrationContext<CU, ?> ctx, final BlockRewriter<CU> br) {
|
||||
ctx.clientbound(ClientboundPackets1_13.BLOCK_EVENT, br::registerBlockEvent);
|
||||
ctx.clientbound(ClientboundPackets1_13.BLOCK_UPDATE, br::registerBlockUpdate);
|
||||
ctx.clientbound(ClientboundPackets1_13.LEVEL_EVENT, br::registerLevelEvent1_13);
|
||||
@@ -70,7 +70,7 @@ final class BlockRegistrations {
|
||||
ctx.clientbound(ClientboundPackets1_19.SECTION_BLOCKS_UPDATE, br::registerSectionBlocksUpdate);
|
||||
}
|
||||
|
||||
private static <CU extends ClientboundPacketType> void common1_19(RegistrationContext<CU, ?> ctx, BlockRewriter<CU> br) {
|
||||
private static <CU extends ClientboundPacketType> void common1_19(final RegistrationContext<CU, ?> ctx, final BlockRewriter<CU> br) {
|
||||
ctx.clientbound(ClientboundPackets1_19.BLOCK_EVENT, br::registerBlockEvent);
|
||||
ctx.clientbound(ClientboundPackets1_19.BLOCK_UPDATE, br::registerBlockUpdate);
|
||||
ctx.clientbound(ClientboundPackets1_19.LEVEL_CHUNK_WITH_LIGHT, br::registerLevelChunk1_18);
|
||||
|
||||
+4
-3
@@ -18,10 +18,12 @@
|
||||
package com.viaversion.viaversion.protocols.v1_10to1_11.storage;
|
||||
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.data.entity.TrackedEntity;
|
||||
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 org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class EntityTracker1_11 extends EntityTrackerBase {
|
||||
private final IntSet holograms = new IntOpenHashSet();
|
||||
@@ -31,10 +33,9 @@ public class EntityTracker1_11 extends EntityTrackerBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntity(int entityId) {
|
||||
super.removeEntity(entityId);
|
||||
|
||||
public @Nullable TrackedEntity removeEntity(int entityId) {
|
||||
removeHologram(entityId);
|
||||
return super.removeEntity(entityId);
|
||||
}
|
||||
|
||||
public boolean addHologram(int entId) {
|
||||
|
||||
+15
-31
@@ -17,12 +17,8 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.protocols.v1_12_2to1_13.blockconnections;
|
||||
|
||||
import com.viaversion.nbt.tag.ByteArrayTag;
|
||||
import com.viaversion.nbt.tag.CompoundTag;
|
||||
import com.viaversion.nbt.tag.IntArrayTag;
|
||||
import com.viaversion.nbt.tag.ListTag;
|
||||
import com.viaversion.nbt.tag.NumberTag;
|
||||
import com.viaversion.nbt.tag.StringTag;
|
||||
import com.viaversion.nbt.tag.Tag;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
@@ -35,12 +31,15 @@ import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection;
|
||||
import com.viaversion.viaversion.api.minecraft.chunks.DataPalette;
|
||||
import com.viaversion.viaversion.api.minecraft.chunks.PaletteType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_12_2to1_13.Protocol1_12_2To1_13;
|
||||
import com.viaversion.viaversion.protocols.v1_12_2to1_13.blockconnections.providers.BlockConnectionProvider;
|
||||
import com.viaversion.viaversion.protocols.v1_12_2to1_13.blockconnections.providers.PacketBlockConnectionProvider;
|
||||
import com.viaversion.viaversion.protocols.v1_12_2to1_13.blockconnections.providers.UserBlockData;
|
||||
import com.viaversion.viaversion.protocols.v1_12_2to1_13.data.BlockStates1_13;
|
||||
import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13;
|
||||
import com.viaversion.viaversion.api.data.IdRanges;
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
@@ -162,16 +161,14 @@ public final class ConnectionData {
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
if (!Via.getConfig().isServersideBlockConnections()) {
|
||||
final ProtocolVersion version = Via.getAPI().getServerVersion().lowestSupportedProtocolVersion();
|
||||
if (!Via.getConfig().isServersideBlockConnections() || (version.isKnown() && version.newerThan(ProtocolVersion.v1_12_2))) {
|
||||
return;
|
||||
}
|
||||
|
||||
Via.getPlatform().getLogger().info("Loading block connection mappings ...");
|
||||
ListTag<StringTag> blockStates = MappingDataLoader.INSTANCE.loadNBT("blockstates-1.13.nbt").getListTag("blockstates", StringTag.class);
|
||||
for (int id = 0; id < blockStates.size(); id++) {
|
||||
String key = blockStates.get(id).getValue();
|
||||
KEY_TO_ID.put(key, id);
|
||||
}
|
||||
CompoundTag blockStatesData = MappingDataLoader.INSTANCE.loadNBT("blockstates-1.13.nbt");
|
||||
BlockStates1_13.forEach(blockStatesData, KEY_TO_ID::put);
|
||||
|
||||
connectionHandlerMap = new Int2ObjectOpenHashMap<>(3650);
|
||||
|
||||
@@ -179,41 +176,28 @@ public final class ConnectionData {
|
||||
blockConnectionData = new Int2ObjectOpenHashMap<>(2048);
|
||||
|
||||
CompoundTag data = MappingDataLoader.INSTANCE.loadNBT("blockConnections.nbt");
|
||||
ListTag<CompoundTag> blockConnectionMappings = data.getListTag("data", CompoundTag.class);
|
||||
for (CompoundTag blockTag : blockConnectionMappings) {
|
||||
for (CompoundTag profileTag : data.getListTag("profiles", CompoundTag.class)) {
|
||||
BlockData blockData = new BlockData();
|
||||
for (Entry<String, Tag> entry : blockTag.entrySet()) {
|
||||
for (Entry<String, Tag> entry : profileTag.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (key.equals("id") || key.equals("ids")) {
|
||||
if (key.equals("ids")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
byte connections = ((NumberTag) entry.getValue()).asByte();
|
||||
boolean[] attachingFaces = new boolean[4];
|
||||
ByteArrayTag connections = (ByteArrayTag) entry.getValue();
|
||||
for (byte blockFaceId : connections.getValue()) {
|
||||
attachingFaces[blockFaceId] = true;
|
||||
for (int i = 0; i < attachingFaces.length; i++) {
|
||||
attachingFaces[i] = (connections & (1 << i)) != 0;
|
||||
}
|
||||
|
||||
int connectionTypeId = Integer.parseInt(key);
|
||||
blockData.put(connectionTypeId, attachingFaces);
|
||||
}
|
||||
|
||||
NumberTag idTag = blockTag.getNumberTag("id");
|
||||
if (idTag != null) {
|
||||
blockConnectionData.put(idTag.asInt(), blockData);
|
||||
} else {
|
||||
IntArrayTag idsTag = blockTag.getIntArrayTag("ids");
|
||||
for (int id : idsTag.getValue()) {
|
||||
blockConnectionData.put(id, blockData);
|
||||
}
|
||||
}
|
||||
IdRanges.forEachId(profileTag.getByteArrayTag("ids"), id -> blockConnectionData.put(id, blockData));
|
||||
}
|
||||
|
||||
IntArrayTag occludingStatesArray = data.getIntArrayTag("occluding-states");
|
||||
for (final int blockStateId : occludingStatesArray.getValue()) {
|
||||
OCCLUDING_STATES.add(blockStateId);
|
||||
}
|
||||
IdRanges.forEachId(data.getByteArrayTag("occluding-states"), OCCLUDING_STATES::add);
|
||||
}
|
||||
|
||||
List<ConnectorInitAction> initActions = new ArrayList<>();
|
||||
|
||||
+6
-5
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.protocols.v1_12_2to1_13.data;
|
||||
|
||||
import com.google.common.collect.ObjectArrays;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.viaversion.viaversion.protocols.v1_12_2to1_13.Protocol1_12_2To1_13;
|
||||
import com.viaversion.viaversion.util.GsonUtil;
|
||||
@@ -26,12 +25,13 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class BlockIdData {
|
||||
public static final String[] PREVIOUS = new String[0];
|
||||
public static final Map<String, String[]> blockIdMapping = new HashMap<>();
|
||||
public static final Map<String, String[]> fallbackReverseMapping = new HashMap<>();
|
||||
public static final Int2ObjectMap<String> numberIdToString = new Int2ObjectOpenHashMap<>();
|
||||
@@ -46,13 +46,14 @@ public class BlockIdData {
|
||||
new TypeToken<Map<String, String[]>>() {
|
||||
}.getType());
|
||||
blockIdMapping.putAll(map);
|
||||
|
||||
Map<String, List<String>> reverse = new HashMap<>();
|
||||
for (Map.Entry<String, String[]> entry : blockIdMapping.entrySet()) {
|
||||
for (String val : entry.getValue()) {
|
||||
String[] previous = fallbackReverseMapping.get(val);
|
||||
if (previous == null) previous = PREVIOUS;
|
||||
fallbackReverseMapping.put(val, ObjectArrays.concat(previous, entry.getKey()));
|
||||
reverse.computeIfAbsent(val, k -> new ArrayList<>()).add(entry.getKey());
|
||||
}
|
||||
}
|
||||
reverse.forEach((val, keys) -> fallbackReverseMapping.put(val, keys.toArray(new String[0])));
|
||||
} catch (IOException e) {
|
||||
Protocol1_12_2To1_13.LOGGER.log(Level.SEVERE, "Failed to load block id mappings", e);
|
||||
}
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
|
||||
* Copyright (C) 2016-2026 ViaVersion and 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.viaversion.viaversion.protocols.v1_12_2to1_13.data;
|
||||
|
||||
import com.viaversion.nbt.tag.CompoundTag;
|
||||
import com.viaversion.nbt.tag.IntArrayTag;
|
||||
import com.viaversion.nbt.tag.ListTag;
|
||||
import com.viaversion.nbt.tag.StringTag;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.ObjIntConsumer;
|
||||
|
||||
public final class BlockStates1_13 {
|
||||
|
||||
/**
|
||||
* Visits every block state key (e.g. {@code chest[facing=north,type=single,waterlogged=true]})
|
||||
* together with its block state id, decoded from the given blockstates-1.13.nbt data.
|
||||
*
|
||||
* @param data root tag of blockstates-1.13.nbt
|
||||
* @param consumer consumer called with each block state key and its id, in id order
|
||||
*/
|
||||
public static void forEach(CompoundTag data, ObjIntConsumer<String> consumer) {
|
||||
ListTag<CompoundTag> propertyTable = data.getListTag("properties", CompoundTag.class);
|
||||
int blockStateId = 0;
|
||||
for (CompoundTag blockTag : data.getListTag("blockstates", CompoundTag.class)) {
|
||||
String name = blockTag.getString("name");
|
||||
IntArrayTag propertiesTag = blockTag.getIntArrayTag("properties");
|
||||
if (propertiesTag == null) {
|
||||
consumer.accept(name, blockStateId++);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Construct the full string from the properties
|
||||
List<String> states = List.of("");
|
||||
for (int propertyIndex : propertiesTag.getValue()) {
|
||||
CompoundTag property = propertyTable.get(propertyIndex);
|
||||
String propertyName = property.getString("name");
|
||||
List<String> combined = new ArrayList<>();
|
||||
for (String state : states) {
|
||||
for (StringTag value : property.getListTag("values", StringTag.class)) {
|
||||
String extendedState = state.isEmpty()
|
||||
? propertyName + "=" + value.getValue()
|
||||
: state + "," + propertyName + "=" + value.getValue();
|
||||
combined.add(extendedState);
|
||||
}
|
||||
}
|
||||
states = combined;
|
||||
}
|
||||
for (String state : states) {
|
||||
consumer.accept(name + "[" + state + "]", blockStateId++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-21
@@ -22,7 +22,6 @@ import com.google.common.collect.HashBiMap;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.viaversion.nbt.tag.CompoundTag;
|
||||
import com.viaversion.nbt.tag.IntArrayTag;
|
||||
import com.viaversion.nbt.tag.StringTag;
|
||||
@@ -33,7 +32,6 @@ import com.viaversion.viaversion.api.data.Int2IntMapBiMappings;
|
||||
import com.viaversion.viaversion.api.data.MappingDataBase;
|
||||
import com.viaversion.viaversion.api.data.MappingDataLoader;
|
||||
import com.viaversion.viaversion.api.data.Mappings;
|
||||
import com.viaversion.viaversion.util.GsonUtil;
|
||||
import com.viaversion.viaversion.util.Int2IntBiHashMap;
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
import java.io.IOException;
|
||||
@@ -94,37 +92,46 @@ public class MappingData1_13 extends MappingDataBase {
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> translationMappingData = GsonUtil.getGson().fromJson(
|
||||
new InputStreamReader(MappingData1_13.class.getClassLoader().getResourceAsStream("assets/viaversion/data/mapping-lang-1.12-1.13.json")),
|
||||
new TypeToken<Map<String, String>>() {
|
||||
}.getType());
|
||||
|
||||
String[] unmappedTranslationLines;
|
||||
String[] translationLines;
|
||||
try (Reader reader = new InputStreamReader(MappingData1_13.class.getClassLoader()
|
||||
.getResourceAsStream("assets/viaversion/data/en_US.properties"), StandardCharsets.UTF_8)) {
|
||||
unmappedTranslationLines = CharStreams.toString(reader).split("\n");
|
||||
.getResourceAsStream("assets/viaversion/data/mapping-lang-1.12-1.13.txt"), StandardCharsets.UTF_8)) {
|
||||
translationLines = CharStreams.toString(reader).split("\n");
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
for (String line : unmappedTranslationLines) {
|
||||
if (line.isEmpty()) {
|
||||
// 1.12 translations with number placeholders already replaced and their mapped 1.13 keys inlined:
|
||||
// key=translation for keys unchanged in 1.13,
|
||||
// old>new=translation for renamed keys with placeholder changes,
|
||||
// -key=translation for keys removed in 1.13 (= translations are inlined into components instead)
|
||||
for (String line : translationLines) {
|
||||
if (line.endsWith("\r")) {
|
||||
line = line.substring(0, line.length() - 1);
|
||||
}
|
||||
if (line.isEmpty() || line.charAt(0) == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] keyAndTranslation = line.split("=", 2);
|
||||
if (keyAndTranslation.length != 2) {
|
||||
int keySeparator = line.indexOf('=');
|
||||
if (keySeparator == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String key = keyAndTranslation[0];
|
||||
String translation = keyAndTranslation[1].replaceAll("%(\\d\\$)?d", "%$1s").trim();
|
||||
mojangTranslation.put(key, translation);
|
||||
String translation = line.substring(keySeparator + 1);
|
||||
if (line.charAt(0) == '-') {
|
||||
mojangTranslation.put(line.substring(1, keySeparator), translation);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Null values in the file mean the key did not change AND the translation has the same amount of placeholders still
|
||||
if (translationMappingData.containsKey(key)) {
|
||||
String mappedKey = translationMappingData.get(key);
|
||||
translateMapping.put(key, mappedKey != null ? mappedKey : key);
|
||||
int renameSeparator = line.indexOf('>');
|
||||
if (renameSeparator == -1 || renameSeparator > keySeparator) {
|
||||
String key = line.substring(0, keySeparator);
|
||||
mojangTranslation.put(key, translation);
|
||||
translateMapping.put(key, key);
|
||||
} else {
|
||||
String key = line.substring(0, renameSeparator);
|
||||
mojangTranslation.put(key, translation);
|
||||
translateMapping.put(key, line.substring(renameSeparator + 1, keySeparator));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+77
-7
@@ -17,30 +17,100 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.protocols.v1_12_2to1_13.data;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.viaversion.viaversion.api.minecraft.item.DataItem;
|
||||
import com.viaversion.viaversion.util.GsonUtil;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class RecipeData {
|
||||
public static Map<String, Recipe> recipes;
|
||||
|
||||
public static void init() {
|
||||
JsonObject object;
|
||||
InputStream stream = MappingData1_13.class.getClassLoader()
|
||||
.getResourceAsStream("assets/viaversion/data/itemrecipes1_12_2to1_13.json");
|
||||
try (InputStreamReader reader = new InputStreamReader(stream)) {
|
||||
recipes = GsonUtil.getGson().fromJson(
|
||||
reader,
|
||||
new TypeToken<Map<String, Recipe>>() {
|
||||
}.getType()
|
||||
);
|
||||
object = GsonUtil.getGson().fromJson(reader, JsonObject.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
// Ignored
|
||||
|
||||
recipes = new LinkedHashMap<>();
|
||||
|
||||
// Shaped and shapeless sections are keyed by their legacy id
|
||||
for (Map.Entry<String, JsonElement> entry : object.getAsJsonObject("shapeless").entrySet()) {
|
||||
JsonObject recipe = entry.getValue().getAsJsonObject();
|
||||
DataItem[][] ingredients = ingredients(recipe.getAsJsonArray("ingredients"));
|
||||
recipes.put("viaversion:legacy/" + entry.getKey(), new Recipe("crafting_shapeless", group(recipe), 0, 0, 0, 0, null, ingredients, result(recipe)));
|
||||
}
|
||||
|
||||
// Ingredients are stored as grid rows, so the width and height are implicit
|
||||
for (Map.Entry<String, JsonElement> entry : object.getAsJsonObject("shaped").entrySet()) {
|
||||
JsonObject recipe = entry.getValue().getAsJsonObject();
|
||||
JsonArray grid = recipe.getAsJsonArray("grid");
|
||||
int height = grid.size();
|
||||
int width = grid.get(0).getAsJsonArray().size();
|
||||
DataItem[][] ingredients = new DataItem[width * height][];
|
||||
int slot = 0;
|
||||
for (JsonElement row : grid) {
|
||||
for (JsonElement element : row.getAsJsonArray()) {
|
||||
ingredients[slot++] = ingredient(element);
|
||||
}
|
||||
}
|
||||
recipes.put("viaversion:legacy/" + entry.getKey(), new Recipe("crafting_shaped", group(recipe), width, height, 0, 0, null, ingredients, result(recipe)));
|
||||
}
|
||||
|
||||
for (Map.Entry<String, JsonElement> entry : object.getAsJsonObject("smelting").entrySet()) {
|
||||
JsonObject recipe = entry.getValue().getAsJsonObject();
|
||||
float experience = recipe.get("experience").getAsFloat();
|
||||
recipes.put(entry.getKey(), new Recipe("smelting", group(recipe), 0, 0, experience, 200, ingredient(recipe.get("ingredient")), null, result(recipe)));
|
||||
}
|
||||
|
||||
// Name to type suffix
|
||||
for (Map.Entry<String, JsonElement> entry : object.getAsJsonObject("special").entrySet()) {
|
||||
recipes.put(entry.getKey(), new Recipe("crafting_special_" + entry.getValue().getAsString(), "", 0, 0, 0, 0, null, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
private static String group(JsonObject recipe) {
|
||||
JsonElement group = recipe.get("group");
|
||||
return group != null ? group.getAsString() : "";
|
||||
}
|
||||
|
||||
private static DataItem result(JsonObject recipe) {
|
||||
JsonElement count = recipe.get("count");
|
||||
return new DataItem(recipe.get("result").getAsInt(), count != null ? count.getAsByte() : (byte) 1, null);
|
||||
}
|
||||
|
||||
// null for an empty slot, a single item id, or an array of item id choices
|
||||
private static DataItem[] ingredient(JsonElement element) {
|
||||
if (element.isJsonNull()) {
|
||||
return new DataItem[0];
|
||||
}
|
||||
if (element.isJsonArray()) {
|
||||
JsonArray choices = element.getAsJsonArray();
|
||||
DataItem[] items = new DataItem[choices.size()];
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
items[i] = new DataItem(choices.get(i).getAsInt(), (byte) 1, null);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
return new DataItem[]{new DataItem(element.getAsInt(), (byte) 1, null)};
|
||||
}
|
||||
|
||||
private static DataItem[][] ingredients(JsonArray slots) {
|
||||
DataItem[][] items = new DataItem[slots.size()][];
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
items[i] = ingredient(slots.get(i));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
public record Recipe(String type, String group, int width, int height, float experience, int cookingTime,
|
||||
|
||||
+58
-6
@@ -51,6 +51,7 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class WorldPacketRewriter1_13 {
|
||||
private static final IntSet VALID_BIOMES = new IntOpenHashSet(70);
|
||||
@@ -512,19 +513,42 @@ public class WorldPacketRewriter1_13 {
|
||||
float speed = wrapper.get(Types.FLOAT, 6);
|
||||
// Only handle for count = 0
|
||||
if (count == 0) {
|
||||
// Change count and speed
|
||||
wrapper.set(Types.INT, 1, 1);
|
||||
wrapper.set(Types.FLOAT, 6, 0f);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
//RGB values are represented by the X/Y/Z offset
|
||||
float colorValue = wrapper.get(Types.FLOAT, i + 3) * speed;
|
||||
if (colorValue == 0 && i == 0) {
|
||||
// https://minecraft.gamepedia.com/User:Alphappy/reddust
|
||||
colorValue = 1;
|
||||
}
|
||||
// RGB values are represented by the X/Y/Z offset
|
||||
float offset = wrapper.get(Types.FLOAT, i + 3);
|
||||
float colorValue = redstoneDustColor(speed, offset, i == 0);
|
||||
particle.<Float>getArgument(i).setValue(colorValue);
|
||||
wrapper.set(Types.FLOAT, i + 3, 0f);
|
||||
}
|
||||
} else if (Via.getConfig().isMultiReddustColorFix()) {
|
||||
wrapper.cancel();
|
||||
|
||||
final boolean longDistance = wrapper.get(Types.BOOLEAN, 0);
|
||||
final float x = wrapper.get(Types.FLOAT, 0);
|
||||
final float y = wrapper.get(Types.FLOAT, 1);
|
||||
final float z = wrapper.get(Types.FLOAT, 2);
|
||||
final float offsetX = wrapper.get(Types.FLOAT, 3);
|
||||
final float offsetY = wrapper.get(Types.FLOAT, 4);
|
||||
final float offsetZ = wrapper.get(Types.FLOAT, 5);
|
||||
final ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final float particleX = (float) (x + random.nextGaussian() * offsetX);
|
||||
final float particleY = (float) (y + random.nextGaussian() * offsetY);
|
||||
final float particleZ = (float) (z + random.nextGaussian() * offsetZ);
|
||||
sendRedstoneParticle(wrapper,
|
||||
longDistance,
|
||||
particleX,
|
||||
particleY,
|
||||
particleZ,
|
||||
redstoneDustColor((float) random.nextGaussian(), speed, true),
|
||||
redstoneDustColor((float) random.nextGaussian(), speed, false),
|
||||
redstoneDustColor((float) random.nextGaussian(), speed, false));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,6 +587,34 @@ public class WorldPacketRewriter1_13 {
|
||||
|
||||
}
|
||||
|
||||
private static float redstoneDustColor(float speed, float offset, boolean red) {
|
||||
// Vanilla 1.12 substitutes red when the raw red value is zero
|
||||
float color = speed * offset;
|
||||
if (red && color == 0F) {
|
||||
return 1F;
|
||||
}
|
||||
return ((int) (color * 255F) & 255) / 255F;
|
||||
}
|
||||
|
||||
private static void sendRedstoneParticle(PacketWrapper wrapper, boolean longDistance, float x, float y, float z, float red, float green, float blue) {
|
||||
final PacketWrapper particlePacket = wrapper.create(ClientboundPackets1_13.LEVEL_PARTICLES);
|
||||
particlePacket.write(Types.INT, 11);
|
||||
particlePacket.write(Types.BOOLEAN, longDistance);
|
||||
particlePacket.write(Types.FLOAT, x);
|
||||
particlePacket.write(Types.FLOAT, y);
|
||||
particlePacket.write(Types.FLOAT, z);
|
||||
particlePacket.write(Types.FLOAT, 0F);
|
||||
particlePacket.write(Types.FLOAT, 0F);
|
||||
particlePacket.write(Types.FLOAT, 0F);
|
||||
particlePacket.write(Types.FLOAT, 0F);
|
||||
particlePacket.write(Types.INT, 0);
|
||||
particlePacket.write(Types.FLOAT, red);
|
||||
particlePacket.write(Types.FLOAT, green);
|
||||
particlePacket.write(Types.FLOAT, blue);
|
||||
particlePacket.write(Types.FLOAT, 1F);
|
||||
particlePacket.send(Protocol1_12_2To1_13.class);
|
||||
}
|
||||
|
||||
public static int toNewId(int oldId) {
|
||||
if (oldId < 0) {
|
||||
oldId = 0; // Some plugins use negative numbers to clear blocks, remap them to air.
|
||||
|
||||
+3
-6
@@ -18,11 +18,10 @@
|
||||
package com.viaversion.viaversion.protocols.v1_13_2to1_14.data;
|
||||
|
||||
import com.viaversion.nbt.tag.CompoundTag;
|
||||
import com.viaversion.nbt.tag.IntArrayTag;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.data.MappingDataBase;
|
||||
import com.viaversion.viaversion.api.data.MappingDataLoader;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import com.viaversion.viaversion.api.data.IdRanges;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
|
||||
public class MappingData1_14 extends MappingDataBase {
|
||||
@@ -36,12 +35,10 @@ public class MappingData1_14 extends MappingDataBase {
|
||||
@Override
|
||||
public void loadExtras(final CompoundTag data) {
|
||||
final CompoundTag heightmap = MappingDataLoader.INSTANCE.loadNBT("heightmap-1.14.nbt");
|
||||
final IntArrayTag motionBlocking = heightmap.getIntArrayTag("motionBlocking");
|
||||
this.motionBlocking = new IntOpenHashSet(motionBlocking.getValue());
|
||||
this.motionBlocking = IdRanges.decode(heightmap.getByteArrayTag("motionBlocking"));
|
||||
|
||||
if (Via.getConfig().isNonFullBlockLightFix()) {
|
||||
final IntArrayTag nonFullBlocks = heightmap.getIntArrayTag("nonFullBlocks");
|
||||
this.nonFullBlocks = new IntOpenHashSet(nonFullBlocks.getValue());
|
||||
this.nonFullBlocks = IdRanges.decode(heightmap.getByteArrayTag("nonFullBlocks"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -18,10 +18,12 @@
|
||||
package com.viaversion.viaversion.protocols.v1_13_2to1_14.storage;
|
||||
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.data.entity.TrackedEntity;
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_14;
|
||||
import com.viaversion.viaversion.data.entity.EntityTrackerBase;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class EntityTracker1_14 extends EntityTrackerBase {
|
||||
private final Int2ObjectMap<Byte> insentientData = new Int2ObjectOpenHashMap<>();
|
||||
@@ -38,12 +40,11 @@ public class EntityTracker1_14 extends EntityTrackerBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntity(int entityId) {
|
||||
super.removeEntity(entityId);
|
||||
|
||||
public @Nullable TrackedEntity removeEntity(int entityId) {
|
||||
insentientData.remove(entityId);
|
||||
sleepingAndRiptideData.remove(entityId);
|
||||
playerEntityFlags.remove(entityId);
|
||||
return super.removeEntity(entityId);
|
||||
}
|
||||
|
||||
public byte getInsentientData(int entity) {
|
||||
|
||||
+2
-1
@@ -37,6 +37,7 @@ import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ServerboundPacke
|
||||
import com.viaversion.viaversion.protocols.v1_15_2to1_16.storage.InventoryTracker1_16;
|
||||
import com.viaversion.viaversion.rewriter.EntityRewriter;
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
import com.viaversion.viaversion.util.UUIDUtil;
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntityPacketRewriter1_16 extends EntityRewriter<ClientboundPackets1_15, Protocol1_15_2To1_16> {
|
||||
@@ -93,7 +94,7 @@ public class EntityPacketRewriter1_16 extends EntityRewriter<ClientboundPackets1
|
||||
|
||||
wrapper.user().getEntityTracker(Protocol1_15_2To1_16.class).addEntity(entityId, EntityTypes1_16.LIGHTNING_BOLT);
|
||||
|
||||
wrapper.write(Types.UUID, UUID.randomUUID()); // uuid
|
||||
wrapper.write(Types.UUID, UUIDUtil.randomUUID()); // uuid
|
||||
wrapper.write(Types.VAR_INT, EntityTypes1_16.LIGHTNING_BOLT.getId()); // entity type
|
||||
|
||||
wrapper.passthrough(Types.DOUBLE); // x
|
||||
|
||||
+8
-2
@@ -36,6 +36,7 @@ import com.viaversion.viaversion.protocols.v1_17_1to1_18.rewriter.ItemPacketRewr
|
||||
import com.viaversion.viaversion.protocols.v1_17_1to1_18.rewriter.WorldPacketRewriter1_18;
|
||||
import com.viaversion.viaversion.protocols.v1_17_1to1_18.storage.ChunkLightStorage;
|
||||
import com.viaversion.viaversion.protocols.v1_17to1_17_1.packet.ClientboundPackets1_17_1;
|
||||
import com.viaversion.viaversion.rewriter.BlockRewriter;
|
||||
import com.viaversion.viaversion.rewriter.ParticleRewriter;
|
||||
import com.viaversion.viaversion.rewriter.TagRewriter;
|
||||
import com.viaversion.viaversion.util.ProtocolLogger;
|
||||
@@ -48,6 +49,7 @@ public final class Protocol1_17_1To1_18 extends AbstractProtocol<ClientboundPack
|
||||
private final ItemPacketRewriter1_18 itemRewriter = new ItemPacketRewriter1_18(this);
|
||||
private final ParticleRewriter<ClientboundPackets1_17_1> particleRewriter = new ParticleRewriter<>(this);
|
||||
private final ComponentRewriter1_18 componentRewriter = new ComponentRewriter1_18(this);
|
||||
private final BlockRewriter<ClientboundPackets1_17_1> blockRewriter = BlockRewriter.for1_14(this);
|
||||
private final TagRewriter<ClientboundPackets1_17_1> tagRewriter = new TagRewriter<>(this);
|
||||
|
||||
public Protocol1_17_1To1_18() {
|
||||
@@ -56,8 +58,7 @@ public final class Protocol1_17_1To1_18 extends AbstractProtocol<ClientboundPack
|
||||
|
||||
@Override
|
||||
protected void registerPackets() {
|
||||
entityRewriter.register();
|
||||
itemRewriter.register();
|
||||
super.registerPackets();
|
||||
WorldPacketRewriter1_18.register(this);
|
||||
|
||||
registerServerbound(ServerboundPackets1_17.CLIENT_INFORMATION, new PacketHandlers() {
|
||||
@@ -110,6 +111,11 @@ public final class Protocol1_17_1To1_18 extends AbstractProtocol<ClientboundPack
|
||||
return itemRewriter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockRewriter<ClientboundPackets1_17_1> getBlockRewriter() {
|
||||
return blockRewriter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParticleRewriter<ClientboundPackets1_17_1> getParticleRewriter() {
|
||||
return particleRewriter;
|
||||
|
||||
+15
@@ -45,6 +45,7 @@ import com.viaversion.viaversion.protocols.v1_18_2to1_19.rewriter.EntityPacketRe
|
||||
import com.viaversion.viaversion.protocols.v1_18_2to1_19.rewriter.ItemPacketRewriter1_19;
|
||||
import com.viaversion.viaversion.protocols.v1_18_2to1_19.rewriter.WorldPacketRewriter1_19;
|
||||
import com.viaversion.viaversion.protocols.v1_18_2to1_19.storage.DimensionRegistryStorage;
|
||||
import com.viaversion.viaversion.protocols.v1_18_2to1_19.storage.LoomStorage;
|
||||
import com.viaversion.viaversion.protocols.v1_18_2to1_19.storage.NonceStorage1_19;
|
||||
import com.viaversion.viaversion.protocols.v1_18_2to1_19.storage.SequenceStorage;
|
||||
import com.viaversion.viaversion.rewriter.BlockRewriter;
|
||||
@@ -341,6 +342,19 @@ public final class Protocol1_18_2To1_19 extends AbstractProtocol<ClientboundPack
|
||||
tagRewriter.addEmptyTag(RegistryType.ENTITY, "minecraft:frog_food");
|
||||
tagRewriter.addEmptyTags(RegistryType.GAME_EVENT, "minecraft:allay_can_listen", "minecraft:shrieker_can_listen", "minecraft:warden_can_listen");
|
||||
|
||||
// Selectable loom patterns. 0 is the base pattern and the last 6 have pattern items
|
||||
final int[] noItemRequiredPatterns = new int[34];
|
||||
for (int i = 0; i < noItemRequiredPatterns.length; i++) {
|
||||
noItemRequiredPatterns[i] = i + 1;
|
||||
}
|
||||
tagRewriter.addTagRaw(RegistryType.BANNER_PATTERN, "minecraft:no_item_required", noItemRequiredPatterns);
|
||||
tagRewriter.addTagRaw(RegistryType.BANNER_PATTERN, "minecraft:pattern_item/globe", 35);
|
||||
tagRewriter.addTagRaw(RegistryType.BANNER_PATTERN, "minecraft:pattern_item/creeper", 36);
|
||||
tagRewriter.addTagRaw(RegistryType.BANNER_PATTERN, "minecraft:pattern_item/skull", 37);
|
||||
tagRewriter.addTagRaw(RegistryType.BANNER_PATTERN, "minecraft:pattern_item/flower", 38);
|
||||
tagRewriter.addTagRaw(RegistryType.BANNER_PATTERN, "minecraft:pattern_item/mojang", 39);
|
||||
tagRewriter.addTagRaw(RegistryType.BANNER_PATTERN, "minecraft:pattern_item/piglin", 40);
|
||||
|
||||
super.onMappingDataLoaded();
|
||||
}
|
||||
|
||||
@@ -355,6 +369,7 @@ public final class Protocol1_18_2To1_19 extends AbstractProtocol<ClientboundPack
|
||||
user.put(new DimensionRegistryStorage());
|
||||
}
|
||||
user.put(new SequenceStorage());
|
||||
user.put(new LoomStorage());
|
||||
addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_19.PLAYER));
|
||||
}
|
||||
|
||||
|
||||
+41
@@ -26,12 +26,16 @@ import com.viaversion.viaversion.protocols.v1_17_1to1_18.packet.ClientboundPacke
|
||||
import com.viaversion.viaversion.protocols.v1_18_2to1_19.Protocol1_18_2To1_19;
|
||||
import com.viaversion.viaversion.protocols.v1_18_2to1_19.packet.ServerboundPackets1_19;
|
||||
import com.viaversion.viaversion.protocols.v1_18_2to1_19.provider.AckSequenceProvider;
|
||||
import com.viaversion.viaversion.protocols.v1_18_2to1_19.storage.LoomStorage;
|
||||
import com.viaversion.viaversion.rewriter.ItemRewriter;
|
||||
import com.viaversion.viaversion.rewriter.RecipeRewriter;
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
|
||||
public final class ItemPacketRewriter1_19 extends ItemRewriter<ClientboundPackets1_18, ServerboundPackets1_19, Protocol1_18_2To1_19> {
|
||||
|
||||
private static final int LOOM_MENU_TYPE_ID = 17;
|
||||
private static final int LOOM_SELECTABLE_PATTERNS = 34;
|
||||
|
||||
public ItemPacketRewriter1_19(Protocol1_18_2To1_19 protocol) {
|
||||
super(protocol, Types.ITEM1_13_2, Types.ITEM1_13_2_ARRAY);
|
||||
}
|
||||
@@ -127,6 +131,43 @@ public final class ItemPacketRewriter1_19 extends ItemRewriter<ClientboundPacket
|
||||
}
|
||||
});
|
||||
|
||||
// The loom's selected pattern changed from the raw pattern id to an index of selectable pattern tags
|
||||
protocol.registerClientbound(ClientboundPackets1_18.CONTAINER_CLOSE, wrapper -> wrapper.user().get(LoomStorage.class).reset());
|
||||
protocol.registerServerbound(ServerboundPackets1_19.CONTAINER_CLOSE, wrapper -> wrapper.user().get(LoomStorage.class).reset());
|
||||
protocol.registerServerbound(ServerboundPackets1_19.CONTAINER_BUTTON_CLICK, wrapper -> {
|
||||
final byte containerId = wrapper.passthrough(Types.BYTE);
|
||||
if (containerId != -1 && containerId == wrapper.user().get(LoomStorage.class).containerId()) {
|
||||
final byte patternIndex = wrapper.read(Types.BYTE);
|
||||
wrapper.write(Types.BYTE, (byte) (patternIndex + 1));
|
||||
}
|
||||
});
|
||||
protocol.registerClientbound(ClientboundPackets1_18.OPEN_SCREEN, wrapper -> {
|
||||
final int containerId = wrapper.passthrough(Types.VAR_INT);
|
||||
final int menuTypeId = wrapper.passthrough(Types.VAR_INT);
|
||||
wrapper.user().get(LoomStorage.class).setContainerId(menuTypeId == LOOM_MENU_TYPE_ID ? containerId : -1);
|
||||
});
|
||||
protocol.appendClientbound(ClientboundPackets1_18.CONTAINER_SET_DATA, wrapper -> {
|
||||
wrapper.resetReader();
|
||||
final short containerId = wrapper.passthrough(Types.UNSIGNED_BYTE);
|
||||
if (containerId == -1 || containerId != wrapper.user().get(LoomStorage.class).containerId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final short property = wrapper.passthrough(Types.SHORT);
|
||||
if (property == 0) { // Selected pattern
|
||||
final short patternId = wrapper.read(Types.SHORT);
|
||||
final short patternIndex;
|
||||
if (patternId == 0) {
|
||||
patternIndex = -1; // No pattern selected
|
||||
} else if (patternId <= LOOM_SELECTABLE_PATTERNS) {
|
||||
patternIndex = (short) (patternId - 1); // Index into the no_item_required tag
|
||||
} else {
|
||||
patternIndex = 0; // Only entry of its pattern_item tag
|
||||
}
|
||||
wrapper.write(Types.SHORT, patternIndex);
|
||||
}
|
||||
});
|
||||
|
||||
new RecipeRewriter<>(protocol).register(ClientboundPackets1_18.UPDATE_RECIPES);
|
||||
}
|
||||
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
|
||||
* Copyright (C) 2016-2026 ViaVersion and 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.viaversion.viaversion.protocols.v1_18_2to1_19.storage;
|
||||
|
||||
import com.viaversion.viaversion.api.connection.StorableObject;
|
||||
|
||||
public final class LoomStorage implements StorableObject {
|
||||
|
||||
private int containerId = -1;
|
||||
|
||||
public int containerId() {
|
||||
return containerId;
|
||||
}
|
||||
|
||||
public void setContainerId(final int containerId) {
|
||||
this.containerId = containerId;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.containerId = -1;
|
||||
}
|
||||
}
|
||||
+53
@@ -23,6 +23,7 @@ import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.data.MappingData;
|
||||
import com.viaversion.viaversion.api.data.MappingDataBase;
|
||||
import com.viaversion.viaversion.api.minecraft.BlockPosition;
|
||||
import com.viaversion.viaversion.api.minecraft.Holder;
|
||||
import com.viaversion.viaversion.api.minecraft.PlayerMessageSignature;
|
||||
import com.viaversion.viaversion.api.minecraft.RegistryType;
|
||||
@@ -69,6 +70,17 @@ public final class Protocol1_19_1To1_19_3 extends AbstractProtocol<ClientboundPa
|
||||
public static final MappingData MAPPINGS = new MappingDataBase("1.19", "1.19.3");
|
||||
private static final UUID ZERO_UUID = new UUID(0, 0);
|
||||
private static final byte[] EMPTY_BYTES = new byte[0];
|
||||
private static final int SOUND_SOURCE_BLOCKS = 4;
|
||||
private static final int SOUND_IRON_DOOR_CLOSE = 614;
|
||||
private static final int SOUND_IRON_DOOR_OPEN = 615;
|
||||
private static final int SOUND_IRON_TRAPDOOR_CLOSE = 622;
|
||||
private static final int SOUND_IRON_TRAPDOOR_OPEN = 623;
|
||||
private static final int SOUND_FENCE_GATE_CLOSE = 110;
|
||||
private static final int SOUND_FENCE_GATE_OPEN = 111;
|
||||
private static final int SOUND_WOODEN_DOOR_CLOSE = 1345;
|
||||
private static final int SOUND_WOODEN_DOOR_OPEN = 1346;
|
||||
private static final int SOUND_WOODEN_TRAPDOOR_CLOSE = 1347;
|
||||
private static final int SOUND_WOODEN_TRAPDOOR_OPEN = 1348;
|
||||
private final EntityPacketRewriter1_19_3 entityRewriter = new EntityPacketRewriter1_19_3(this);
|
||||
private final ItemPacketRewriter1_19_3 itemRewriter = new ItemPacketRewriter1_19_3(this);
|
||||
private final ParticleRewriter<ClientboundPackets1_19_1> particleRewriter = new ParticleRewriter<>(this);
|
||||
@@ -101,6 +113,31 @@ public final class Protocol1_19_1To1_19_3 extends AbstractProtocol<ClientboundPa
|
||||
final String soundIdentifier = wrapper.read(Types.STRING);
|
||||
wrapper.write(Types.SOUND_EVENT, Holder.of(new SoundEvent(soundIdentifier, null)));
|
||||
});
|
||||
appendClientbound(ClientboundPackets1_19_1.LEVEL_EVENT, wrapper -> {
|
||||
wrapper.resetReader();
|
||||
|
||||
final int eventId = wrapper.read(Types.INT);
|
||||
final int soundId = levelEventSoundId(eventId);
|
||||
if (soundId == -1) {
|
||||
wrapper.write(Types.INT, eventId);
|
||||
return;
|
||||
}
|
||||
|
||||
final BlockPosition position = wrapper.read(Types.BLOCK_POSITION1_14);
|
||||
wrapper.read(Types.INT); // Data
|
||||
wrapper.read(Types.BOOLEAN); // Global event
|
||||
|
||||
// These level events were removed, instead send them as sounds directly
|
||||
wrapper.setPacketType(ClientboundPackets1_19_3.SOUND);
|
||||
wrapper.write(Types.SOUND_EVENT, Holder.of(soundId));
|
||||
wrapper.write(Types.VAR_INT, SOUND_SOURCE_BLOCKS);
|
||||
wrapper.write(Types.INT, position.x() * 8 + 4);
|
||||
wrapper.write(Types.INT, position.y() * 8 + 4);
|
||||
wrapper.write(Types.INT, position.z() * 8 + 4);
|
||||
wrapper.write(Types.FLOAT, 1F); // Volume
|
||||
wrapper.write(Types.FLOAT, ThreadLocalRandom.current().nextFloat() * 0.1F + 0.9F); // Pitch
|
||||
wrapper.write(Types.LONG, ThreadLocalRandom.current().nextLong()); // Seed
|
||||
});
|
||||
|
||||
final CommandRewriter<ClientboundPackets1_19_1> commandRewriter = new CommandRewriter<>(this) {
|
||||
@Override
|
||||
@@ -380,4 +417,20 @@ public final class Protocol1_19_1To1_19_3 extends AbstractProtocol<ClientboundPa
|
||||
public TagRewriter<ClientboundPackets1_19_1> getTagRewriter() {
|
||||
return tagRewriter;
|
||||
}
|
||||
|
||||
private static int levelEventSoundId(final int eventId) {
|
||||
return switch (eventId) {
|
||||
case 1005 -> SOUND_IRON_DOOR_OPEN;
|
||||
case 1006 -> SOUND_WOODEN_DOOR_OPEN;
|
||||
case 1007 -> SOUND_WOODEN_TRAPDOOR_OPEN;
|
||||
case 1008 -> SOUND_FENCE_GATE_OPEN;
|
||||
case 1011 -> SOUND_IRON_DOOR_CLOSE;
|
||||
case 1012 -> SOUND_WOODEN_DOOR_CLOSE;
|
||||
case 1013 -> SOUND_WOODEN_TRAPDOOR_CLOSE;
|
||||
case 1014 -> SOUND_FENCE_GATE_CLOSE;
|
||||
case 1036 -> SOUND_IRON_TRAPDOOR_CLOSE;
|
||||
case 1037 -> SOUND_IRON_TRAPDOOR_OPEN;
|
||||
default -> -1;
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -196,8 +196,8 @@ public final class EntityPacketRewriter1_19_4 extends EntityRewriter<Clientbound
|
||||
wrapper.setPacketType(ClientboundPackets1_19_4.DAMAGE_EVENT);
|
||||
wrapper.write(Types.VAR_INT, entityId);
|
||||
wrapper.write(Types.VAR_INT, damageType);
|
||||
wrapper.write(Types.VAR_INT, 0); // No source entity
|
||||
wrapper.write(Types.VAR_INT, 0); // No direct source entity
|
||||
wrapper.write(Types.OPTIONAL_VAR_INT, null); // No source entity
|
||||
wrapper.write(Types.OPTIONAL_VAR_INT, null); // No direct source entity
|
||||
wrapper.write(Types.BOOLEAN, false); // No source position
|
||||
return;
|
||||
}
|
||||
|
||||
+2
-1
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.protocols.v1_20_3to1_20_5.data;
|
||||
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
import com.viaversion.viaversion.util.KeyMappings;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -136,7 +137,7 @@ public final class BannerPatterns1_20_5 {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String[] keys() {
|
||||
public static Key[] keys() {
|
||||
return PATTERNS.keys();
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -54,6 +54,7 @@ import com.viaversion.viaversion.rewriter.EntityRewriter;
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
import com.viaversion.viaversion.util.KeyMappings;
|
||||
import com.viaversion.viaversion.util.TagUtil;
|
||||
import com.viaversion.viaversion.util.UUIDUtil;
|
||||
import it.unimi.dsi.fastutil.ints.IntArraySet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import java.util.Arrays;
|
||||
@@ -224,15 +225,14 @@ public final class EntityPacketRewriter1_20_5 extends EntityRewriter<Clientbound
|
||||
|
||||
final PacketWrapper bannerPatternsPacket = wrapper.create(ClientboundConfigurationPackets1_20_5.REGISTRY_DATA);
|
||||
bannerPatternsPacket.write(Types.STRING, "minecraft:banner_pattern");
|
||||
final RegistryEntry[] patternEntries = new RegistryEntry[BannerPatterns1_20_5.keys().length];
|
||||
final String[] keys = BannerPatterns1_20_5.keys();
|
||||
final Key[] keys = BannerPatterns1_20_5.keys();
|
||||
final RegistryEntry[] patternEntries = new RegistryEntry[keys.length];
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
final CompoundTag pattern = new CompoundTag();
|
||||
final String key = keys[i];
|
||||
final String resourceLocation = "minecraft:" + key;
|
||||
pattern.putString("asset_id", key);
|
||||
pattern.putString("translation_key", "block.minecraft.banner." + key);
|
||||
patternEntries[i] = new RegistryEntry(resourceLocation, pattern);
|
||||
final Key key = keys[i];
|
||||
pattern.putString("asset_id", key.path());
|
||||
pattern.putString("translation_key", "block.minecraft.banner." + key.path());
|
||||
patternEntries[i] = new RegistryEntry(key.toString(), pattern);
|
||||
}
|
||||
bannerPatternsPacket.write(Types.REGISTRY_ENTRY_ARRAY, patternEntries);
|
||||
bannerPatternsPacket.send(Protocol1_20_3To1_20_5.class);
|
||||
@@ -407,7 +407,7 @@ public final class EntityPacketRewriter1_20_5 extends EntityRewriter<Clientbound
|
||||
|
||||
for (int i = 0; i < entries.length; i++) {
|
||||
if (entries[i] == null) {
|
||||
entries[i] = first.withKey(UUID.randomUUID().toString());
|
||||
entries[i] = first.withKey(UUIDUtil.randomUUID().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -159,7 +159,7 @@ public final class Protocol1_20_5To1_21 extends AbstractProtocol<ClientboundPack
|
||||
}
|
||||
|
||||
uuid = UUIDUtil.parseUUID(Key.stripNamespace(id).toUpperCase(Locale.ROOT));
|
||||
return uuid != null ? uuid : UUID.randomUUID();
|
||||
return uuid != null ? uuid : UUIDUtil.randomUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+19
-9
@@ -66,6 +66,7 @@ import com.viaversion.viaversion.rewriter.TagRewriter;
|
||||
import com.viaversion.viaversion.rewriter.block.BlockRewriter1_21_5;
|
||||
import com.viaversion.viaversion.rewriter.text.NBTComponentRewriter;
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
import com.viaversion.viaversion.util.MathUtil;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap;
|
||||
@@ -144,16 +145,19 @@ public final class Protocol1_21_11To26_1 extends AbstractProtocol<ClientboundPac
|
||||
registryDataRewriter.addHandler("dimension_type", (key, tag) -> {
|
||||
tag.putBoolean("has_ender_dragon_fight", Key.equals(key, "the_end"));
|
||||
|
||||
final CompoundTag attributes = tag.getCompoundTag("attributes");
|
||||
if (attributes != null) {
|
||||
final int ambientLightColor = switch (Key.stripMinecraftNamespace(key)) {
|
||||
case "the_end" -> -12630209;
|
||||
case "the_nether" -> -13621215;
|
||||
case "overworld" -> -16119286;
|
||||
default -> -16777216;
|
||||
};
|
||||
attributes.putInt("visual/ambient_light_color", ambientLightColor);
|
||||
CompoundTag attributes = tag.getCompoundTag("attributes");
|
||||
if (attributes == null) {
|
||||
attributes = new CompoundTag();
|
||||
tag.put("attributes", attributes);
|
||||
}
|
||||
|
||||
final int ambientLightColor = switch (Key.stripMinecraftNamespace(key)) {
|
||||
case "the_end" -> -12630209;
|
||||
case "the_nether" -> -13621215;
|
||||
case "overworld" -> -16119286;
|
||||
default -> ambientLightColor(tag.getFloat("ambient_light", 0F));
|
||||
};
|
||||
attributes.putInt("visual/ambient_light_color", ambientLightColor);
|
||||
});
|
||||
registerClientbound(ClientboundPackets1_21_11.SET_TIME, wrapper -> {
|
||||
wrapper.passthrough(Types.LONG); // Game time
|
||||
@@ -273,6 +277,12 @@ public final class Protocol1_21_11To26_1 extends AbstractProtocol<ClientboundPac
|
||||
assetIdTag.setValue(assetId.replace(key + "/", key + "/" + key + "_"));
|
||||
}
|
||||
|
||||
private int ambientLightColor(final float ambientLight) {
|
||||
// Interpolate between the overworld (ambient_light=0) and full white
|
||||
final int value = MathUtil.clamp(Math.round(10 + ambientLight * 245), 10, 255);
|
||||
return 0xFF000000 | value << 16 | value << 8 | value;
|
||||
}
|
||||
|
||||
private void swapEntityNameAffix(final String key, final CompoundTag tag) {
|
||||
final StringTag assetIdTag = tag.getStringTag("asset_id");
|
||||
final String assetId = assetIdTag.getValue();
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ package com.viaversion.viaversion.protocols.v1_21_11to26_1.data;
|
||||
import com.viaversion.nbt.tag.CompoundTag;
|
||||
import com.viaversion.viaversion.api.data.MappingDataBase;
|
||||
import com.viaversion.viaversion.api.data.MappingDataLoader;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import com.viaversion.viaversion.api.data.IdRanges;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
|
||||
public final class MappingData26_1 extends MappingDataBase {
|
||||
@@ -43,7 +43,7 @@ public final class MappingData26_1 extends MappingDataBase {
|
||||
pigSoundVariants = tag.getCompoundTag("pig_sound_variant");
|
||||
chickenSoundVariants = tag.getCompoundTag("chicken_sound_variant");
|
||||
|
||||
fluidBlockStates = new IntOpenHashSet(MappingDataLoader.INSTANCE.loadNBTFromFile("fluids-26.1.nbt").getIntArrayTag("fluids").getValue());
|
||||
fluidBlockStates = IdRanges.decode(MappingDataLoader.INSTANCE.loadNBTFromFile("fluids-26.1.nbt").getByteArrayTag("fluids"));
|
||||
}
|
||||
|
||||
public CompoundTag catSoundVariants() {
|
||||
|
||||
+9
-8
@@ -63,14 +63,12 @@ public final class BlockItemPacketRewriter26_1 extends StructuredItemRewriter<Cl
|
||||
}
|
||||
|
||||
final DataPalette blockPalette = section.palette(PaletteType.BLOCKS);
|
||||
for (int idx = 0; idx < ChunkSection.SIZE; idx++) {
|
||||
final int id = blockPalette.idAt(idx);
|
||||
if (Protocol1_21_11To26_1.MAPPINGS.fluidBlockStates().contains(id)) {
|
||||
// Needed for certain client-side fluid interactions
|
||||
section.setFluidCount(section.getFluidCount() + 1);
|
||||
}
|
||||
}
|
||||
blockPalette.forEachMatchingCoordinate(id -> Protocol1_21_11To26_1.MAPPINGS.fluidBlockStates().contains(id), $ -> {
|
||||
// Needed for certain client-side fluid interactions
|
||||
section.setFluidCount(section.getFluidCount() + 1);
|
||||
});
|
||||
}
|
||||
|
||||
protocol.getBlockRewriter().handleBlockEntities(chunk, wrapper.user());
|
||||
});
|
||||
}
|
||||
@@ -134,7 +132,10 @@ public final class BlockItemPacketRewriter26_1 extends StructuredItemRewriter<Cl
|
||||
container.replace(StructuredDataKey.ZOMBIE_NAUTILUS_VARIANT26_1, StructuredDataKey.ZOMBIE_NAUTILUS_VARIANT1_21_11, Either::left);
|
||||
container.replace(StructuredDataKey.DAMAGE_TYPE26_1, StructuredDataKey.DAMAGE_TYPE1_21_11, damageType -> new DamageType(Either.left(damageType)));
|
||||
container.replace(StructuredDataKey.PROVIDES_BANNER_PATTERNS26_1, StructuredDataKey.PROVIDES_BANNER_PATTERNS1_21_5, patterns -> tagOrNull(patterns.patterns()));
|
||||
container.replace(StructuredDataKey.DAMAGE_RESISTANT26_1, StructuredDataKey.DAMAGE_RESISTANT1_21_2, damageResistant -> new DamageResistant1_21_2(tagOrNull(damageResistant.types())));
|
||||
container.replace(StructuredDataKey.DAMAGE_RESISTANT26_1, StructuredDataKey.DAMAGE_RESISTANT1_21_2, damageResistant -> {
|
||||
final Key key = tagOrNull(damageResistant.types());
|
||||
return key != null ? new DamageResistant1_21_2(key) : null;
|
||||
});
|
||||
container.replace(StructuredDataKey.BLOCKS_ATTACKS26_1, StructuredDataKey.BLOCKS_ATTACKS1_21_5, blocksAttacks -> {
|
||||
if (blocksAttacks.bypassedBy() == null) {
|
||||
return blocksAttacks;
|
||||
|
||||
+2
-1
@@ -36,6 +36,7 @@ import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacke
|
||||
import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2;
|
||||
import com.viaversion.viaversion.rewriter.EntityRewriter;
|
||||
import com.viaversion.viaversion.rewriter.entitydata.EntityDataHandlerEvent;
|
||||
import com.viaversion.viaversion.util.UUIDUtil;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@@ -54,7 +55,7 @@ public final class EntityPacketRewriter1_21_5 extends EntityRewriter<Clientbound
|
||||
// No more special experience orb add packet
|
||||
protocol.registerClientbound(ClientboundPackets1_21_2.ADD_EXPERIENCE_ORB, ClientboundPackets1_21_5.ADD_ENTITY, wrapper -> {
|
||||
wrapper.passthrough(Types.VAR_INT); // Entity ID
|
||||
wrapper.write(Types.UUID, UUID.randomUUID()); // UUID...
|
||||
wrapper.write(Types.UUID, UUIDUtil.randomUUID()); // UUID...
|
||||
wrapper.write(Types.VAR_INT, EntityTypes1_21_5.EXPERIENCE_ORB.getId()); // Type
|
||||
wrapper.passthrough(Types.DOUBLE); // X
|
||||
wrapper.passthrough(Types.DOUBLE); // Y
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ public class ItemHashStorage1_21_5 implements ItemHasher {
|
||||
return;
|
||||
}
|
||||
|
||||
final HashOps hasher = new HashOps(context, HashFunction.CRC32C);
|
||||
final HashOps hasher = new HashOps(context, HashFunction.crc32c());
|
||||
final int hash = hasher.context().isSupported(structuredData.key()) ? hash(hasher, structuredData) : ItemHasherBase.UNKNOWN_HASH;
|
||||
if (hash == ItemHasherBase.UNKNOWN_HASH) {
|
||||
return;
|
||||
|
||||
+3
-2
@@ -18,6 +18,7 @@
|
||||
package com.viaversion.viaversion.protocols.v1_21_7to1_21_9.rewriter;
|
||||
|
||||
import com.viaversion.nbt.tag.CompoundTag;
|
||||
import com.viaversion.viaversion.api.data.entity.TrackedEntity;
|
||||
import com.viaversion.viaversion.api.minecraft.BlockPosition;
|
||||
import com.viaversion.viaversion.api.minecraft.GlobalBlockPosition;
|
||||
import com.viaversion.viaversion.api.minecraft.Vector3d;
|
||||
@@ -56,8 +57,8 @@ public final class EntityPacketRewriter1_21_9 extends EntityRewriter<Clientbound
|
||||
wrapper.passthrough(Types.BYTE); // Yaw
|
||||
wrapper.passthrough(Types.BYTE); // Head yaw
|
||||
final int data = wrapper.passthrough(Types.VAR_INT);
|
||||
final EntityType entityType = trackAndRewrite(wrapper, entityTypeId, entityId);
|
||||
if (protocol.getMappingData() != null && entityType == EntityTypes1_21_9.FALLING_BLOCK) {
|
||||
final TrackedEntity entity = trackAndRewrite(wrapper, entityTypeId, entityId);
|
||||
if (protocol.getMappingData() != null && entity.entityType() == EntityTypes1_21_9.FALLING_BLOCK) {
|
||||
final int mappedBlockStateId = protocol.getMappingData().getNewBlockStateId(data);
|
||||
wrapper.set(Types.VAR_INT, 2, mappedBlockStateId);
|
||||
}
|
||||
|
||||
+1
-1
@@ -588,7 +588,7 @@ public final class BlockItemPacketRewriter1_21_2 extends StructuredItemRewriter<
|
||||
|
||||
// Back from json in the string tag to plain text
|
||||
final StringTag customName = TagUtil.getNamespacedStringTag(itemComponentsTag, "custom_name");
|
||||
if (customName == null) {
|
||||
if (customName == null || customName.getValue().equals("null")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+14
-17
@@ -17,20 +17,20 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.protocols.v1_21to1_21_2.storage;
|
||||
|
||||
import com.viaversion.viaversion.api.connection.StorableObject;
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.data.entity.TrackedEntity;
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_2;
|
||||
import com.viaversion.viaversion.api.minecraft.entitydata.EntityData;
|
||||
import com.viaversion.viaversion.data.entity.EntityTrackerBase;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public final class EntityTracker1_21_2 extends EntityTrackerBase {
|
||||
|
||||
private final Int2ObjectMap<BoatEntity> boats = new Int2ObjectOpenHashMap<>();
|
||||
private double playerMaxHealthAttributeValue = 20F;
|
||||
|
||||
public EntityTracker1_21_2(final UserConnection connection) {
|
||||
@@ -39,25 +39,21 @@ public final class EntityTracker1_21_2 extends EntityTrackerBase {
|
||||
|
||||
public BoatEntity trackBoatEntity(final int entityId, final UUID uuid, final int data) {
|
||||
final BoatEntity entity = new BoatEntity(uuid, data);
|
||||
boats.put(entityId, entity);
|
||||
entity(entityId).put(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public BoatEntity trackedBoatEntity(final int entityId) {
|
||||
return boats.get(entityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntity(final int id) {
|
||||
super.removeEntity(id);
|
||||
boats.remove(id);
|
||||
public @Nullable BoatEntity trackedBoatEntity(final int entityId) {
|
||||
final TrackedEntity entity = entity(entityId);
|
||||
return entity != null ? entity.get(BoatEntity.class) : null;
|
||||
}
|
||||
|
||||
public void updateBoatType(final int entityId, final EntityType type) {
|
||||
final BoatEntity entity = boats.get(entityId);
|
||||
final BoatEntity boatEntity = trackedBoatEntity(entityId);
|
||||
removeEntity(entityId);
|
||||
boats.put(entityId, entity);
|
||||
addEntity(entityId, type);
|
||||
|
||||
final TrackedEntity newEntity = addEntity(entityId, type);
|
||||
newEntity.put(boatEntity);
|
||||
}
|
||||
|
||||
public double playerMaxHealthAttributeValue() {
|
||||
@@ -68,7 +64,8 @@ public final class EntityTracker1_21_2 extends EntityTrackerBase {
|
||||
this.playerMaxHealthAttributeValue = playerMaxHealthAttributeValue;
|
||||
}
|
||||
|
||||
public static class BoatEntity {
|
||||
// Track boats to allow boat type changes from the default type -> respawn as new entity
|
||||
public static final class BoatEntity implements StorableObject {
|
||||
|
||||
private final List<EntityData> entityData = new ArrayList<>();
|
||||
|
||||
@@ -136,7 +133,7 @@ public final class EntityTracker1_21_2 extends EntityTrackerBase {
|
||||
return entityData;
|
||||
}
|
||||
|
||||
public int[] passengers() {
|
||||
public int @Nullable [] passengers() {
|
||||
return passengers;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-4
@@ -20,6 +20,7 @@ package com.viaversion.viaversion.protocols.v1_8to1_9.storage;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.data.entity.TrackedEntity;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossBar;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
|
||||
@@ -39,6 +40,7 @@ import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ClientboundPackets1_
|
||||
import com.viaversion.viaversion.protocols.v1_8to1_9.provider.BossBarProvider;
|
||||
import com.viaversion.viaversion.protocols.v1_8to1_9.provider.EntityIdProvider;
|
||||
import com.viaversion.viaversion.util.ComponentUtil;
|
||||
import com.viaversion.viaversion.util.UUIDUtil;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
@@ -51,6 +53,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class EntityTracker1_9 extends EntityTrackerBase {
|
||||
public static final String WITHER_TRANSLATABLE = "{\"translate\":\"entity.WitherBoss.name\"}";
|
||||
@@ -79,7 +82,7 @@ public class EntityTracker1_9 extends EntityTrackerBase {
|
||||
}
|
||||
|
||||
public UUID getEntityUUID(int id) {
|
||||
return uuidMap.computeIfAbsent(id, k -> UUID.randomUUID());
|
||||
return uuidMap.computeIfAbsent(id, k -> UUIDUtil.randomUUID());
|
||||
}
|
||||
|
||||
public void setSecondHand(Item item) {
|
||||
@@ -134,9 +137,7 @@ public class EntityTracker1_9 extends EntityTrackerBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntity(int entityId) {
|
||||
super.removeEntity(entityId);
|
||||
|
||||
public @Nullable TrackedEntity removeEntity(int entityId) {
|
||||
vehicleMap.remove(entityId);
|
||||
uuidMap.remove(entityId);
|
||||
validBlocking.remove(entityId);
|
||||
@@ -148,6 +149,8 @@ public class EntityTracker1_9 extends EntityTrackerBase {
|
||||
// Send to provider
|
||||
Via.getManager().getProviders().get(BossBarProvider.class).handleRemove(user(), bar.getId());
|
||||
}
|
||||
|
||||
return super.removeEntity(entityId);
|
||||
}
|
||||
|
||||
public boolean interactedBlockRecently(final int x, final int y, final int z) {
|
||||
|
||||
+7
-11
@@ -98,17 +98,13 @@ public class Protocol1_9_1To1_9_3 extends AbstractProtocol<ClientboundPackets1_9
|
||||
if (section == null) continue;
|
||||
DataPalette blocks = section.palette(PaletteType.BLOCKS);
|
||||
|
||||
for (int idx = 0; idx < ChunkSection.SIZE; idx++) {
|
||||
int id = blocks.idAt(idx) >> 4;
|
||||
if (FakeTileEntities1_9_1.isTileEntity(id)) {
|
||||
tags.add(FakeTileEntities1_9_1.createTileEntity(
|
||||
ChunkSection.xFromIndex(idx) + (chunk.getX() << 4),
|
||||
ChunkSection.yFromIndex(idx) + (s << 4),
|
||||
ChunkSection.zFromIndex(idx) + (chunk.getZ() << 4),
|
||||
id
|
||||
));
|
||||
}
|
||||
}
|
||||
final int ySection = s;
|
||||
blocks.forEachMatchingCoordinate(id -> FakeTileEntities1_9_1.isTileEntity(id >> 4), idx -> tags.add(FakeTileEntities1_9_1.createTileEntity(
|
||||
ChunkSection.xFromIndex(idx) + (chunk.getX() << 4),
|
||||
ChunkSection.yFromIndex(idx) + (ySection << 4),
|
||||
ChunkSection.zFromIndex(idx) + (chunk.getZ() << 4),
|
||||
blocks.idAt(idx) >> 4
|
||||
)));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+4
-35
@@ -17,8 +17,6 @@
|
||||
*/
|
||||
package com.viaversion.viaversion.protocols.v26_1to26_2;
|
||||
|
||||
import com.viaversion.nbt.tag.CompoundTag;
|
||||
import com.viaversion.nbt.tag.Tag;
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.data.MappingData;
|
||||
import com.viaversion.viaversion.api.data.MappingDataBase;
|
||||
@@ -49,6 +47,7 @@ import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundCon
|
||||
import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundConfigurationPackets1_21_9;
|
||||
import com.viaversion.viaversion.protocols.v26_1to26_2.rewriter.BlockItemPacketRewriter26_2;
|
||||
import com.viaversion.viaversion.protocols.v26_1to26_2.rewriter.EntityPacketRewriter26_2;
|
||||
import com.viaversion.viaversion.protocols.v26_1to26_2.rewriter.RegistryDataRewriter26_2;
|
||||
import com.viaversion.viaversion.protocols.v26_1to26_2.storage.Encrypted;
|
||||
import com.viaversion.viaversion.rewriter.BlockRewriter;
|
||||
import com.viaversion.viaversion.rewriter.ParticleRewriter;
|
||||
@@ -57,7 +56,6 @@ import com.viaversion.viaversion.rewriter.RegistryDataRewriter;
|
||||
import com.viaversion.viaversion.rewriter.TagRewriter;
|
||||
import com.viaversion.viaversion.rewriter.block.BlockRewriter1_21_5;
|
||||
import com.viaversion.viaversion.rewriter.text.NBTComponentRewriter;
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap;
|
||||
@@ -71,38 +69,9 @@ public final class Protocol26_1To26_2 extends AbstractProtocol<ClientboundPacket
|
||||
private final ParticleRewriter<ClientboundPacket26_1> particleRewriter = new ParticleRewriter<>(this);
|
||||
private final TagRewriter<ClientboundPacket26_1> tagRewriter = new TagRewriter<>(this);
|
||||
private final NBTComponentRewriter<ClientboundPacket26_1> componentRewriter = new NBTComponentRewriter<>(this);
|
||||
private final RegistryDataRewriter registryDataRewriter = new RegistryDataRewriter(this) {
|
||||
@Override
|
||||
public void updateEnchantmentTerm(final CompoundTag term) {
|
||||
super.updateEnchantmentTerm(term);
|
||||
|
||||
final String condition = term.getString("condition");
|
||||
if (Key.equals(condition, "entity_properties")) {
|
||||
final CompoundTag predicate = term.getCompoundTag("predicate");
|
||||
final CompoundTag typeSpecific = predicate.removeUnchecked("type_specific");
|
||||
if (typeSpecific != null) {
|
||||
updateTypeSpecificTerm(predicate, typeSpecific);
|
||||
return;
|
||||
}
|
||||
|
||||
final Tag typeTag = predicate.remove("type");
|
||||
if (typeTag != null) {
|
||||
predicate.put("entity_type", typeTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
private final RegistryDataRewriter registryDataRewriter = new RegistryDataRewriter26_2(this);
|
||||
private final RecipeDisplayRewriter1_21_5<ClientboundPacket26_1> recipeRewriter = new RecipeDisplayRewriter1_21_5<>(this);
|
||||
|
||||
private void updateTypeSpecificTerm(final CompoundTag predicate, final CompoundTag typeSpecific) {
|
||||
final String type = typeSpecific.getString("type");
|
||||
final String strippedType = Key.stripMinecraftNamespace(type);
|
||||
if (strippedType.equals("player") || strippedType.equals("lightning")
|
||||
|| strippedType.equals("fishing_hook") || strippedType.equals("raider")) {
|
||||
predicate.put("type_specific/" + strippedType, typeSpecific);
|
||||
}
|
||||
}
|
||||
|
||||
private final UUID sessionId = UUID.randomUUID(); // One shared session
|
||||
|
||||
public Protocol26_1To26_2() {
|
||||
super(ClientboundPacket26_1.class, ClientboundPacket26_1.class, ServerboundPacket26_1.class, ServerboundPacket26_1.class);
|
||||
@@ -137,7 +106,7 @@ public final class Protocol26_1To26_2 extends AbstractProtocol<ClientboundPacket
|
||||
wrapper.passthrough(Types.STRING); // Name
|
||||
wrapper.passthrough(Types.PROFILE_PROPERTY_ARRAY);
|
||||
|
||||
wrapper.write(Types.UUID, UUID.randomUUID()); // Session ID
|
||||
wrapper.write(Types.UUID, sessionId);
|
||||
});
|
||||
|
||||
registerServerbound(State.LOGIN, ServerboundLoginPackets.ENCRYPTION_KEY, wrapper -> {
|
||||
|
||||
+129
-2
@@ -20,13 +20,18 @@ package com.viaversion.viaversion.protocols.v26_1to26_2.rewriter;
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
|
||||
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes26_2;
|
||||
import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes26_1;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.protocol.remapper.PacketHandler;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPacket26_1;
|
||||
import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPackets26_1;
|
||||
import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ServerboundPackets26_1;
|
||||
import com.viaversion.viaversion.protocols.v26_1to26_2.Protocol26_1To26_2;
|
||||
import com.viaversion.viaversion.protocols.v26_1to26_2.storage.Encrypted;
|
||||
import com.viaversion.viaversion.protocols.v26_1to26_2.storage.FakeEntityId;
|
||||
import com.viaversion.viaversion.rewriter.EntityRewriter;
|
||||
import com.viaversion.viaversion.rewriter.entitydata.EntityDataHandler;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public final class EntityPacketRewriter26_2 extends EntityRewriter<ClientboundPacket26_1, Protocol26_1To26_2> {
|
||||
|
||||
@@ -41,13 +46,19 @@ public final class EntityPacketRewriter26_2 extends EntityRewriter<ClientboundPa
|
||||
protocol.registerServerbound(ServerboundPackets26_1.SPECTATE_ENTITY, wrapper -> {
|
||||
final Integer entityId = wrapper.read(Types.OPTIONAL_VAR_INT);
|
||||
if (entityId != null) {
|
||||
wrapper.write(Types.VAR_INT, entityId);
|
||||
wrapper.write(Types.VAR_INT, toOriginalEntityId(wrapper, entityId));
|
||||
} else {
|
||||
wrapper.cancel();
|
||||
// if entity id is null, they just left-clicked the air, which used to be handled by the swing packet in 1.21.10 and below (1.21.11 and 26.1.X does not send spectator left clicks at all)
|
||||
wrapper.setPacketType(ServerboundPackets26_1.SWING);
|
||||
wrapper.write(Types.VAR_INT, 0); // Hand to Swing, 0 = Main Hand
|
||||
}
|
||||
});
|
||||
|
||||
protocol.appendClientbound(ClientboundPackets26_1.LOGIN, wrapper -> {
|
||||
wrapper.user().put(new FakeEntityId(ThreadLocalRandom.current().nextInt(Integer.MIN_VALUE, -1)));
|
||||
final int entityId = wrapper.get(Types.INT, 0);
|
||||
wrapper.set(Types.INT, 0, toFakeEntityId(wrapper, entityId));
|
||||
|
||||
// Continuing from the registered handler
|
||||
wrapper.passthrough(Types.BYTE); // Previous gamemode
|
||||
wrapper.passthrough(Types.BOOLEAN); // Debug
|
||||
@@ -58,6 +69,112 @@ public final class EntityPacketRewriter26_2 extends EntityRewriter<ClientboundPa
|
||||
final boolean onlineMode = wrapper.user().has(Encrypted.class);
|
||||
wrapper.write(Types.BOOLEAN, onlineMode);
|
||||
});
|
||||
|
||||
// Iterate through **all** packets containing an entity ID and replace it with a randomized negative value if the ID is zero,
|
||||
// since the client no longer accepts zero as a valid entity ID.
|
||||
final PacketHandler setFakeEntityId = wrapper -> wrapper.set(Types.VAR_INT, 0, toFakeEntityId(wrapper, wrapper.get(Types.VAR_INT, 0)));
|
||||
protocol.appendClientbound(ClientboundPackets26_1.ADD_ENTITY, wrapper -> {
|
||||
setFakeEntityId.handle(wrapper);
|
||||
final EntityType entityType = typeFromId(wrapper.get(Types.VAR_INT, 1));
|
||||
if (entityType != null && entityType.isOrHasParent(EntityTypes26_2.PROJECTILE)) {
|
||||
// For projectiles, the data field holds the owner entity id
|
||||
wrapper.set(Types.VAR_INT, 2, toFakeEntityId(wrapper, wrapper.get(Types.VAR_INT, 2)));
|
||||
}
|
||||
});
|
||||
protocol.appendClientbound(ClientboundPackets26_1.SET_ENTITY_DATA, setFakeEntityId);
|
||||
protocol.appendClientbound(ClientboundPackets26_1.SET_EQUIPMENT, setFakeEntityId);
|
||||
protocol.appendClientbound(ClientboundPackets26_1.PLAYER_COMBAT_KILL, setFakeEntityId);
|
||||
protocol.appendClientbound(ClientboundPackets26_1.UPDATE_ATTRIBUTES, setFakeEntityId);
|
||||
protocol.appendClientbound(ClientboundPackets26_1.REMOVE_ENTITIES, wrapper -> {
|
||||
final int[] entities = wrapper.get(Types.VAR_INT_ARRAY_PRIMITIVE, 0);
|
||||
for (int i = 0; i < entities.length; i++) {
|
||||
entities[i] = toFakeEntityId(wrapper, entities[i]);
|
||||
}
|
||||
});
|
||||
|
||||
final PacketHandler toFakeEntityId = wrapper -> wrapper.write(Types.VAR_INT, toFakeEntityId(wrapper, wrapper.read(Types.VAR_INT)));
|
||||
final PacketHandler toOriginalEntityId = wrapper -> wrapper.write(Types.VAR_INT, toOriginalEntityId(wrapper, wrapper.read(Types.VAR_INT)));
|
||||
protocol.registerClientbound(ClientboundPackets26_1.ANIMATE, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.DEBUG_ENTITY_VALUE, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.ENTITY_POSITION_SYNC, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.HURT_ANIMATION, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.MOVE_ENTITY_POS, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.MOVE_ENTITY_ROT, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.MOVE_ENTITY_POS_ROT, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.MOVE_MINECART_ALONG_TRACK, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.REMOVE_MOB_EFFECT, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.ROTATE_HEAD, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.SET_CAMERA, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.SET_ENTITY_MOTION, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.UPDATE_MOB_EFFECT, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.TELEPORT_ENTITY, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.PROJECTILE_POWER, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.BLOCK_DESTRUCTION, toFakeEntityId);
|
||||
protocol.registerClientbound(ClientboundPackets26_1.ENTITY_EVENT, wrapper -> wrapper.write(Types.INT, toFakeEntityId(wrapper, wrapper.read(Types.INT))));
|
||||
protocol.registerClientbound(ClientboundPackets26_1.DAMAGE_EVENT, wrapper -> {
|
||||
toFakeEntityId.handle(wrapper); // Entity id
|
||||
wrapper.passthrough(Types.VAR_INT); // Source type
|
||||
final Integer sourceCauseId = wrapper.passthrough(Types.OPTIONAL_VAR_INT);
|
||||
if (sourceCauseId != null) {
|
||||
wrapper.set(Types.OPTIONAL_VAR_INT, 0, toFakeEntityId(wrapper, sourceCauseId));
|
||||
}
|
||||
final Integer sourceDirectId = wrapper.passthrough(Types.OPTIONAL_VAR_INT);
|
||||
if (sourceDirectId != null) {
|
||||
wrapper.set(Types.OPTIONAL_VAR_INT, 1, toFakeEntityId(wrapper, sourceDirectId));
|
||||
}
|
||||
});
|
||||
protocol.appendClientbound(ClientboundPackets26_1.SOUND_ENTITY, wrapper -> {
|
||||
wrapper.passthrough(Types.VAR_INT); // Source
|
||||
toFakeEntityId.handle(wrapper);
|
||||
});
|
||||
protocol.registerClientbound(ClientboundPackets26_1.PLAYER_LOOK_AT, wrapper -> {
|
||||
wrapper.passthrough(Types.VAR_INT); // From anchor
|
||||
wrapper.passthrough(Types.DOUBLE); // X
|
||||
wrapper.passthrough(Types.DOUBLE); // Y
|
||||
wrapper.passthrough(Types.DOUBLE); // Z
|
||||
final boolean atEntity = wrapper.passthrough(Types.BOOLEAN);
|
||||
if (atEntity) {
|
||||
toFakeEntityId.handle(wrapper);
|
||||
}
|
||||
});
|
||||
protocol.registerClientbound(ClientboundPackets26_1.SET_PASSENGERS, wrapper -> {
|
||||
toFakeEntityId.handle(wrapper); // Vehicle
|
||||
final int[] passengers = wrapper.passthrough(Types.VAR_INT_ARRAY_PRIMITIVE);
|
||||
for (int i = 0; i < passengers.length; i++) {
|
||||
passengers[i] = toFakeEntityId(wrapper, passengers[i]);
|
||||
}
|
||||
});
|
||||
protocol.registerClientbound(ClientboundPackets26_1.MOUNT_SCREEN_OPEN, wrapper -> {
|
||||
wrapper.passthrough(Types.VAR_INT); // Container id
|
||||
wrapper.passthrough(Types.VAR_INT); // Inventory columns
|
||||
wrapper.write(Types.INT, toFakeEntityId(wrapper, wrapper.read(Types.INT)));
|
||||
});
|
||||
protocol.registerClientbound(ClientboundPackets26_1.SET_ENTITY_LINK, wrapper -> {
|
||||
wrapper.write(Types.INT, toFakeEntityId(wrapper, wrapper.read(Types.INT))); // Source id
|
||||
// Destination id can be zero - ignore it
|
||||
});
|
||||
protocol.registerClientbound(ClientboundPackets26_1.TAKE_ITEM_ENTITY, wrapper -> {
|
||||
toFakeEntityId.handle(wrapper); // Item id
|
||||
toFakeEntityId.handle(wrapper); // Player id
|
||||
});
|
||||
|
||||
protocol.registerServerbound(ServerboundPackets26_1.ATTACK, toOriginalEntityId);
|
||||
protocol.registerServerbound(ServerboundPackets26_1.INTERACT, toOriginalEntityId);
|
||||
protocol.registerServerbound(ServerboundPackets26_1.PICK_ITEM_FROM_ENTITY, toOriginalEntityId);
|
||||
protocol.registerServerbound(ServerboundPackets26_1.PLAYER_COMMAND, toOriginalEntityId);
|
||||
protocol.registerServerbound(ServerboundPackets26_1.SET_COMMAND_MINECART, toOriginalEntityId);
|
||||
protocol.registerServerbound(ServerboundPackets26_1.ENTITY_TAG_QUERY, wrapper -> {
|
||||
wrapper.passthrough(Types.VAR_INT); // Transaction id
|
||||
toOriginalEntityId.handle(wrapper);
|
||||
});
|
||||
}
|
||||
|
||||
private int toFakeEntityId(final PacketWrapper wrapper, final int entityId) {
|
||||
return entityId == 0 ? wrapper.user().get(FakeEntityId.class).id() : entityId;
|
||||
}
|
||||
|
||||
private int toOriginalEntityId(final PacketWrapper wrapper, final int entityId) {
|
||||
return entityId < -1 && entityId == wrapper.user().get(FakeEntityId.class).id() ? 0 : entityId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,6 +193,16 @@ public final class EntityPacketRewriter26_2 extends EntityRewriter<ClientboundPa
|
||||
|
||||
filter().type(EntityTypes26_2.ABSTRACT_CUBE_MOB).addIndex(16); // baby
|
||||
filter().type(EntityTypes26_2.ABSTRACT_CUBE_MOB).addIndex(17); // age locked
|
||||
|
||||
final EntityDataHandler toFakeEntityId = (event, data) -> {
|
||||
final Integer target = data.value();
|
||||
if (target != null) {
|
||||
data.setValue(target == 0 ? event.user().get(FakeEntityId.class).id() : target);
|
||||
}
|
||||
};
|
||||
filter().type(EntityTypes26_2.FROG).index(19).handler(toFakeEntityId); // Tongue target
|
||||
filter().type(EntityTypes26_2.FIREWORK_ROCKET).index(9).handler(toFakeEntityId); // Attached to target
|
||||
// Guardian & Wither use zero as default - ignore them
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
|
||||
* Copyright (C) 2016-2026 ViaVersion and 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.viaversion.viaversion.protocols.v26_1to26_2.rewriter;
|
||||
|
||||
import com.viaversion.nbt.tag.CompoundTag;
|
||||
import com.viaversion.nbt.tag.StringTag;
|
||||
import com.viaversion.nbt.tag.Tag;
|
||||
import com.viaversion.viaversion.protocols.v26_1to26_2.Protocol26_1To26_2;
|
||||
import com.viaversion.viaversion.rewriter.RegistryDataRewriter;
|
||||
import com.viaversion.viaversion.util.Key;
|
||||
|
||||
public final class RegistryDataRewriter26_2 extends RegistryDataRewriter {
|
||||
|
||||
public RegistryDataRewriter26_2(final Protocol26_1To26_2 protocol) {
|
||||
super(protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateEnchantmentTerm(final CompoundTag term) {
|
||||
super.updateEnchantmentTerm(term);
|
||||
|
||||
final String condition = term.getString("condition");
|
||||
if (Key.equals(condition, "entity_properties")) {
|
||||
final CompoundTag predicate = term.getCompoundTag("predicate");
|
||||
if (predicate != null) {
|
||||
updateEntityPredicate(predicate);
|
||||
}
|
||||
} else if (Key.equals(condition, "damage_source_properties")) {
|
||||
final CompoundTag predicate = term.getCompoundTag("predicate");
|
||||
if (predicate != null) {
|
||||
updateNestedEntityPredicate(predicate, "source_entity");
|
||||
updateNestedEntityPredicate(predicate, "direct_entity");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Entity predicates are now a map of sub predicate type -> data, with the previously inlined fields keeping their names
|
||||
private void updateEntityPredicate(final CompoundTag predicate) {
|
||||
final CompoundTag typeSpecific = predicate.removeUnchecked("type_specific");
|
||||
if (typeSpecific != null) {
|
||||
updateTypeSpecificTerm(predicate, typeSpecific);
|
||||
}
|
||||
|
||||
final Tag typeTag = predicate.remove("type");
|
||||
if (typeTag != null) {
|
||||
predicate.put("entity_type", typeTag);
|
||||
}
|
||||
|
||||
updateNestedEntityPredicate(predicate, "vehicle");
|
||||
updateNestedEntityPredicate(predicate, "passenger");
|
||||
updateNestedEntityPredicate(predicate, "targeted_entity");
|
||||
}
|
||||
|
||||
private void updateNestedEntityPredicate(final CompoundTag predicate, final String key) {
|
||||
final CompoundTag nestedPredicate = predicate.getCompoundTag(key);
|
||||
if (nestedPredicate != null) {
|
||||
updateEntityPredicate(nestedPredicate);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTypeSpecificTerm(final CompoundTag predicate, final CompoundTag typeSpecific) {
|
||||
final StringTag type = typeSpecific.removeUnchecked("type");
|
||||
if (type == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String strippedType = Key.stripMinecraftNamespace(type.getValue());
|
||||
switch (strippedType) {
|
||||
case "player", "lightning", "fishing_hook", "raider", "sheep" -> predicate.put("type_specific/" + strippedType, typeSpecific);
|
||||
case "slime" -> predicate.put("type_specific/cube_mob", typeSpecific);
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
|
||||
* Copyright (C) 2016-2026 ViaVersion and 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.viaversion.viaversion.protocols.v26_1to26_2.storage;
|
||||
|
||||
import com.viaversion.viaversion.api.connection.StorableObject;
|
||||
|
||||
public record FakeEntityId(int id) implements StorableObject {
|
||||
}
|
||||
@@ -287,12 +287,18 @@ public class BlockRewriter<C extends ClientboundPacketType> {
|
||||
}
|
||||
|
||||
protected Type<Chunk> createChunkType(ChunkTypeSupplier supplier, EntityTracker tracker, boolean mapped) {
|
||||
final Mappings mappings = protocol.getMappingData().getBlockStateMappings();
|
||||
return supplier.supply(
|
||||
tracker.currentWorldSectionHeight(),
|
||||
MathUtil.ceilLog2(mapped ? mappings.mappedSize() : mappings.size()),
|
||||
MathUtil.ceilLog2(tracker.biomesSent())
|
||||
);
|
||||
// Cached in the tracker and invalidated when the section height or biome count changes
|
||||
Type<Chunk> type = tracker.chunkType(mapped);
|
||||
if (type == null) {
|
||||
final Mappings blockStateMappings = protocol.getMappingData().getBlockStateMappings();
|
||||
type = supplier.supply(
|
||||
tracker.currentWorldSectionHeight(),
|
||||
MathUtil.ceilLog2(mapped ? blockStateMappings.mappedSize() : blockStateMappings.size()),
|
||||
MathUtil.ceilLog2(tracker.biomesSent())
|
||||
);
|
||||
tracker.setChunkType(mapped, type);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
public void registerBlockEntityData1_18(C packetType) {
|
||||
|
||||
@@ -63,6 +63,7 @@ public abstract class EntityRewriter<C extends ClientboundPacketType, T extends
|
||||
protected final List<EntityDataFilter> entityDataFilters = new ArrayList<>();
|
||||
protected final boolean trackMappedType;
|
||||
protected Mappings typeMappings;
|
||||
private EntityType fallingBlockType;
|
||||
|
||||
protected EntityRewriter(T protocol) {
|
||||
this(protocol, true);
|
||||
@@ -315,7 +316,7 @@ public abstract class EntityRewriter<C extends ClientboundPacketType, T extends
|
||||
handler(wrapper -> {
|
||||
int entityId = wrapper.get(Types.VAR_INT, 0);
|
||||
EntityType entityType = tracker(wrapper.user()).entityType(entityId);
|
||||
if (entityType == typeFromId("falling_block")) {
|
||||
if (entityType == fallingBlockType()) {
|
||||
wrapper.set(Types.INT, 0, protocol.getMappingData().getNewBlockStateId(wrapper.get(Types.INT, 0)));
|
||||
}
|
||||
});
|
||||
@@ -324,59 +325,34 @@ public abstract class EntityRewriter<C extends ClientboundPacketType, T extends
|
||||
}
|
||||
|
||||
public void registerTrackerWithData1_19(C packetType) {
|
||||
protocol.registerClientbound(packetType, wrapper -> {
|
||||
final int entityId = wrapper.passthrough(Types.VAR_INT);
|
||||
wrapper.passthrough(Types.UUID); // Entity UUID
|
||||
final int entityTypeId = wrapper.passthrough(Types.VAR_INT);
|
||||
wrapper.passthrough(Types.DOUBLE); // X
|
||||
wrapper.passthrough(Types.DOUBLE); // Y
|
||||
wrapper.passthrough(Types.DOUBLE); // Z
|
||||
wrapper.passthrough(Types.BYTE); // Pitch
|
||||
wrapper.passthrough(Types.BYTE); // Yaw
|
||||
wrapper.passthrough(Types.BYTE); // Head yaw
|
||||
final int data = wrapper.passthrough(Types.VAR_INT);
|
||||
|
||||
final EntityType entityType = trackAndRewrite(wrapper, entityTypeId, entityId);
|
||||
if (protocol.getMappingData() != null && entityType == typeFromId("falling_block")) {
|
||||
final int mappedBlockStateId = protocol.getMappingData().getNewBlockStateId(data);
|
||||
wrapper.set(Types.VAR_INT, 2, mappedBlockStateId);
|
||||
}
|
||||
});
|
||||
protocol.registerClientbound(packetType, wrapper -> handleAddEntity(wrapper, false));
|
||||
}
|
||||
|
||||
public void registerTrackerWithData1_21_9(C packetType) {
|
||||
protocol.registerClientbound(packetType, wrapper -> {
|
||||
final int entityId = wrapper.passthrough(Types.VAR_INT);
|
||||
wrapper.passthrough(Types.UUID); // Entity UUID
|
||||
final int entityTypeId = wrapper.passthrough(Types.VAR_INT);
|
||||
wrapper.passthrough(Types.DOUBLE); // X
|
||||
wrapper.passthrough(Types.DOUBLE); // Y
|
||||
wrapper.passthrough(Types.DOUBLE); // Z
|
||||
wrapper.passthrough(Types.LOW_PRECISION_VECTOR); // Movement
|
||||
wrapper.passthrough(Types.BYTE); // Pitch
|
||||
wrapper.passthrough(Types.BYTE); // Yaw
|
||||
wrapper.passthrough(Types.BYTE); // Head yaw
|
||||
final int data = wrapper.passthrough(Types.VAR_INT);
|
||||
final EntityType entityType = trackAndRewrite(wrapper, entityTypeId, entityId);
|
||||
if (protocol.getMappingData() != null && entityType == typeFromId("falling_block")) {
|
||||
final int mappedBlockStateId = protocol.getMappingData().getNewBlockStateId(data);
|
||||
wrapper.set(Types.VAR_INT, 2, mappedBlockStateId);
|
||||
}
|
||||
});
|
||||
protocol.registerClientbound(packetType, wrapper -> handleAddEntity(wrapper, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an entity tracker for the extra spawn packets like player, painting, or xp orb spawns.
|
||||
*
|
||||
* @param packetType packet type
|
||||
* @param entityType entity type
|
||||
* @param intType int type of the entity id
|
||||
*/
|
||||
public void registerTracker(C packetType, EntityType entityType, Type<Integer> intType) {
|
||||
protocol.registerClientbound(packetType, wrapper -> {
|
||||
int entityId = wrapper.passthrough(intType);
|
||||
tracker(wrapper.user()).addEntity(entityId, entityType);
|
||||
});
|
||||
protected TrackedEntity handleAddEntity(final PacketWrapper wrapper, final boolean hasMovement) {
|
||||
final int entityId = wrapper.passthrough(Types.VAR_INT);
|
||||
wrapper.passthrough(Types.UUID); // Entity UUID
|
||||
final int entityTypeId = wrapper.passthrough(Types.VAR_INT);
|
||||
wrapper.passthrough(Types.DOUBLE); // X
|
||||
wrapper.passthrough(Types.DOUBLE); // Y
|
||||
wrapper.passthrough(Types.DOUBLE); // Z
|
||||
if (hasMovement) {
|
||||
wrapper.passthrough(Types.LOW_PRECISION_VECTOR); // Movement
|
||||
}
|
||||
wrapper.passthrough(Types.BYTE); // Pitch
|
||||
wrapper.passthrough(Types.BYTE); // Yaw
|
||||
wrapper.passthrough(Types.BYTE); // Head yaw
|
||||
final int data = wrapper.passthrough(Types.VAR_INT);
|
||||
|
||||
final TrackedEntity entity = trackAndRewrite(wrapper, entityTypeId, entityId);
|
||||
if (protocol.getMappingData() != null && entity.entityType() == fallingBlockType()) {
|
||||
final int mappedBlockStateId = protocol.getMappingData().getNewBlockStateId(data);
|
||||
wrapper.set(Types.VAR_INT, 2, mappedBlockStateId);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -386,7 +362,10 @@ public abstract class EntityRewriter<C extends ClientboundPacketType, T extends
|
||||
* @param entityType entity type
|
||||
*/
|
||||
public void registerTracker(C packetType, EntityType entityType) {
|
||||
registerTracker(packetType, entityType, Types.VAR_INT);
|
||||
protocol.registerClientbound(packetType, wrapper -> {
|
||||
int entityId = wrapper.passthrough(Types.VAR_INT);
|
||||
tracker(wrapper.user()).addEntity(entityId, entityType);
|
||||
});
|
||||
}
|
||||
|
||||
public void registerRemoveEntities(C packetType) {
|
||||
@@ -608,7 +587,7 @@ public abstract class EntityRewriter<C extends ClientboundPacketType, T extends
|
||||
tracker(connection).setDimensions(dimensionDataMap);
|
||||
}
|
||||
|
||||
public EntityType trackAndRewrite(final PacketWrapper wrapper, final int typeId, final int entityId) {
|
||||
public TrackedEntity trackAndRewrite(final PacketWrapper wrapper, final int typeId, final int entityId) {
|
||||
final int mappedTypeId = newEntityId(typeId);
|
||||
if (mappedTypeId != typeId) {
|
||||
wrapper.set(Types.VAR_INT, 1, mappedTypeId);
|
||||
@@ -616,11 +595,11 @@ public abstract class EntityRewriter<C extends ClientboundPacketType, T extends
|
||||
|
||||
final EntityType entityType = typeFromId(trackMappedType ? mappedTypeId : typeId);
|
||||
if (entityType == null) {
|
||||
// Legacy protocols for invalid ids
|
||||
return null;
|
||||
}
|
||||
|
||||
tracker(wrapper.user()).addEntity(entityId, entityType);
|
||||
return entityType;
|
||||
return tracker(wrapper.user()).addEntity(entityId, entityType);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -675,6 +654,13 @@ public abstract class EntityRewriter<C extends ClientboundPacketType, T extends
|
||||
};
|
||||
}
|
||||
|
||||
protected EntityType fallingBlockType() {
|
||||
if (fallingBlockType == null) {
|
||||
fallingBlockType = typeFromId("falling_block");
|
||||
}
|
||||
return fallingBlockType;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private void logException(Exception e, @Nullable EntityType type, List<EntityData> entityDataList, EntityData entityData) {
|
||||
|
||||
@@ -53,6 +53,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewriter.RegistryDataRewriter {
|
||||
private final Map<String, BiConsumer<String, CompoundTag>> registryEntryHandlers = new Object2ObjectArrayMap<>();
|
||||
private final Map<String, Consumer<CompoundTag>> enchantmentEffectHandlers = new Object2ObjectArrayMap<>(); // for nested enchantment data
|
||||
private final Set<String> enchantmentEffectsToRemove = new HashSet<>();
|
||||
private final Map<String, List<RegistryEntry>> toAdd = new Object2ObjectArrayMap<>();
|
||||
private final Set<String> toRemove = new HashSet<>();
|
||||
protected final Protocol<?, ?, ?, ?> protocol;
|
||||
@@ -88,6 +89,7 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
|
||||
case "trim_material" -> updateTrimMaterials(entries);
|
||||
case "jukebox_song" -> updateJukeboxSongs(entries);
|
||||
case "worldgen/biome" -> updateBiomes(entries);
|
||||
case "dimension_type" -> updateDimensionTypes(entries);
|
||||
case "dialog" -> updateDialogs(connection, entries);
|
||||
}
|
||||
|
||||
@@ -170,6 +172,17 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
|
||||
enchantmentEffectHandlers.put(Key.stripMinecraftNamespace(key), rewriter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces enchantment effects with the given types by an empty all_of effect, as unknown types will result in an error on the client.
|
||||
*
|
||||
* @param types effect types to remove, e.g. entity effect or value effect types
|
||||
*/
|
||||
public void removeEnchantmentEffects(final String... types) {
|
||||
for (final String type : types) {
|
||||
enchantmentEffectsToRemove.add(Key.stripMinecraftNamespace(type));
|
||||
}
|
||||
}
|
||||
|
||||
public void trackDimensionAndBiomes(final UserConnection connection, final String registryKey, final RegistryEntry[] entries) {
|
||||
if (registryKey.equals("worldgen/biome")) {
|
||||
protocol.getEntityRewriter().tracker(connection).setBiomesSent(entries.length);
|
||||
@@ -367,13 +380,32 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
|
||||
// can be overridden
|
||||
}
|
||||
|
||||
public void updateDimensionTypes(final RegistryEntry[] entries) {
|
||||
for (final RegistryEntry entry : entries) {
|
||||
if (entry.tag() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final CompoundTag attributes = ((CompoundTag) entry.tag()).getCompoundTag("attributes");
|
||||
if (attributes != null) {
|
||||
updateEnvironmentAttributes(attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateBiomes(final RegistryEntry[] entries) {
|
||||
for (final RegistryEntry entry : entries) {
|
||||
if (entry.tag() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final CompoundTag effects = ((CompoundTag) entry.tag()).getCompoundTag("effects");
|
||||
final CompoundTag tag = (CompoundTag) entry.tag();
|
||||
final CompoundTag attributes = tag.getCompoundTag("attributes");
|
||||
if (attributes != null) {
|
||||
updateEnvironmentAttributes(attributes);
|
||||
}
|
||||
|
||||
final CompoundTag effects = tag.getCompoundTag("effects");
|
||||
if (effects == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -385,6 +417,18 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateEnvironmentAttributes(final CompoundTag tag) {
|
||||
final MappingData mappings = protocol.getMappingData();
|
||||
if (mappings == null || mappings.changedEnvironmentAttributes() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove no longer present environment attributes, else the client throws
|
||||
for (final String attribute : mappings.changedEnvironmentAttributes()) {
|
||||
TagUtil.removeNamespaced(tag, attribute);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNestedEffect(final CompoundTag effectsTag) {
|
||||
final CompoundTag effect = effectsTag.getCompoundTag("effect");
|
||||
if (effect != null) {
|
||||
@@ -451,6 +495,14 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
|
||||
}
|
||||
|
||||
effect = Key.stripMinecraftNamespace(effect);
|
||||
if (enchantmentEffectsToRemove.contains(effect)) {
|
||||
// Replace with an empty all_of effect as a no-op
|
||||
effectTag.clear();
|
||||
effectTag.putString("type", "minecraft:all_of");
|
||||
effectTag.put("effects", new ListTag<>(CompoundTag.class));
|
||||
return;
|
||||
}
|
||||
|
||||
if (effect.equals("attribute")) {
|
||||
updateType(effectTag, "attribute", protocol.getMappingData().getAttributeMappings());
|
||||
} else if (effect.equals("spawn_particles")) {
|
||||
|
||||
+18
-12
@@ -21,6 +21,7 @@ import com.google.common.base.Preconditions;
|
||||
import com.viaversion.nbt.tag.CompoundTag;
|
||||
import com.viaversion.nbt.tag.IntArrayTag;
|
||||
import com.viaversion.nbt.tag.IntTag;
|
||||
import com.viaversion.nbt.tag.ListTag;
|
||||
import com.viaversion.nbt.tag.Tag;
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.data.FullMappings;
|
||||
@@ -54,9 +55,9 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
|
||||
T extends Protocol<C, ?, ?, S>> extends ItemRewriter<C, S, T> {
|
||||
|
||||
public static final String MARKER_KEY = "VV|custom_data";
|
||||
private static final String ORIGINAL_HASHES_KEY = "VV|original_hashes";
|
||||
protected static final String ORIGINAL_HASHES_KEY = "VV|original_hashes";
|
||||
|
||||
public StructuredItemRewriter(T protocol) {
|
||||
public StructuredItemRewriter(final T protocol) {
|
||||
super(protocol);
|
||||
}
|
||||
|
||||
@@ -70,7 +71,7 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
|
||||
* @see #handleItemToServer(UserConnection, Item)
|
||||
*/
|
||||
@Override
|
||||
public Item handleItemToClient(UserConnection connection, Item item) {
|
||||
public Item handleItemToClient(final UserConnection connection, final Item item) {
|
||||
if (Item.isEmpty(item)) {
|
||||
return item;
|
||||
}
|
||||
@@ -97,7 +98,7 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
|
||||
handleItemDataComponentsToClient(connection, item, dataContainer);
|
||||
|
||||
if (originalHashedItem != null) {
|
||||
storeOriginalHashedItem(connection, item, itemHasher, originalHashedItem); // has to be called AFTER all modifications - override handleItemDataComponentsToClient instead of this method if needed
|
||||
storeOriginalHashedItemIfNeeded(connection, item, itemHasher, originalHashedItem); // has to be called AFTER all modifications - override handleItemDataComponentsToClient instead of this method if needed
|
||||
}
|
||||
return item;
|
||||
}
|
||||
@@ -122,7 +123,8 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
|
||||
final CompoundTag originalHashes;
|
||||
if (customData != null && (originalHashes = customData.getCompoundTag(ORIGINAL_HASHES_KEY)) != null) {
|
||||
if (isFirstServerbound(connection)) {
|
||||
// Get the item that was originally saved when there was the first hash change to cache in this protocol
|
||||
// Get the item that was originally saved when there was the first hash change to cache in this protocol,
|
||||
// so that it can be cached in the hasher
|
||||
return backedUpOriginalHashes(originalHashes, item);
|
||||
}
|
||||
// If the original has already been saved and this isn't the final clientbound protocol, no need to do anything else
|
||||
@@ -143,7 +145,7 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
|
||||
* @param hasher hasher
|
||||
* @param originalHashedItem pre-modification hashed item
|
||||
*/
|
||||
protected void storeOriginalHashedItem(final UserConnection connection, final Item item, final ItemHasherBase hasher, final HashedItem originalHashedItem) {
|
||||
protected void storeOriginalHashedItemIfNeeded(final UserConnection connection, final Item item, final ItemHasherBase hasher, final HashedItem originalHashedItem) {
|
||||
if (originalHashedItem == null || (originalHashedItem.dataHashesById().isEmpty() && originalHashedItem.removedDataIds().isEmpty())) {
|
||||
return;
|
||||
}
|
||||
@@ -162,6 +164,10 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
|
||||
return;
|
||||
}
|
||||
|
||||
storeOriginalHashedItemInTag(connection, item, hasher, originalHashedItem);
|
||||
}
|
||||
|
||||
protected void storeOriginalHashedItemInTag(final UserConnection connection, final Item item, final ItemHasherBase hasher, final HashedItem originalHashedItem) {
|
||||
// Always has to be AFTER any modification - Use the custom_data hash as a key to the original data (excluding amount, as that will simply be when copying).
|
||||
// This is much easier/cheaper than tracking via the full hashed item, as collisions are both acceptable and still unlikely.
|
||||
final CompoundTag originalHashes = new CompoundTag();
|
||||
@@ -175,7 +181,7 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
|
||||
final CompoundTag customTag = createCustomTag(item);
|
||||
customTag.put(ORIGINAL_HASHES_KEY, originalHashes);
|
||||
|
||||
if (isFirstServerbound(connection)) {
|
||||
if (isFirstServerbound(connection)) { // = the last clientbound protocol: actually cache it now to be retreived during serverbound packets
|
||||
hasher.trackOriginalHashedItem(customTag, originalHashedItem, nbtTagName());
|
||||
}
|
||||
}
|
||||
@@ -189,7 +195,7 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
|
||||
updateHashedItemDataComponentIds(item, mappingData.getDataComponentSerializerMappings().inverse());
|
||||
}
|
||||
|
||||
private boolean isFirstServerbound(final UserConnection connection) {
|
||||
protected boolean isFirstServerbound(final UserConnection connection) {
|
||||
// Only actually cache the original item once in the final clientbound/first serverbound protocol
|
||||
for (final Protocol<?, ?, ?, ?> protocol : connection.getProtocolInfo().getPipeline().pipes()) {
|
||||
if (connection.getItemHasher(protocol.getClass()) instanceof ItemHasherBase) {
|
||||
@@ -326,8 +332,7 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
|
||||
// Casting around Rewritable and especially Holder gets ugly, but the only good alternative is to do everything manually
|
||||
@SuppressWarnings("unchecked")
|
||||
private void handleRewritables(UserConnection connection, boolean clientbound, StructuredDataContainer container, ItemHandler itemHandler) {
|
||||
for (final Map.Entry<StructuredDataKey<?>, StructuredData<?>> entry : container.data().entrySet()) {
|
||||
final StructuredData<?> data = entry.getValue();
|
||||
for (final StructuredData<?> data : container.data().values()) {
|
||||
if (data.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -368,7 +373,8 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
|
||||
|
||||
protected void updateTextComponent(final UserConnection connection, final Item item, final StructuredDataKey<Tag> key, final String backupKey) {
|
||||
final Tag name = item.dataContainer().get(key);
|
||||
if (name == null) {
|
||||
// Skip primitive tags - processTag only handles compound and list tags
|
||||
if (!(name instanceof CompoundTag) && !(name instanceof ListTag)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -468,7 +474,7 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
|
||||
final StructuredDataKey<?> key = keys.get(i);
|
||||
final StructuredDataKey<?> mappedKey = mappedKeys.get(i);
|
||||
replaceKeyUnchecked(container, key, mappedKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> void replaceKeyUnchecked(final StructuredDataContainer container, final StructuredDataKey<T> key, final StructuredDataKey<?> mappedKey) {
|
||||
|
||||
@@ -96,8 +96,9 @@ public final class ComponentUtil {
|
||||
}
|
||||
return TagUtil.handleDeep(input, (key, tag) -> {
|
||||
if (tag instanceof StringTag stringTag) {
|
||||
final byte[] value = stringTag.getValue().getBytes(StandardCharsets.UTF_8);
|
||||
if (value.length > MAX_UNSIGNED_SHORT) {
|
||||
final String value = stringTag.getValue();
|
||||
// Cheap upper bound check first, then check the actual bytes
|
||||
if (value.length() > MAX_UNSIGNED_SHORT / 3 && value.getBytes(StandardCharsets.UTF_8).length > MAX_UNSIGNED_SHORT) {
|
||||
stringTag.setValue("{}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,8 @@ register-userconnections-on-join: true
|
||||
piston-animation-patch: false
|
||||
# Experimental - Should we fix shift quick move action for 1.12 clients (causes shift + double click not to work when moving items) (only works on 1.8-1.11.2 bukkit based servers)
|
||||
quick-move-action-fix: false
|
||||
# Should we fix the client-randomized reddust color for 1.13+ clients? This will result in an extra packet per count, so enabling this may spam clients.
|
||||
multi-reddust-color-fix: false
|
||||
# Should we use prefix for team color on 1.13 and above clients?
|
||||
team-colour-fix: true
|
||||
# 1.13 introduced new auto complete which can trigger "Kicked for spamming" for servers older than 1.13, the following option will disable it completely.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large.
Load diff
Binary file not shown.
Loaded 100 of 166 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user