Compare commits

..
6 Commits
Author SHA1 Message Date
NotAlexNoyle c09fa331c6 Exclude CODEOWNERS from language statistics 2026-10-28 08:31:40 -04:00
NotAlexNoyle 0088f657e7 Add CODEOWNERS (one staff approval) 2026-10-28 08:31:40 -04:00
NotAlexNoyle 0f95f7cbaf Merge branch 'ViaVersion:master' into master 2026-06-17 12:51:33 -04:00
NotAlexNoyle e607988751 Suppress jdk17 warning 2026-06-17 04:00:23 -04:00
NotAlexNoyle 1f6103aa9b Fix network protocol error on 1.21.5+ clients 2026-06-05 12:25:14 -04:00
NotAlexNoyle 00bb773c4a TrueOG fork patches
Cumulative customizations on top of upstream ViaVersion master:
- build.gradle.kts: enable Eclipse gradle plugin
- common/.../config.yml: TrueOG network 1.19.4 production config,
  disallow clients above 1.21.11, rely on Scoreboard-OG to hide
  scoreboard numbers, suppress warnings
- common/.../v1_19_4to1_20 EntityPacketRewriter1_20: cancel Purpur
  Rainglow non-vanilla GLOW_SQUID metadata (placeholder index, fixed
  in the follow-up commit)

Squashes: Add eclipse gradle plugin (47af35dfa), Use live TrueOG
Network 1.19.4/main config files (f44d281a8, 47e67a1b9), Suppress
warnings (17d367bd9), Rely on Scoreboard-OG to hide scoreboard
numbers (e69efcc39), Disallow clients above 1.21.11 (7cfbe8b94),
Bee anger field iterations (dd95e81c3, 38746d70e), Fix packets
(57f9f1016).
2026-06-05 12:25:09 -04:00
198 changed files with 29042 additions and 14434 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
-2
View File
@@ -6,11 +6,9 @@ updates:
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 10
- package-ecosystem: "gradle"
cooldown:
default-days: 7
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 10
+3 -3
View File
@@ -11,16 +11,16 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout Repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Gradle
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
with:
cache-disabled: false
cache-provider: basic
- name: Set up JDK 17
uses: actions/setup-java@v5.5.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: 17
+3 -3
View File
@@ -16,16 +16,16 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout Repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Gradle
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
with:
cache-disabled: false
cache-provider: basic
- name: Set up JDK 17
uses: actions/setup-java@v5.5.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.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 27;
return 26;
}
/**
@@ -261,8 +261,6 @@ 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 token that will let a packet be passed through without
* Returns a newly generated uuid that will let a packet be passed through without
* transforming its contents if used together with {@link PacketWrapper#PASSTHROUGH_ID}.
*
* @return generated passthrough token
*/
long generatePassthroughToken();
UUID generatePassthroughToken();
}
@@ -22,29 +22,88 @@
*/
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 extends FullMappingsBase {
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 FullIdentityMappings(final IdentifiersPair identifiersPair, final Mappings mappings) {
this(toInverseMap(identifiersPair.unmapped()), identifiersPair.unmapped().stream().map(Key::of).toArray(Key[]::new), mappings);
Preconditions.checkNotNull(mappings, "Mappings cannot be null");
this.mappings = mappings;
this.stringToId = FullMappingsBase.toInverseMap(identifiersPair.unmapped());
this.idToString = identifiersPair.unmapped().toArray(EMPTY_ARRAY);
}
private FullIdentityMappings(final Object2IntMap<String> stringToId, final Key[] idToKey, final Mappings mappings) {
super(stringToId, stringToId, idToKey, idToKey, 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);
}
@Override
public @Nullable String identifier(final String mappedIdentifier) {
return mappedIdentifier(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 Key keyFromMappedKey(final String mappedIdentifier) {
return mappedKeyFromKey(mappedIdentifier);
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();
}
@Override
@@ -58,7 +117,7 @@ public class FullIdentityMappings extends FullMappingsBase {
}
@Override
public FullIdentityMappings inverse() {
return new FullIdentityMappings(stringToId, idToKey, mappings.inverse());
public FullMappings inverse() {
return new FullIdentityMappings(stringToId, idToString, mappings.inverse());
}
}
@@ -22,7 +22,6 @@
*/
package com.viaversion.viaversion.api.data;
import com.viaversion.viaversion.util.Key;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
@@ -52,10 +51,7 @@ public interface FullMappings extends BiMappings {
* @param id unmapped id
* @return unmapped string identifier, or null if out of bounds
*/
default @Nullable String identifier(int id) {
final Key key = keyFromId(id);
return key != null ? key.toString() : null;
}
@Nullable String identifier(int id);
/**
* Returns the unmapped string identifier for the given mapped identifier.
@@ -63,10 +59,7 @@ public interface FullMappings extends BiMappings {
* @param mappedIdentifier mapped identifier
* @return unmapped string identifier, or null if not found
*/
default @Nullable String identifier(String mappedIdentifier) {
final Key key = keyFromMappedKey(mappedIdentifier);
return key != null ? key.toString() : null;
}
@Nullable String identifier(String mappedIdentifier);
/**
* Returns the mapped string identifier for the given mapped id.
@@ -74,10 +67,7 @@ public interface FullMappings extends BiMappings {
* @param mappedId mapped id
* @return mapped string identifier, or null if out of bounds
*/
default @Nullable String mappedIdentifier(int mappedId) {
final Key key = mappedKeyFromMappedId(mappedId);
return key != null ? key.toString() : null;
}
@Nullable String mappedIdentifier(int mappedId);
/**
* Returns the mapped string identifier for the given unmapped identifier.
@@ -85,42 +75,7 @@ public interface FullMappings extends BiMappings {
* @param identifier unmapped identifier
* @return mapped string identifier, or null if not found
*/
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);
@Nullable String mappedIdentifier(String identifier);
/**
* Returns whether the int id mappings are identity mappings.
@@ -31,26 +31,27 @@ import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
public class FullMappingsBase implements FullMappings {
protected final Object2IntMap<String> stringToId;
protected final Object2IntMap<String> mappedStringToId;
protected final Key[] idToKey;
protected final Key[] mappedIdToKey;
protected final Mappings mappings;
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;
public FullMappingsBase(final IdentifiersPair identifiersPair, final Mappings mappings) {
Preconditions.checkNotNull(mappings, "Mappings cannot be null");
this.mappings = mappings;
this.stringToId = toInverseMap(identifiersPair.unmapped());
this.idToKey = identifiersPair.unmapped().stream().map(Key::of).toArray(Key[]::new);
this.idToString = identifiersPair.unmapped().toArray(EMPTY_ARRAY);
this.mappedStringToId = toInverseMap(identifiersPair.mapped());
this.mappedIdToKey = identifiersPair.mapped().stream().map(Key::of).toArray(Key[]::new);
this.mappedIdToString = identifiersPair.mapped().toArray(EMPTY_ARRAY);
}
protected FullMappingsBase(final Object2IntMap<String> stringToId, final Object2IntMap<String> mappedStringToId, final Key[] idToKey, final Key[] mappedIdToKey, final Mappings mappings) {
private FullMappingsBase(final Object2IntMap<String> stringToId, final Object2IntMap<String> mappedStringToId, final String[] idToString, final String[] mappedIdToString, final Mappings mappings) {
this.stringToId = stringToId;
this.mappedStringToId = mappedStringToId;
this.idToKey = idToKey;
this.mappedIdToKey = mappedIdToKey;
this.idToString = idToString;
this.mappedIdToString = mappedIdToString;
this.mappings = mappings;
}
@@ -78,43 +79,45 @@ public class FullMappingsBase implements FullMappings {
}
@Override
public @Nullable Key keyFromId(final int id) {
if (id < 0 || id >= idToKey.length) {
public String identifier(final int id) {
if (id < 0 || id >= idToString.length) {
return null;
}
return idToKey[id];
final String identifier = idToString[id];
return Key.namespaced(identifier);
}
@Override
public @Nullable Key keyFromMappedKey(final String mappedIdentifier) {
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 ? keyFromId(id) : null;
return id != -1 ? identifier(id) : null;
}
@Override
public @Nullable Key mappedKeyFromMappedId(final int mappedId) {
if (mappedId < 0 || mappedId >= mappedIdToKey.length) {
public @Nullable String mappedIdentifier(final int mappedId) {
if (mappedId < 0 || mappedId >= mappedIdToString.length) {
return null;
}
return mappedIdToKey[mappedId];
final String identifier = mappedIdToString[mappedId];
return Key.namespaced(identifier);
}
@Override
public @Nullable Key mappedKeyFromKey(final String identifier) {
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 ? mappedKeyFromMappedId(mappedId) : null;
return mappedId != -1 ? mappedIdentifier(mappedId) : null;
}
@Override
@@ -144,6 +147,6 @@ public class FullMappingsBase implements FullMappings {
@Override
public FullMappings inverse() {
return new FullMappingsBase(mappedStringToId, stringToId, mappedIdToKey, idToKey, mappings.inverse());
return new FullMappingsBase(mappedStringToId, stringToId, mappedIdToString, idToString, mappings.inverse());
}
}
@@ -1,64 +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;
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;
}
}
}
@@ -1,111 +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;
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,7 +29,6 @@ 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 {
@@ -191,23 +190,15 @@ 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 registry() {
return this.key;
public Key key() {
return Key.of(name().toLowerCase(Locale.ROOT));
}
}
}
@@ -24,8 +24,6 @@ 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;
@@ -37,9 +35,7 @@ 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 {
@@ -67,7 +63,6 @@ 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;
@@ -123,7 +118,6 @@ 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");
@@ -131,11 +125,6 @@ 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);
}
@@ -178,10 +167,6 @@ 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);
}
@@ -364,11 +349,6 @@ public class MappingDataBase implements MappingData {
return changedBlocks;
}
@Override
public @Nullable Set<String> changedEnvironmentAttributes() {
return changedEnvironmentAttributes;
}
protected Logger getLogger() {
return Via.getPlatform().getLogger();
}
@@ -31,17 +31,13 @@ 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;
@@ -55,7 +51,6 @@ 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;
@@ -64,16 +59,15 @@ 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 ConcurrentHashMap<>();
private final Map<String, CompoundTag> mappingsCache = new HashMap<>();
private final Class<?> dataLoaderClass;
private final String dataPath;
private volatile boolean cacheValid = true;
private boolean cacheValid = true;
public MappingDataLoader(final Class<?> dataLoaderClass, final String dataPath) {
this.dataLoaderClass = dataLoaderClass;
@@ -110,14 +104,14 @@ public class MappingDataLoader {
throw new IllegalArgumentException("Unknown global identifier key: " + registry);
}
if (globalId < 0 || globalId >= array.length) {
throw new IllegalArgumentException("Unknown global identifier index: " + registry + "/" + globalId);
throw new IllegalArgumentException("Unknown global identifier index: " + globalId);
}
return array[globalId];
}
public void clearCache() {
cacheValid = false;
mappingsCache.clear();
cacheValid = false;
}
/**
@@ -166,14 +160,17 @@ public class MappingDataLoader {
return loadNBTFromFile(name);
}
if (cache) {
// Also blocks concurrent loaders of the same file until the first one has finished
return mappingsCache.computeIfAbsent(name, this::loadNBTFromFile);
CompoundTag data = mappingsCache.get(name);
if (data != null) {
return data;
}
// Still do a lookup, but don't store it
final CompoundTag data = mappingsCache.get(name);
return data != null ? data : loadNBTFromFile(name);
data = loadNBTFromFile(name);
if (cache && data != null) {
mappingsCache.put(name, data);
}
return data;
}
public @Nullable CompoundTag loadNBT(final String name) {
@@ -214,29 +211,15 @@ 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) {
// 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);
final IntArrayTag valuesTag = tag.getIntArrayTag("val");
return IntArrayMappings.of(valuesTag.getValue(), mappedSize);
} else if (strategy == SHIFTS_ID) {
final ValuePairs pairs = readAtValuePairs(tag);
final int[] shiftsAt = pairs.first;
final int[] shiftsTo = pairs.second;
final int[] shiftsAt = tag.getIntArrayTag("at").getValue();
final int[] shiftsTo = tag.getIntArrayTag("to").getValue();
final int size = tag.getIntTag("size").asInt();
mappings = holderSupplier.get(size);
@@ -259,9 +242,8 @@ public class MappingDataLoader {
}
}
} else if (strategy == CHANGES_ID) {
final ValuePairs pairs = readAtValuePairs(tag);
final int[] changesAt = pairs.first;
final int[] values = pairs.second;
final int[] changesAt = tag.getIntArrayTag("at").getValue();
final int[] values = tag.getIntArrayTag("val").getValue();
final int size = tag.getIntTag("size").asInt();
final boolean fillBetween = tag.get("nofill") == null;
mappings = holderSupplier.get(size);
@@ -280,13 +262,6 @@ public class MappingDataLoader {
// Assign the changed value
addConsumer.addTo(mappings, changedId, values[i]);
}
// Fill the remainder
if (fillBetween && nextUnhandledId != size) {
for (int id = nextUnhandledId; id < size; id++) {
addConsumer.addTo(mappings, id, id);
}
}
} else if (strategy == IDENTITY_ID) {
final IntTag sizeTag = tag.getIntTag("size");
return new IdentityMappings(sizeTag.asInt(), mappedSize);
@@ -296,37 +271,6 @@ 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;
@@ -371,10 +315,6 @@ public class MappingDataLoader {
continue;
}
if (mappedGlobalId == -1) {
throw new IllegalArgumentException("Mapped global id -1 for unmapped global id: " + key + "/" + unmappedIdentifier + " (" + unmappedGlobalId + ")");
}
identity = false;
unmappedIdentifiers.add(unmappedIdentifier);
mappedIdentifiers.add(identifierFromGlobalId(key, mappedGlobalId));
@@ -383,9 +323,8 @@ public class MappingDataLoader {
}
private List<String> identifiers(final Mappings mappings, final String key) {
final int size = mappings.size();
final List<String> identifiers = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
final List<String> identifiers = new ArrayList<>(mappings.size());
for (int i = 0; i < mappings.size(); i++) {
final int globalId = mappings.getNewId(i);
identifiers.add(identifierFromGlobalId(key, globalId));
}
@@ -462,7 +401,4 @@ public class MappingDataLoader {
this(identifiers, identifiers, true);
}
}
private record ValuePairs(int[] first, int[] second) {
}
}
@@ -23,9 +23,7 @@
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;
@@ -45,9 +43,8 @@ public interface EntityTracker {
*
* @param id entity id
* @param type entity type
* @return tracked entity
*/
TrackedEntity addEntity(int id, EntityType type);
void addEntity(int id, EntityType type);
/**
* Returns whether the entity is currently tracked.
@@ -78,9 +75,8 @@ public interface EntityTracker {
* Untracks an entity.
*
* @param id entity id
* @return previously tracked entity
*/
@Nullable TrackedEntity removeEntity(int id);
void removeEntity(int id);
/**
* Clears stored entity types and data, only leaving behind the client entity.
@@ -92,6 +88,25 @@ 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.
*
@@ -189,24 +204,6 @@ 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);
@@ -0,0 +1,69 @@
/*
* 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,9 +22,7 @@
*/
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 {
@@ -36,37 +34,18 @@ public interface TrackedEntity {
EntityType entityType();
/**
* Checks if the storage contains an object of the given type.
* Object to hold arbitrary additional data.
*
* @param objectClass object class to check
* @return whether an object of the given type is in the storage
* @return entity data
*/
boolean has(Class<? extends StorableObject> objectClass);
StoredEntityData data();
/**
* Returns an object from the storage if present.
* Returns whether the stored entity currently has any additional data.
*
* @param objectClass class of the object to get
* @param <T> object type
* @return object if present
* @return whether the stored entity currently has additional data
*/
@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);
boolean hasData();
/**
* 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 registry();
Key key();
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 Key key;
private final String identifier;
RegistryType(final String identifier) {
this.key = Key.of(identifier);
this.identifier = identifier;
}
public String identifier() {
return key.path();
return identifier;
}
@Override
public Key registry() {
return this.key;
public Key key() {
return Key.of(identifier);
}
}
@@ -24,10 +24,10 @@ package com.viaversion.viaversion.api.minecraft;
import com.viaversion.viaversion.util.Key;
record SimpleRegistryKey(Key registry) implements RegistryKey {
record SimpleRegistryKey(Key key) implements RegistryKey {
@Override
public String toString() {
return registry.toString();
return key.toString();
}
}
@@ -22,9 +22,6 @@
*/
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 {
@@ -149,27 +146,6 @@ 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.
*
@@ -22,13 +22,9 @@
*/
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 {
@@ -36,8 +32,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) {
@@ -47,30 +43,11 @@ 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
// Pre-size the palette array/map
palette = new IntArrayList(initialSize);
}
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;
// 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);
}
@Override
@@ -86,7 +63,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);
@@ -106,38 +83,6 @@ 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();
@@ -151,7 +96,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 || inversePalette == null) return;
if (oldId == id) return;
inversePalette.put(id, index);
if (inversePalette.get(oldId) == index) {
@@ -167,7 +112,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);
@@ -185,12 +130,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);
@@ -227,51 +172,14 @@ public final class DataPaletteImpl implements DataPalette {
@Override
public void addId(final int id) {
if (inversePalette != null) {
inversePalette.put(id, palette.size());
}
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 = null;
inversePalette.clear();
}
interface ChunkData {
@@ -280,11 +188,6 @@ 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 {
@@ -312,11 +215,6 @@ 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 {
@@ -326,10 +224,6 @@ 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;
@@ -350,11 +244,6 @@ 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 {
@@ -381,63 +270,6 @@ 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);
}
}
}
@@ -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(), "No mapping data present for protocol");
this.entityTracker = Preconditions.checkNotNull(protocol.getEntityRewriter().tracker(connection), "Entity tracker has to be added first");
this.mappingData = Preconditions.checkNotNull(protocol.getMappingData());
this.entityTracker = Preconditions.checkNotNull(protocol.getEntityRewriter().tracker(connection));
this.connection = connection;
this.mapped = false;
}
@@ -102,16 +102,17 @@ 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);
return keyOrUnknown(mappings.key(id), id);
final String identifier = id >= 0 && id < mappings.size() ? mappings.idToKey(id) : null;
return key(identifier, id);
}
private Key key(final FullMappings mappings, final int id) {
final Key key = mapped ? mappings.mappedKeyFromMappedId(id) : mappings.keyFromId(id);
return keyOrUnknown(key, id);
final String identifier = mapped ? mappings.mappedIdentifier(id) : mappings.identifier(id);
return key(identifier, id);
}
private Key keyOrUnknown(@Nullable final Key key, final int id) {
return key != null ? key : Key.of("viaversion", "unknown/" + id);
private Key key(@Nullable final String identifier, final int id) {
return identifier != null ? Key.of(identifier) : Key.of("viaversion", "unknown/" + id);
}
@Override
@@ -270,7 +270,7 @@ public final class StructuredDataContainer implements Copyable {
@Override
public StructuredDataContainer copy() {
final Reference2ObjectOpenHashMap<StructuredDataKey<?>, StructuredData<?>> map = new Reference2ObjectOpenHashMap<>(data.size());
final Reference2ObjectOpenHashMap<StructuredDataKey<?>, StructuredData<?>> map = new Reference2ObjectOpenHashMap<>();
for (final StructuredData<?> value : data.values()) {
map.put(value.key(), value.copy());
}
@@ -35,7 +35,6 @@ import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_
import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_21_11;
import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_21_2;
import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_21_5;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.minecraft.item.data.AdventureModePredicate;
import com.viaversion.viaversion.api.minecraft.item.data.ArmorTrim;
import com.viaversion.viaversion.api.minecraft.item.data.ArmorTrimMaterial;
@@ -34,7 +34,7 @@ public class StructuredDataKeys1_20_5 extends VersionedStructuredDataKeys {
public StructuredDataKeys1_20_5(final VersionedTypesHolder types) {
this.container = add("container", types.itemArray());
this.chargedProjectiles = add("charged_projectiles", types.itemArray().withMaxLength(1024));
this.bundleContents = add("bundle_contents", types.itemArray().withMaxLength(1024));
this.chargedProjectiles = add("charged_projectiles", types.itemArray());
this.bundleContents = add("bundle_contents", types.itemArray());
}
}
@@ -36,8 +36,8 @@ public class StructuredDataKeys1_21_2 extends VersionedStructuredDataKeys {
public StructuredDataKeys1_21_2(final VersionedTypesHolder types) {
this.container = add("container", new ContainerContentsType(types.optionalItemTemplate()));
this.chargedProjectiles = add("charged_projectiles", types.itemTemplateArray().withMaxLength(1024));
this.bundleContents = add("bundle_contents", types.itemTemplateArray().withMaxLength(1024));
this.chargedProjectiles = add("charged_projectiles", types.itemTemplateArray());
this.bundleContents = add("bundle_contents", types.itemTemplateArray());
this.useRemainder = add("use_remainder", types.itemTemplate());
}
}
@@ -1,37 +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.minecraft.data.version;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.type.types.version.VersionedTypesHolder;
public class StructuredDataKeys26_2 extends StructuredDataKeys1_21_11 {
public final StructuredDataKey<Item> sulfurCubeContent;
public StructuredDataKeys26_2(final VersionedTypesHolder types) {
super(types);
this.sulfurCubeContent = add("sulfur_cube_content", types.item());
}
}
@@ -1,332 +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.minecraft.entities;
import com.google.common.base.Preconditions;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.util.EntityTypeUtil;
import com.viaversion.viaversion.util.Key;
import java.util.Locale;
import org.checkerframework.checker.nullness.qual.Nullable;
public enum EntityTypes26_2 implements EntityType {
ENTITY(null, null),
AREA_EFFECT_CLOUD(ENTITY),
END_CRYSTAL(ENTITY),
EVOKER_FANGS(ENTITY),
EXPERIENCE_ORB(ENTITY),
EYE_OF_ENDER(ENTITY),
FALLING_BLOCK(ENTITY),
ITEM(ENTITY),
TNT(ENTITY),
OMINOUS_ITEM_SPAWNER(ENTITY),
MARKER(ENTITY),
LIGHTNING_BOLT(ENTITY),
INTERACTION(ENTITY),
DISPLAY(ENTITY, null),
BLOCK_DISPLAY(DISPLAY),
ITEM_DISPLAY(DISPLAY),
TEXT_DISPLAY(DISPLAY),
// Hanging entities
HANGING_ENTITY(ENTITY, null),
LEASH_KNOT(HANGING_ENTITY),
PAINTING(HANGING_ENTITY),
ITEM_FRAME(HANGING_ENTITY),
GLOW_ITEM_FRAME(ITEM_FRAME),
// Projectiles
PROJECTILE(ENTITY, null),
ITEM_PROJECTILE(PROJECTILE, null),
SNOWBALL(ITEM_PROJECTILE),
ENDER_PEARL(ITEM_PROJECTILE),
EGG(ITEM_PROJECTILE),
EXPERIENCE_BOTTLE(ITEM_PROJECTILE),
FIREWORK_ROCKET(PROJECTILE),
LLAMA_SPIT(PROJECTILE),
SHULKER_BULLET(PROJECTILE),
FISHING_BOBBER(PROJECTILE),
WITHER_SKULL(PROJECTILE),
DRAGON_FIREBALL(PROJECTILE), // Doesn't actually inherit fireball
ABSTRACT_THROWN_POTION(ITEM_PROJECTILE, null),
SPLASH_POTION(ABSTRACT_THROWN_POTION),
LINGERING_POTION(ABSTRACT_THROWN_POTION),
ABSTRACT_ARROW(PROJECTILE, null),
ARROW(ABSTRACT_ARROW),
SPECTRAL_ARROW(ABSTRACT_ARROW),
TRIDENT(ABSTRACT_ARROW),
ABSTRACT_FIREBALL(ENTITY, null),
FIREBALL(ABSTRACT_FIREBALL),
SMALL_FIREBALL(ABSTRACT_FIREBALL),
ABSTRACT_WIND_CHARGE(PROJECTILE, null),
WIND_CHARGE(ABSTRACT_WIND_CHARGE),
BREEZE_WIND_CHARGE(ABSTRACT_WIND_CHARGE),
// Vehicles
VEHICLE(ENTITY, null),
ABSTRACT_MINECART(VEHICLE, null),
MINECART(ABSTRACT_MINECART),
FURNACE_MINECART(ABSTRACT_MINECART),
COMMAND_BLOCK_MINECART(ABSTRACT_MINECART),
TNT_MINECART(ABSTRACT_MINECART),
SPAWNER_MINECART(ABSTRACT_MINECART),
ABSTRACT_MINECART_CONTAINER(ABSTRACT_MINECART, null),
CHEST_MINECART(ABSTRACT_MINECART_CONTAINER),
HOPPER_MINECART(ABSTRACT_MINECART_CONTAINER),
ABSTRACT_BOAT(VEHICLE, null),
ABSTRACT_CHEST_BOAT(ABSTRACT_BOAT, null),
ACACIA_BOAT(ABSTRACT_BOAT),
ACACIA_CHEST_BOAT(ABSTRACT_CHEST_BOAT),
BAMBOO_CHEST_RAFT(ABSTRACT_CHEST_BOAT),
BAMBOO_RAFT(ABSTRACT_BOAT),
BIRCH_BOAT(ABSTRACT_BOAT),
BIRCH_CHEST_BOAT(ABSTRACT_CHEST_BOAT),
CHERRY_BOAT(ABSTRACT_BOAT),
CHERRY_CHEST_BOAT(ABSTRACT_CHEST_BOAT),
DARK_OAK_BOAT(ABSTRACT_BOAT),
DARK_OAK_CHEST_BOAT(ABSTRACT_CHEST_BOAT),
JUNGLE_BOAT(ABSTRACT_BOAT),
JUNGLE_CHEST_BOAT(ABSTRACT_CHEST_BOAT),
MANGROVE_BOAT(ABSTRACT_BOAT),
MANGROVE_CHEST_BOAT(ABSTRACT_CHEST_BOAT),
OAK_BOAT(ABSTRACT_BOAT),
OAK_CHEST_BOAT(ABSTRACT_CHEST_BOAT),
PALE_OAK_BOAT(ABSTRACT_BOAT),
PALE_OAK_CHEST_BOAT(ABSTRACT_CHEST_BOAT),
SPRUCE_BOAT(ABSTRACT_BOAT),
SPRUCE_CHEST_BOAT(ABSTRACT_CHEST_BOAT),
// Living entities as a larger subclass
LIVING_ENTITY(ENTITY, null),
ARMOR_STAND(LIVING_ENTITY),
// Players
AVATAR(LIVING_ENTITY, null),
MANNEQUIN(AVATAR),
PLAYER(AVATAR),
// Mobs as a larger subclass
MOB(LIVING_ENTITY, null),
ENDER_DRAGON(MOB),
// Ambient mobs
AMBIENT_CREATURE(MOB, null),
BAT(AMBIENT_CREATURE),
// Flying mobs
FLYING_MOB(MOB, null),
GHAST(FLYING_MOB),
PHANTOM(FLYING_MOB),
// Pathfinder mobs and its subclasses
PATHFINDER_MOB(MOB, null),
ALLAY(PATHFINDER_MOB),
ABSTRACT_GOLEM(PATHFINDER_MOB, null),
SNOW_GOLEM(ABSTRACT_GOLEM),
IRON_GOLEM(ABSTRACT_GOLEM),
SHULKER(ABSTRACT_GOLEM),
COPPER_GOLEM(ABSTRACT_GOLEM),
// Ageable mobs and (tamable) animals
ABSTRACT_AGEABLE(PATHFINDER_MOB, null),
ABSTRACT_VILLAGER(ABSTRACT_AGEABLE, null),
VILLAGER(ABSTRACT_VILLAGER),
WANDERING_TRADER(ABSTRACT_VILLAGER),
ABSTRACT_CUBE_MOB(ABSTRACT_AGEABLE, null),
SLIME(ABSTRACT_CUBE_MOB),
MAGMA_CUBE(ABSTRACT_CUBE_MOB),
SULFUR_CUBE(ABSTRACT_CUBE_MOB),
ABSTRACT_ANIMAL(ABSTRACT_AGEABLE, null),
AXOLOTL(ABSTRACT_ANIMAL),
CHICKEN(ABSTRACT_ANIMAL),
PANDA(ABSTRACT_ANIMAL),
PIG(ABSTRACT_ANIMAL),
POLAR_BEAR(ABSTRACT_ANIMAL),
RABBIT(ABSTRACT_ANIMAL),
SHEEP(ABSTRACT_ANIMAL),
BEE(ABSTRACT_ANIMAL),
TURTLE(ABSTRACT_ANIMAL),
FOX(ABSTRACT_ANIMAL),
FROG(ABSTRACT_ANIMAL),
GOAT(ABSTRACT_ANIMAL),
HOGLIN(ABSTRACT_ANIMAL),
STRIDER(ABSTRACT_ANIMAL),
SNIFFER(ABSTRACT_ANIMAL),
ARMADILLO(ABSTRACT_ANIMAL),
HAPPY_GHAST(ABSTRACT_ANIMAL),
OCELOT(ABSTRACT_ANIMAL),
ABSTRACT_COW(ABSTRACT_ANIMAL, null),
COW(ABSTRACT_COW),
MOOSHROOM(ABSTRACT_COW),
TAMABLE_ANIMAL(ABSTRACT_ANIMAL, null),
CAT(TAMABLE_ANIMAL),
WOLF(TAMABLE_ANIMAL),
PARROT(TAMABLE_ANIMAL),
ABSTRACT_NAUTILUS(TAMABLE_ANIMAL, null),
NAUTILUS(ABSTRACT_NAUTILUS),
ZOMBIE_NAUTILUS(ABSTRACT_NAUTILUS),
ABSTRACT_HORSE(ABSTRACT_ANIMAL, null),
HORSE(ABSTRACT_HORSE),
SKELETON_HORSE(ABSTRACT_HORSE),
ZOMBIE_HORSE(ABSTRACT_HORSE),
CAMEL(ABSTRACT_HORSE),
CAMEL_HUSK(CAMEL),
ABSTRACT_CHESTED_HORSE(ABSTRACT_HORSE, null),
DONKEY(ABSTRACT_CHESTED_HORSE),
MULE(ABSTRACT_CHESTED_HORSE),
LLAMA(ABSTRACT_CHESTED_HORSE),
TRADER_LLAMA(LLAMA),
// Monsters
ABSTRACT_MONSTER(PATHFINDER_MOB, null),
BLAZE(ABSTRACT_MONSTER),
CREEPER(ABSTRACT_MONSTER),
ENDERMITE(ABSTRACT_MONSTER),
ENDERMAN(ABSTRACT_MONSTER),
GIANT(ABSTRACT_MONSTER),
SILVERFISH(ABSTRACT_MONSTER),
VEX(ABSTRACT_MONSTER),
WITHER(ABSTRACT_MONSTER),
BREEZE(ABSTRACT_MONSTER),
ZOGLIN(ABSTRACT_MONSTER),
WARDEN(ABSTRACT_MONSTER),
CREAKING(ABSTRACT_MONSTER),
ABSTRACT_SKELETON(ABSTRACT_MONSTER, null),
SKELETON(ABSTRACT_SKELETON),
STRAY(ABSTRACT_SKELETON),
WITHER_SKELETON(ABSTRACT_SKELETON),
BOGGED(ABSTRACT_SKELETON),
PARCHED(ABSTRACT_SKELETON),
ZOMBIE(ABSTRACT_MONSTER),
DROWNED(ZOMBIE),
HUSK(ZOMBIE),
ZOMBIFIED_PIGLIN(ZOMBIE),
ZOMBIE_VILLAGER(ZOMBIE),
GUARDIAN(ABSTRACT_MONSTER),
ELDER_GUARDIAN(GUARDIAN),
SPIDER(ABSTRACT_MONSTER),
CAVE_SPIDER(SPIDER),
ABSTRACT_PIGLIN(ABSTRACT_MONSTER, null),
PIGLIN(ABSTRACT_PIGLIN),
PIGLIN_BRUTE(ABSTRACT_PIGLIN),
// Water mobs
AGEABLE_WATER_CREATURE(ABSTRACT_AGEABLE, null),
DOLPHIN(AGEABLE_WATER_CREATURE),
SQUID(AGEABLE_WATER_CREATURE),
GLOW_SQUID(SQUID),
WATER_ANIMAL(PATHFINDER_MOB, null),
ABSTRACT_FISH(WATER_ANIMAL, null),
PUFFERFISH(ABSTRACT_FISH),
TADPOLE(ABSTRACT_FISH),
ABSTRACT_SCHOOLING_FISH(ABSTRACT_FISH, null),
COD(ABSTRACT_SCHOOLING_FISH),
SALMON(ABSTRACT_SCHOOLING_FISH),
TROPICAL_FISH(ABSTRACT_SCHOOLING_FISH),
// Raiders
ABSTRACT_RAIDER(ABSTRACT_MONSTER, null),
WITCH(ABSTRACT_RAIDER),
RAVAGER(ABSTRACT_RAIDER),
ABSTRACT_ILLAGER(ABSTRACT_RAIDER, null),
SPELLCASTER_ILLAGER(ABSTRACT_ILLAGER, null),
VINDICATOR(ABSTRACT_ILLAGER),
PILLAGER(ABSTRACT_ILLAGER),
EVOKER(SPELLCASTER_ILLAGER),
ILLUSIONER(SPELLCASTER_ILLAGER);
private static final EntityType[] TYPES = EntityTypeUtil.createSizedArray(values());
private final EntityType parent;
private final String identifier;
private int id = -1;
EntityTypes26_2(final EntityType parent) {
this.parent = parent;
this.identifier = Key.namespaced(name().toLowerCase(Locale.ROOT));
}
EntityTypes26_2(final EntityType parent, @Nullable final String identifier) {
this.parent = parent;
this.identifier = identifier;
}
@Override
public int getId() {
if (id == -1) {
throw new IllegalStateException("Ids have not been initialized yet (type " + name() + ")");
}
return id;
}
@Override
public String identifier() {
Preconditions.checkArgument(identifier != null, "Called identifier method on abstract type");
return identifier;
}
@Override
public @Nullable EntityType getParent() {
return parent;
}
@Override
public boolean isAbstractType() {
return identifier == null;
}
public static EntityType getTypeFromId(final int typeId) {
return EntityTypeUtil.getTypeFromId(TYPES, typeId, PIG);
}
public static void initialize(final Protocol<?, ?, ?, ?> protocol) {
EntityTypeUtil.initialize(values(), TYPES, protocol, (type, id) -> type.id = id);
}
}
@@ -23,12 +23,10 @@
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) {
@@ -36,7 +34,7 @@ public record DamageResistant26_1(HolderSet types) {
@Override
public void write(final Ops ops, final DamageResistant26_1 value) {
ops.writeMap(map -> map.write("types", new HolderSetType(RegistryKey.of("damage_type")), value.types));
ops.writeMap(map -> map.write("types", Types.HOLDER_SET, value.types));
}
};
}
@@ -94,7 +94,6 @@ public class ProtocolVersion implements Comparable<ProtocolVersion> {
public static final ProtocolVersion v1_21_9 = register(773, "1.21.9-1.21.10", new SubVersionRange("1.21", 9, 10));
public static final ProtocolVersion v1_21_11 = register(774, "1.21.11");
public static final ProtocolVersion v26_1 = register(775, "26.1-26.1.2", new SubVersionRange("26.1", 0, 2));
public static final ProtocolVersion v26_2 = register(776, "26.2");
public static final ProtocolVersion unknown = new ProtocolVersion(VersionType.SPECIAL, -1, -1, "UNKNOWN", null);
static {
@@ -179,8 +179,7 @@ public final class Types {
public static final Type<UUID[]> UUID_ARRAY = new ArrayType<>(Types.UUID);
public static final VarIntType VAR_INT = new VarIntType();
public static final OptionalVarIntType OPTIONAL_VAR_INT = new OptionalVarIntType(); // compact
public static final OptionalType<Integer> BOOL_OPTIONAL_VAR_INT = new VarIntType.OptionalVarIntType(); // regular boolean prefixed
public static final OptionalVarIntType OPTIONAL_VAR_INT = new OptionalVarIntType();
public static final Type<int[]> VAR_INT_ARRAY_PRIMITIVE = new VarIntArrayType();
public static final VarLongType VAR_LONG = new VarLongType();
@@ -45,12 +45,6 @@ public class ArrayType<T> extends Type<T[]> {
this.maxLength = maxLength;
}
private ArrayType(Class<? super T[]> outputClass, Type<T> type, int maxLength) {
super(type.getTypeName() + " Array", outputClass);
this.elementType = type;
this.maxLength = maxLength;
}
public static Class<?> getArrayClass(Class<?> componentType) {
// Should only happen once per class init.
return Array.newInstance(componentType, 0).getClass();
@@ -107,8 +101,4 @@ public class ArrayType<T> extends Type<T[]> {
}
});
}
public ArrayType<T> withMaxLength(final int maxLength) {
return new ArrayType<>(this.getOutputClass(), this.elementType, maxLength);
}
}
@@ -22,6 +22,7 @@
*/
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;
@@ -34,7 +35,6 @@ 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,22 +43,21 @@ 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);
if (len > maxByteLength) {
throw new IllegalArgumentException("Cannot receive string longer than " + maxLength + " * " + MAX_CHAR_UTF_8_LENGTH + " bytes (got " + len + " bytes)");
}
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);
String string = buffer.toString(buffer.readerIndex(), len, StandardCharsets.UTF_8);
buffer.skipBytes(len);
if (string.length() > maxLength) {
throw new IllegalArgumentException("Cannot receive string longer than " + maxLength + " characters (got " + string.length() + ")");
}
Preconditions.checkArgument(string.length() <= maxLength,
"Cannot receive string longer than " + maxLength + " characters (got %s bytes)", string.length());
return string;
}
@@ -23,7 +23,6 @@
package com.viaversion.viaversion.api.type.types;
import com.viaversion.viaversion.api.minecraft.codec.Ops;
import com.viaversion.viaversion.api.type.OptionalType;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.TypeConverter;
import com.viaversion.viaversion.api.type.Types;
@@ -109,11 +108,4 @@ public class VarIntType extends Type<Integer> implements TypeConverter<Integer>
public static int varIntLength(final int value) {
return VAR_INT_LENGTHS[Integer.numberOfLeadingZeros(value)];
}
public static final class OptionalVarIntType extends OptionalType<Integer> {
public OptionalVarIntType() {
super(Types.VAR_INT);
}
}
}
@@ -83,11 +83,8 @@ public class PaletteType1_18 extends PaletteTypeBase {
final int valuesPerLong = (char) (64 / bitsPerValue);
final int expectedLength = (type.size() + valuesPerLong - 1) / valuesPerLong;
if (values.length == expectedLength) {
if (bitsPerValue == globalPaletteBits) {
CompactArrayUtil.iterateCompactArrayWithPadding(bitsPerValue, type.size(), values, palette::setIdAt);
} else {
palette.setPaletteIndexes(values, bitsPerValue, valuesPerLong);
}
CompactArrayUtil.iterateCompactArrayWithPadding(bitsPerValue, type.size(), values,
bitsPerValue == globalPaletteBits ? palette::setIdAt : palette::setPaletteIndexAt);
}
}
@@ -123,7 +120,11 @@ public class PaletteType1_18 extends PaletteTypeBase {
return;
}
final long[] values = palette.createPackedValues(bitsPerValue, type.size(), bitsPerValue == globalPaletteBits);
final long[] values = CompactArrayUtil.createCompactArrayWithPadding(
bitsPerValue,
type.size(),
bitsPerValue == globalPaletteBits ? palette::idAt : palette::paletteIndexAt
);
Types.LONG_ARRAY_PRIMITIVE.write(buffer, values);
}
@@ -46,11 +46,12 @@ 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) {
if (bitsPerValue == globalPaletteBits) {
CompactArrayUtil.iterateCompactArrayWithPadding(bitsPerValue, type.size(), values, palette::setIdAt);
} else {
palette.setPaletteIndexes(values, bitsPerValue, valuesPerLong);
}
CompactArrayUtil.iterateCompactArrayWithPadding(
bitsPerValue,
type.size(),
values,
bitsPerValue == globalPaletteBits ? palette::setIdAt : palette::setPaletteIndexAt
);
}
}
@@ -60,7 +61,11 @@ public final class PaletteType1_21_5 extends PaletteType1_18 {
return;
}
final long[] values = palette.createPackedValues(bitsPerValue, type.size(), bitsPerValue == globalPaletteBits);
final long[] values = CompactArrayUtil.createCompactArrayWithPadding(
bitsPerValue,
type.size(),
bitsPerValue == globalPaletteBits ? palette::idAt : palette::paletteIndexAt
);
LongArrayType.writeFixedLength(buffer, values);
}
@@ -133,7 +133,8 @@ public class ItemType1_20_5 extends Type<Item> {
}
map.writeMap("components", components -> {
for (final StructuredData<?> data : item.dataContainer().data().values()) {
for (final Map.Entry<StructuredDataKey<?>, StructuredData<?>> entry : item.dataContainer().data().entrySet()) {
final StructuredData<?> data = entry.getValue();
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 registryValueType.byId(id);
return Key.of(registryValueType.byId(id));
} else {
return ops.context().registryAccess().registryKey(registryKey.registry().toString(), id);
return ops.context().registryAccess().registryKey(registryKey.key().toString(), id);
}
}
@@ -163,13 +163,6 @@ public class ParticleType extends DynamicType<Particle> {
particle.add(Types.INT, Types.INT.readPrimitive(buf)); // Color
particle.add(Types.FLOAT, Types.FLOAT.readPrimitive(buf)); // Power
};
public static final DataReader<Particle> GEYSER_BASE = (buf, particle) -> {
particle.add(Types.INT, Types.INT.readPrimitive(buf)); // Water blocks
particle.add(Types.FLOAT, Types.FLOAT.readPrimitive(buf)); // Burst impulse base
};
public static final DataReader<Particle> GEYSER = (buf, particle) -> {
particle.add(Types.INT, Types.INT.readPrimitive(buf)); // Water blocks
};
public static DataReader<Particle> item(Type<Item> item) {
return (buf, particle) -> particle.add(item, item.read(buf));
@@ -266,20 +259,12 @@ public class ParticleType extends DynamicType<Particle> {
return fill1_21_4(protocol).reader("tinted_leaves", Readers.COLOR);
}
public static DataFiller fill1_21_9(final Protocol<?, ?, ?, ?> protocol) {
return fill1_21_5(protocol)
public static void fill1_21_9(final Protocol<?, ?, ?, ?> protocol) {
fill1_21_5(protocol)
.reader("dragon_breath", Readers.POWER)
.reader("effect", Readers.SPELL)
.reader("instant_effect", Readers.SPELL)
.reader("flash", Readers.COLOR);
}
public static void fill26_2(final Protocol<?, ?, ?, ?> protocol) {
fill1_21_9(protocol)
.reader("geyser", Readers.GEYSER)
.reader("geyser_base", Readers.GEYSER_BASE)
.reader("geyser_poof", Readers.GEYSER_BASE)
.reader("geyser_plume", Readers.GEYSER);
}
}
}
@@ -30,32 +30,32 @@ import com.viaversion.viaversion.util.Key;
public final class RegistryValueType extends VarIntType implements RegistryKey {
private final Key registry;
private final Key[] keys;
private final Key key;
private final String[] names;
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]);
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]);
}
}
@Override
public void write(final Ops ops, final Integer value) {
Types.IDENTIFIER.write(ops, keys[value]);
Types.IDENTIFIER.write(ops, Key.of(names[value]));
}
public Key[] keys() {
return keys;
public String[] names() {
return names;
}
public Key byId(final int id) {
return keys[id];
public String byId(final int id) {
return names[id];
}
@Override
public Key registry() {
return registry;
public Key key() {
return key;
}
}
@@ -87,4 +87,4 @@ public final class ResolvableProfileType extends Type<ResolvableProfile> {
.writeOptional("model", Types.OPTIONAL_STRING, modelType)
);
}
}
};
@@ -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.registry().toString(), value.left());
final Key key = ops.context().registryAccess().registryKey(this.registryKey.key().toString(), value.left());
Types.IDENTIFIER.write(ops, key);
} else {
Types.IDENTIFIER.write(ops, Key.of(value.right()));
@@ -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.registry().toString(), value);
final Key key = ops.context().registryAccess().registryKey(this.registryKey.key().toString(), value);
Types.IDENTIFIER.write(ops, key);
}
}
@@ -47,7 +47,7 @@ public class Types1_20_5<K extends VersionedStructuredDataKeys, E extends Abstra
public final Type<StructuredData<?>[]> structuredDataArray = new ArrayType<>(structuredData);
public final Type<Item> item = new ItemType1_20_5(structuredData);
public final Type<Item> lengthPrefixedItem = new ItemType1_20_5(lengthPrefixedStructuredData);
public final ArrayType<Item> itemArray = new ArrayType<>(item);
public final Type<Item[]> itemArray = new ArrayType<>(item);
public final Type<Item> itemCost = new ItemCostType1_20_5(structuredDataArray);
public final Type<Item> optionalItemCost = new ItemCostType1_20_5.OptionalItemCostType(itemCost);
private K structuredDataKeys;
@@ -77,7 +77,7 @@ public class Types1_20_5<K extends VersionedStructuredDataKeys, E extends Abstra
}
@Override
public ArrayType<Item> itemArray() {
public Type<Item[]> itemArray() {
return itemArray;
}
@@ -92,7 +92,7 @@ public class Types1_20_5<K extends VersionedStructuredDataKeys, E extends Abstra
}
@Override
public ArrayType<Item> itemTemplateArray() {
public Type<Item[]> itemTemplateArray() {
return itemArray;
}
@@ -34,7 +34,7 @@ public class Types26_1<K extends VersionedStructuredDataKeys, E extends Abstract
public final Type<Item> itemTemplate = new ItemTemplateType26_1(structuredData);
public final Type<Item> optionalItemTemplate = new ItemTemplateType26_1.OptionalItemTemplateType(itemTemplate);
public final ArrayType<Item> itemTemplateArray = new ArrayType<>(itemTemplate);
public final Type<Item[]> itemTemplateArray = new ArrayType<>(itemTemplate);
public Types26_1(final Function<VersionedTypesHolder, K> keysSupplier, final Function<VersionedTypesHolder, E> entityDataTypesSupplier) {
super(null, entityDataTypesSupplier);
@@ -52,7 +52,7 @@ public class Types26_1<K extends VersionedStructuredDataKeys, E extends Abstract
}
@Override
public ArrayType<Item> itemTemplateArray() {
public Type<Item[]> itemTemplateArray() {
return itemTemplateArray;
}
}
@@ -26,7 +26,6 @@ import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_
import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_21_11;
import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_21_2;
import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_21_5;
import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys26_2;
import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_20_5;
import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21;
import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_11;
@@ -52,5 +51,4 @@ public final class VersionedTypes {
public static final Types1_20_5<StructuredDataKeys1_21_5, EntityDataTypes1_21_9> V1_21_9 = new Types1_20_5<>(StructuredDataKeys1_21_5::new, EntityDataTypes1_21_9::new);
public static final Types1_20_5<StructuredDataKeys1_21_11, EntityDataTypes1_21_11> V1_21_11 = new Types1_20_5<>(StructuredDataKeys1_21_11::new, EntityDataTypes1_21_11::new);
public static final Types26_1<StructuredDataKeys1_21_11, EntityDataTypes26_1> V26_1 = new Types26_1<>(StructuredDataKeys1_21_11::new, EntityDataTypes26_1::new);
public static final Types26_1<StructuredDataKeys26_2, EntityDataTypes26_1> V26_2 = new Types26_1<>(StructuredDataKeys26_2::new, EntityDataTypes26_1::new);
}
@@ -46,7 +46,7 @@ public interface VersionedTypesHolder {
*/
Type<Item> item();
ArrayType<Item> itemArray();
Type<Item[]> itemArray();
/**
* Returns the unmapped item stack template type, used for certain recipes/templates in 26.1+.
@@ -57,7 +57,7 @@ public interface VersionedTypesHolder {
Type<Item> optionalItemTemplate();
ArrayType<Item> itemTemplateArray();
Type<Item[]> itemTemplateArray();
/**
* Returns the unmapped item cost type, used in merchant offers.
@@ -88,67 +88,6 @@ 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)];
@@ -1,91 +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.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,17 +24,25 @@ package com.viaversion.viaversion.util;
import org.checkerframework.checker.nullness.qual.Nullable;
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;
public final class Key {
/**
* 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 {
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;
}
/**
* Creates a new key with the given namespace and path.
@@ -44,10 +52,7 @@ public abstract sealed class Key permits MinecraftKey, CustomKey {
* @return a new key with the given namespace and path
*/
public static Key of(final String namespace, final String path) {
if (namespace.equals(MINECRAFT_NAMESPACE) || namespace.isEmpty()) {
return ofPath(path); // ends up compacting it
}
return new CustomKey(namespace + ':' + path, namespace, path);
return new Key(namespace + ':' + path, namespace, path);
}
/**
@@ -57,7 +62,7 @@ public abstract sealed class Key permits MinecraftKey, CustomKey {
* @return a new key with the given path and the default namespace
*/
public static Key ofPath(final String path) {
return new MinecraftKey.CompactMinecraftKey(path);
return new Key(path, MINECRAFT_NAMESPACE, path);
}
/**
@@ -72,17 +77,9 @@ public abstract sealed class Key permits MinecraftKey, CustomKey {
return ofPath(identifier);
}
final String namespace = separatorIndex == 0 ? MINECRAFT_NAMESPACE : identifier.substring(0, separatorIndex);
final String path = identifier.substring(separatorIndex + 1);
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);
return new Key(identifier, namespace, path);
}
/**
@@ -99,38 +96,6 @@ public abstract sealed class Key permits MinecraftKey, CustomKey {
}
}
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) {
@@ -141,14 +106,16 @@ public abstract sealed class Key permits MinecraftKey, CustomKey {
public static String namespace(final String identifier) {
final int index = identifier.indexOf(':');
if (index == -1 || index == 0) {
if (index == -1) {
return MINECRAFT_NAMESPACE;
} else if (index == 0) {
return MINECRAFT_NAMESPACE;
}
return identifier.substring(0, index);
}
public static String stripMinecraftNamespace(final String identifier) {
if (identifier.startsWith(MINECRAFT_NAMESPACE_WITH_COLON)) {
if (identifier.startsWith("minecraft:")) {
return identifier.substring(MINECRAFT_NAMESPACE_LENGTH + 1);
} else if (!identifier.isEmpty() && identifier.charAt(0) == ':') {
return identifier.substring(1);
@@ -163,7 +130,7 @@ public abstract sealed class Key permits MinecraftKey, CustomKey {
public static String namespaced(final String identifier) {
final int index = identifier.indexOf(':');
if (index == -1) {
return MINECRAFT_NAMESPACE_WITH_COLON + identifier;
return "minecraft:" + identifier;
} else if (index == 0) {
return MINECRAFT_NAMESPACE + identifier;
}
@@ -173,13 +140,98 @@ public abstract sealed class Key permits MinecraftKey, CustomKey {
public static boolean isValid(final String identifier) {
final int separatorIndex = identifier.indexOf(':');
if (separatorIndex == -1) {
return MinecraftKey.isValidPath(identifier);
return isValidPath(identifier);
} else if (separatorIndex == 0) {
return MinecraftKey.isValidPath(identifier.substring(1));
return isValidPath(identifier.substring(1));
}
final String namespace = identifier.substring(0, separatorIndex);
final String path = identifier.substring(separatorIndex + 1);
return MinecraftKey.isValidNamespace(namespace) && MinecraftKey.isValidPath(path);
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;
}
}
@@ -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.Arrays;
import java.util.Collection;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class KeyMappings {
private final Object2IntMap<String> keyToId;
private final Key[] keys;
private final String[] keys;
public KeyMappings(final Key... keys) {
public KeyMappings(final String... keys) {
this.keys = keys;
keyToId = new Object2IntOpenHashMap<>(keys.length);
keyToId.defaultReturnValue(-1);
for (int i = 0; i < keys.length; i++) {
keyToId.put(keys[i].minimized(), i);
keyToId.put(keys[i], i);
}
}
public KeyMappings(final String... keys) {
this(Arrays.stream(keys).map(Key::of).toArray(Key[]::new));
public KeyMappings(final Collection<String> keys) {
this(keys.toArray(new String[0]));
}
public KeyMappings(final ListTag<StringTag> keys) {
@@ -52,13 +52,6 @@ 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;
}
@@ -69,7 +62,7 @@ public final class KeyMappings {
return keyToId.getInt(Key.stripMinecraftNamespace(identifier));
}
public Key[] keys() {
public String[] keys() {
return keys;
}
@@ -1,175 +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.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,7 +23,6 @@
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 {
@@ -50,20 +49,4 @@ 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);
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ repositories {
dependencies {
// version must be manually kept in sync with the one in root project settings.gradle.kts
implementation("com.gradleup.shadow:shadow-gradle-plugin:9.4.2")
implementation("com.gradleup.shadow:shadow-gradle-plugin:9.4.1")
// A nice no-conflict comment for patching in downgrading
}
+1
View File
@@ -1,5 +1,6 @@
plugins {
base
eclipse
id("via.build-logic")
}
@@ -245,8 +245,7 @@ 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,6 +51,8 @@ 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();
@@ -243,15 +245,30 @@ public class ViaManagerImpl implements ViaManager {
platform.getLogger().info("ViaVersion has been disabled; uninjected the platform and shut down the scheduler.");
}
private void checkJavaVersion() {
int javaVersion = Runtime.version().feature();
if (javaVersion < 17) {
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) {
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 (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.");
} else if (version < 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.");
}
}
@@ -17,9 +17,7 @@
*/
package com.viaversion.viaversion.codec.hash;
// Required so long as Java 8 downgraded VV jars are offered
@Deprecated
final class FallbackCRC32C implements HashFunction {
final class CRC32C implements HashFunction {
private static final int[] CRC32C_TABLE = new int[256];
@@ -20,9 +20,7 @@ package com.viaversion.viaversion.codec.hash;
@FunctionalInterface
public interface HashFunction {
static HashFunction crc32c() {
return Runtime.version().feature() < 9 ? new FallbackCRC32C() : new JavaCRC32C();
}
HashFunction CRC32C = new CRC32C();
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() * Character.BYTES) + Byte.BYTES + Integer.BYTES)
hashBuilder.preSize(sequence.length() + Byte.BYTES + Integer.BYTES)
.writeByte(TAG_STRING)
.writeInt(sequence.length())
.writeString(sequence);
@@ -1,32 +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.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,7 +72,6 @@ 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;
@@ -147,7 +146,6 @@ 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);
@@ -477,11 +475,6 @@ public class AbstractViaConfig extends Config implements ViaVersionConfig {
return reloadDisconnectMessage;
}
@Override
public boolean isMultiReddustColorFix() {
return multiReddustColorFix;
}
@Override
public boolean is1_13TeamColourFix() {
return teamColourFix;
@@ -35,7 +35,6 @@ 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) {
@@ -121,7 +120,7 @@ public class ConnectionManagerImpl implements ConnectionManager {
@Override
public Set<UserConnection> getConnections() {
return connectionsView;
return Collections.unmodifiableSet(connections);
}
}
@@ -17,6 +17,7 @@
*/
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;
@@ -42,28 +43,30 @@ 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.SplittableRandom;
import java.util.Set;
import java.util.UUID;
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;
private static final int PASSTHROUGH_DATA_BYTES = Long.BYTES * 2 + 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 LongSet passthroughTokens = new LongOpenHashSet();
private final Set<UUID> passthroughTokens = Collections.newSetFromMap(CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.<UUID, Boolean>build().asMap());
private final ProtocolInfo protocolInfo = new ProtocolInfoImpl();
private final Channel channel;
private final boolean clientSide;
@@ -258,7 +261,7 @@ public class UserConnectionImpl implements UserConnection {
if (shouldTransformPacket()) {
// Bypass serverbound packet decoder transforming
Types.VAR_INT.writePrimitive(buf, PacketWrapper.PASSTHROUGH_ID);
Types.LONG.writePrimitive(buf, generatePassthroughToken());
Types.UUID.write(buf, generatePassthroughToken());
}
buf.writeBytes(packet);
@@ -343,13 +346,11 @@ public class UserConnectionImpl implements UserConnection {
}
final int id = Types.VAR_INT.readPrimitive(buf);
if (id == PacketWrapper.PASSTHROUGH_ID && buf.readableBytes() >= Long.BYTES) {
if (removePassthroughToken(buf.readLong())) {
return;
if (id == PacketWrapper.PASSTHROUGH_ID) {
if (!passthroughTokens.remove(Types.UUID.read(buf))) {
throw new IllegalArgumentException("Invalid token");
}
// Move reader back and treat it like a regular packet
buf.readerIndex(buf.readerIndex() - Long.BYTES);
return;
}
final int valuesReaderIndex = buf.readerIndex();
@@ -442,22 +443,10 @@ public class UserConnectionImpl implements UserConnection {
}
@Override
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);
}
public UUID generatePassthroughToken() {
UUID token = UUID.randomUUID();
passthroughTokens.add(token);
return token;
}
@Override
@@ -23,10 +23,9 @@ 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;
@@ -49,8 +48,6 @@ 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;
@@ -63,10 +60,8 @@ public class EntityTrackerBase implements EntityTracker, ClientEntityIdChangeLis
}
@Override
public TrackedEntity addEntity(int id, EntityType type) {
final TrackedEntityImpl entity = new TrackedEntityImpl(type);
entities.put(id, entity);
return entity;
public void addEntity(int id, EntityType type) {
entities.put(id, new TrackedEntityImpl(type));
}
@Override
@@ -86,8 +81,20 @@ public class EntityTrackerBase implements EntityTracker, ClientEntityIdChangeLis
}
@Override
public TrackedEntity removeEntity(int id) {
return entities.remove(id);
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);
}
@Override
@@ -157,10 +164,6 @@ 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;
}
@@ -201,27 +204,9 @@ 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;
@@ -0,0 +1,60 @@
/*
* 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,16 +17,13 @@
*/
package com.viaversion.viaversion.data.entity;
import com.viaversion.viaversion.api.connection.StorableObject;
import com.viaversion.viaversion.api.data.entity.StoredEntityData;
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 Map<Class<?>, StorableObject> storedObjects;
private StoredEntityData data;
private boolean sentEntityData;
public TrackedEntityImpl(final EntityType entityType) {
@@ -39,32 +36,16 @@ public final class TrackedEntityImpl implements TrackedEntity {
}
@Override
public boolean has(final Class<? extends StorableObject> objectClass) {
return storedObjects != null && storedObjects.containsKey(objectClass);
}
@Override
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
public StoredEntityData data() {
if (data == null) {
data = new StoredEntityDataImpl(entityType);
}
return storedObjects;
return data;
}
@Override
public boolean hasData() {
return data != null;
}
@Override
@@ -81,7 +62,7 @@ public final class TrackedEntityImpl implements TrackedEntity {
public String toString() {
return "TrackedEntityImpl{" +
"entityType=" + entityType +
", storedObjects=" + storedObjects +
", data=" + data +
", 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,7 +31,6 @@ 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;
@@ -55,7 +54,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 = UUIDUtil.randomUUID();
this.uuid = UUID.randomUUID();
this.title = title;
this.health = health;
this.color = color == null ? BossColor.PURPLE : color;
@@ -22,7 +22,6 @@ 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;
@@ -58,7 +57,7 @@ public class ViaCodecHandler extends ByteToMessageCodec<ByteBuf> implements ViaC
throw CancelDecoderException.generate(null);
}
final ByteBuf transformedBuf = ByteBufUtil.copy(ctx.alloc(), in);
final ByteBuf transformedBuf = ctx.alloc().buffer().writeBytes(in);
try {
if (this.connection.shouldTransformPacket()) {
this.connection.transformIncoming(transformedBuf, CancelDecoderException::generate);
@@ -90,19 +90,18 @@ import com.viaversion.viaversion.protocols.v1_8to1_9.Protocol1_8To1_9;
import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.Protocol1_9_1To1_9_3;
import com.viaversion.viaversion.protocols.v1_9_3to1_10.Protocol1_9_3To1_10;
import com.viaversion.viaversion.protocols.v1_9to1_9_1.Protocol1_9To1_9_1;
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;
@@ -220,7 +219,6 @@ public class ProtocolManagerImpl implements ProtocolManager {
registerProtocol(new Protocol1_21_9To1_21_11(), ProtocolVersion.v1_21_11, ProtocolVersion.v1_21_9);
registerProtocol(new Protocol1_21_11To26_1(), ProtocolVersion.v26_1, ProtocolVersion.v1_21_11);
registerProtocol(new Protocol26_1To26_2(), ProtocolVersion.v26_2, ProtocolVersion.v26_1);
}
@Override
@@ -323,13 +321,17 @@ public class ProtocolManagerImpl implements ProtocolManager {
}
// Calculate path
List<ProtocolPathEntry> path = calculateProtocolPath(clientVersion, serverVersion);
if (path == null) {
Object2ObjectSortedMap<ProtocolVersion, Protocol> outputPath = getProtocolPath(new Object2ObjectLinkedOpenHashMap<>(), clientVersion, serverVersion);
if (outputPath == 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;
}
@@ -345,70 +347,56 @@ public class ProtocolManagerImpl implements ProtocolManager {
}
/**
* Calculates the shortest path to get from an input protocol to the server's protocol using a breadth-first search.
* Calculates a path to get from an input protocol to the server's protocol.
*
* @param clientVersion input version
* @param current current items in the path
* @param clientVersion current input version
* @param serverVersion desired output version
* @return shortest path to the server version, or null if none exists
* @return path that has been generated, null if failed
*/
private @Nullable List<ProtocolPathEntry> calculateProtocolPath(ProtocolVersion clientVersion, ProtocolVersion serverVersion) {
Set<ProtocolVersion> visited = new HashSet<>();
visited.add(clientVersion);
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.
Deque<PathNode> queue = new ArrayDeque<>();
queue.add(new PathNode(null, clientVersion, null, 0));
// First, check if there is any protocols for this
Object2ObjectMap<ProtocolVersion, Protocol> toServerProtocolMap = registryMap.get(clientVersion);
if (toServerProtocolMap == null) {
return null; // Not supported
}
PathNode node;
while ((node = queue.poll()) != null) {
if (node.depth() > maxProtocolPathSize){
continue; // Fail-safe, protocol too complicated
}
// 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
}
Object2ObjectMap<ProtocolVersion, Protocol> toServerProtocolMap = registryMap.get(node.version());
if (toServerProtocolMap == null) {
continue; // Not supported
}
// 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;
// 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)) {
// 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) {
continue;
}
}
// 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;
}
}
// Create a copy
Object2ObjectSortedMap<ProtocolVersion, Protocol> newCurrent = new Object2ObjectLinkedOpenHashMap<>(current);
newCurrent.put(translatedToVersion, entry.getValue());
// Go on to the next node
visited.add(translatedToVersion);
queue.add(new PathNode(node, translatedToVersion, entry.getValue(), node.depth() + 1));
// 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;
}
}
return null; // No path 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) {
return shortest; // null if none found
}
@Override
@@ -122,11 +122,6 @@ public final class RegistrationBuilder {
this.adapter = adapter;
}
/**
* @param max max version, exclusive
* @param action registration
* @return self
*/
public TypedRangesBuilder<CU, SU, R> to(final ProtocolVersion max, final TypedRegistrationAction<CU, SU, R> action) {
to(max, wrapAction(action));
return this;
@@ -78,7 +78,7 @@ public final class SharedRegistrations {
}
for (final VersionedTemplateGroup group : groups) {
if (group.min().newerThan(version)) {
if (group.min().newerThan(version)) { // TODO binary search?
// Sorted by the minimum, so we can stop early if we're out
break;
}
@@ -38,7 +38,7 @@ final class BlockRegistrations {
ctx.clientbound(ClientboundPackets1_13.CHUNK_BLOCKS_UPDATE, br::registerChunkBlocksUpdate);
}
private static <CU extends ClientboundPacketType> void common1_13(final RegistrationContext<CU, ?> ctx, final BlockRewriter<CU> br) {
private static <CU extends ClientboundPacketType> void common1_13(RegistrationContext<CU, ?> ctx, 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(final RegistrationContext<CU, ?> ctx, final BlockRewriter<CU> br) {
private static <CU extends ClientboundPacketType> void common1_19(RegistrationContext<CU, ?> ctx, 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);
@@ -86,8 +86,7 @@ public final class DefaultRegistrations {
.to(ProtocolVersion.v1_21_5, TextComponentRegistrations::registerComponents1_21_4)
)
.ranges(TextComponentRegistrations::nbtText, ProtocolVersion.v1_21_5, b -> b
.to(ProtocolVersion.v26_2, TextComponentRegistrations::registerComponents1_21_5)
.since(TextComponentRegistrations::registerComponents26_2)
.since(TextComponentRegistrations::registerComponents1_21_5)
)
.ranges(ProtocolVersion.v1_17_1, sbeps -> sbeps
@@ -183,14 +183,6 @@ final class TextComponentRegistrations {
ctx.clientbound(ClientboundPackets1_21_5.PLAYER_CHAT, cr::registerPlayerChat1_21_5);
}
static <CU extends ClientboundPacketType> void registerComponents26_2(final RegistrationContext<CU, ?> ctx, final NBTComponentRewriter<CU> cr) {
common1_20_5(ctx, cr);
ctx.clientbound(ClientboundPackets1_21_5.SET_PLAYER_TEAM, cr::registerSetPlayerTeam26_2);
ctx.clientbound(ClientboundPackets1_21_5.DISGUISED_CHAT, cr::registerDisguisedChat);
ctx.clientbound(ClientboundPackets1_21_5.PLAYER_INFO_UPDATE, cr::registerPlayerInfoUpdate1_21_4);
ctx.clientbound(ClientboundPackets1_21_5.PLAYER_CHAT, cr::registerPlayerChat1_21_5);
}
static @Nullable <CU extends ClientboundPacketType, SU extends ServerboundPacketType> ComponentRewriterBase<CU> text(final RegistrationContext<CU, SU> ctx) {
return (ComponentRewriterBase<CU>) ctx.protocol().getComponentRewriter();
}
@@ -30,14 +30,12 @@ import com.viaversion.viaversion.api.type.types.version.Types1_20_5;
import com.viaversion.viaversion.api.type.types.version.VersionedTypes;
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_4to1_21_5.rewriter.RecipeDisplayRewriter1_21_5;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPackets1_21_6;
import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundConfigurationPackets1_21_9;
import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundConfigurationPackets1_21_9;
import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundPacket1_21_9;
import com.viaversion.viaversion.rewriter.BlockRewriter;
import com.viaversion.viaversion.rewriter.ParticleRewriter;
import com.viaversion.viaversion.rewriter.RecipeDisplayRewriter;
import com.viaversion.viaversion.rewriter.RegistryDataRewriter;
import com.viaversion.viaversion.rewriter.TagRewriter;
import com.viaversion.viaversion.rewriter.block.BlockRewriter1_21_5;
@@ -60,7 +58,6 @@ final class Protocol98_1To99_1 extends AbstractProtocol<ClientboundPacket26_1, C
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 ComponentRewriter99_1(this);
private final RecipeDisplayRewriter<ClientboundPacket26_1> recipeewriter = new RecipeDisplayRewriter1_21_5<>(this);
private final RegistryDataRewriter registryDataRewriter = new RegistryDataRewriter(this);
public Protocol98_1To99_1() {
@@ -135,11 +132,6 @@ final class Protocol98_1To99_1 extends AbstractProtocol<ClientboundPacket26_1, C
return registryDataRewriter;
}
@Override
public RecipeDisplayRewriter<ClientboundPacket26_1> getRecipeRewriter() {
return recipeewriter;
}
@Override
public ParticleRewriter<ClientboundPacket26_1> getParticleRewriter() {
return particleRewriter;
@@ -18,12 +18,10 @@
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();
@@ -33,9 +31,10 @@ public class EntityTracker1_11 extends EntityTrackerBase {
}
@Override
public @Nullable TrackedEntity removeEntity(int entityId) {
public void removeEntity(int entityId) {
super.removeEntity(entityId);
removeHologram(entityId);
return super.removeEntity(entityId);
}
public boolean addHologram(int entId) {
@@ -17,8 +17,12 @@
*/
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;
@@ -31,15 +35,12 @@ 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;
@@ -161,14 +162,16 @@ public final class ConnectionData {
}
public static void init() {
final ProtocolVersion version = Via.getAPI().getServerVersion().lowestSupportedProtocolVersion();
if (!Via.getConfig().isServersideBlockConnections() || (version.isKnown() && version.newerThan(ProtocolVersion.v1_12_2))) {
if (!Via.getConfig().isServersideBlockConnections()) {
return;
}
Via.getPlatform().getLogger().info("Loading block connection mappings ...");
CompoundTag blockStatesData = MappingDataLoader.INSTANCE.loadNBT("blockstates-1.13.nbt");
BlockStates1_13.forEach(blockStatesData, KEY_TO_ID::put);
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);
}
connectionHandlerMap = new Int2ObjectOpenHashMap<>(3650);
@@ -176,28 +179,41 @@ public final class ConnectionData {
blockConnectionData = new Int2ObjectOpenHashMap<>(2048);
CompoundTag data = MappingDataLoader.INSTANCE.loadNBT("blockConnections.nbt");
for (CompoundTag profileTag : data.getListTag("profiles", CompoundTag.class)) {
ListTag<CompoundTag> blockConnectionMappings = data.getListTag("data", CompoundTag.class);
for (CompoundTag blockTag : blockConnectionMappings) {
BlockData blockData = new BlockData();
for (Entry<String, Tag> entry : profileTag.entrySet()) {
for (Entry<String, Tag> entry : blockTag.entrySet()) {
String key = entry.getKey();
if (key.equals("ids")) {
if (key.equals("id") || key.equals("ids")) {
continue;
}
byte connections = ((NumberTag) entry.getValue()).asByte();
boolean[] attachingFaces = new boolean[4];
for (int i = 0; i < attachingFaces.length; i++) {
attachingFaces[i] = (connections & (1 << i)) != 0;
ByteArrayTag connections = (ByteArrayTag) entry.getValue();
for (byte blockFaceId : connections.getValue()) {
attachingFaces[blockFaceId] = true;
}
int connectionTypeId = Integer.parseInt(key);
blockData.put(connectionTypeId, attachingFaces);
}
IdRanges.forEachId(profileTag.getByteArrayTag("ids"), id -> blockConnectionData.put(id, blockData));
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(data.getByteArrayTag("occluding-states"), OCCLUDING_STATES::add);
IntArrayTag occludingStatesArray = data.getIntArrayTag("occluding-states");
for (final int blockStateId : occludingStatesArray.getValue()) {
OCCLUDING_STATES.add(blockStateId);
}
}
List<ConnectorInitAction> initActions = new ArrayList<>();
@@ -17,6 +17,7 @@
*/
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;
@@ -25,13 +26,12 @@ 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,14 +46,13 @@ 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()) {
reverse.computeIfAbsent(val, k -> new ArrayList<>()).add(entry.getKey());
String[] previous = fallbackReverseMapping.get(val);
if (previous == null) previous = PREVIOUS;
fallbackReverseMapping.put(val, ObjectArrays.concat(previous, 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);
}
@@ -1,69 +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.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++);
}
}
}
}
@@ -22,6 +22,7 @@ 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;
@@ -32,6 +33,7 @@ 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;
@@ -92,46 +94,37 @@ public class MappingData1_13 extends MappingDataBase {
}
}
String[] translationLines;
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;
try (Reader reader = new InputStreamReader(MappingData1_13.class.getClassLoader()
.getResourceAsStream("assets/viaversion/data/mapping-lang-1.12-1.13.txt"), StandardCharsets.UTF_8)) {
translationLines = CharStreams.toString(reader).split("\n");
.getResourceAsStream("assets/viaversion/data/en_US.properties"), StandardCharsets.UTF_8)) {
unmappedTranslationLines = CharStreams.toString(reader).split("\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
// 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) == '#') {
for (String line : unmappedTranslationLines) {
if (line.isEmpty()) {
continue;
}
int keySeparator = line.indexOf('=');
if (keySeparator == -1) {
String[] keyAndTranslation = line.split("=", 2);
if (keyAndTranslation.length != 2) {
continue;
}
String translation = line.substring(keySeparator + 1);
if (line.charAt(0) == '-') {
mojangTranslation.put(line.substring(1, keySeparator), translation);
continue;
}
String key = keyAndTranslation[0];
String translation = keyAndTranslation[1].replaceAll("%(\\d\\$)?d", "%$1s").trim();
mojangTranslation.put(key, translation);
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));
// 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);
}
}
}
@@ -17,100 +17,30 @@
*/
package com.viaversion.viaversion.protocols.v1_12_2to1_13.data;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
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)) {
object = GsonUtil.getGson().fromJson(reader, JsonObject.class);
recipes = GsonUtil.getGson().fromJson(
reader,
new TypeToken<Map<String, Recipe>>() {
}.getType()
);
} catch (IOException e) {
throw new RuntimeException(e);
}
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;
// Ignored
}
public record Recipe(String type, String group, int width, int height, float experience, int cookingTime,
@@ -51,7 +51,6 @@ 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);
@@ -513,42 +512,19 @@ 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 offset = wrapper.get(Types.FLOAT, i + 3);
float colorValue = redstoneDustColor(speed, offset, i == 0);
//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;
}
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;
}
}
@@ -587,34 +563,6 @@ 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.
@@ -18,10 +18,11 @@
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 com.viaversion.viaversion.api.data.IdRanges;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
public class MappingData1_14 extends MappingDataBase {
@@ -35,10 +36,12 @@ public class MappingData1_14 extends MappingDataBase {
@Override
public void loadExtras(final CompoundTag data) {
final CompoundTag heightmap = MappingDataLoader.INSTANCE.loadNBT("heightmap-1.14.nbt");
this.motionBlocking = IdRanges.decode(heightmap.getByteArrayTag("motionBlocking"));
final IntArrayTag motionBlocking = heightmap.getIntArrayTag("motionBlocking");
this.motionBlocking = new IntOpenHashSet(motionBlocking.getValue());
if (Via.getConfig().isNonFullBlockLightFix()) {
this.nonFullBlocks = IdRanges.decode(heightmap.getByteArrayTag("nonFullBlocks"));
final IntArrayTag nonFullBlocks = heightmap.getIntArrayTag("nonFullBlocks");
this.nonFullBlocks = new IntOpenHashSet(nonFullBlocks.getValue());
}
}
@@ -18,12 +18,10 @@
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<>();
@@ -40,11 +38,12 @@ public class EntityTracker1_14 extends EntityTrackerBase {
}
@Override
public @Nullable TrackedEntity removeEntity(int entityId) {
public void removeEntity(int entityId) {
super.removeEntity(entityId);
insentientData.remove(entityId);
sleepingAndRiptideData.remove(entityId);
playerEntityFlags.remove(entityId);
return super.removeEntity(entityId);
}
public byte getInsentientData(int entity) {
@@ -37,7 +37,6 @@ 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> {
@@ -94,7 +93,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, UUIDUtil.randomUUID()); // uuid
wrapper.write(Types.UUID, UUID.randomUUID()); // uuid
wrapper.write(Types.VAR_INT, EntityTypes1_16.LIGHTNING_BOLT.getId()); // entity type
wrapper.passthrough(Types.DOUBLE); // x
@@ -36,7 +36,6 @@ 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;
@@ -49,7 +48,6 @@ 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() {
@@ -58,7 +56,8 @@ public final class Protocol1_17_1To1_18 extends AbstractProtocol<ClientboundPack
@Override
protected void registerPackets() {
super.registerPackets();
entityRewriter.register();
itemRewriter.register();
WorldPacketRewriter1_18.register(this);
registerServerbound(ServerboundPackets1_17.CLIENT_INFORMATION, new PacketHandlers() {
@@ -111,11 +110,6 @@ 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;
@@ -45,7 +45,6 @@ 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;
@@ -342,19 +341,6 @@ 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();
}
@@ -369,7 +355,6 @@ 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));
}
@@ -26,16 +26,12 @@ 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);
}
@@ -131,43 +127,6 @@ 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);
}
@@ -1,37 +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.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;
}
}
@@ -23,7 +23,6 @@ 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;
@@ -70,17 +69,6 @@ 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);
@@ -113,31 +101,6 @@ 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
@@ -417,20 +380,4 @@ 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;
};
}
}
@@ -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.OPTIONAL_VAR_INT, null); // No source entity
wrapper.write(Types.OPTIONAL_VAR_INT, null); // No direct source entity
wrapper.write(Types.VAR_INT, 0); // No source entity
wrapper.write(Types.VAR_INT, 0); // No direct source entity
wrapper.write(Types.BOOLEAN, false); // No source position
return;
}
@@ -142,6 +142,14 @@ public final class EntityPacketRewriter1_20 extends EntityRewriter<ClientboundPa
registerEntityDataTypeHandler(Types1_20.ENTITY_DATA_TYPES.itemType, Types1_20.ENTITY_DATA_TYPES.blockStateType, Types1_20.ENTITY_DATA_TYPES.optionalBlockStateType, Types1_20.ENTITY_DATA_TYPES.particleType, null);
registerBlockStateHandler(EntityTypes1_19_4.ABSTRACT_MINECART, 11);
// Purpur's Rainglow patch adds a non-vanilla SQUID_COLOR (String) accessor on GlowSquid,
// declared right after DATA_DARK_TICKS_REMAINING, so the server emits it at source index 17.
// AGEABLE_WATER_CREATURE.addIndex(16) in v1_21To1_21_2 later shifts it to index 18, where
// 1.21.11+ clients only allocate 18 slots (max valid index 17) and crash with
// ArrayIndexOutOfBoundsException: Index 18 out of bounds for length 18. Drop at source.
// Vanilla 1.19.4 GlowSquid has no slot 17, so this is safe for non-Purpur servers.
filter().type(EntityTypes1_19_4.GLOW_SQUID).cancel(17);
// Rotate item display by 180 degrees around the Y axis
filter().type(EntityTypes1_19_4.ITEM_DISPLAY).handler((event, data) -> {
if (event.trackedEntity().hasSentEntityData() || event.hasExtraData()) {
@@ -17,7 +17,6 @@
*/
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;
@@ -137,7 +136,7 @@ public final class BannerPatterns1_20_5 {
return null;
}
public static Key[] keys() {
public static String[] keys() {
return PATTERNS.keys();
}
}
@@ -1550,9 +1550,8 @@ public class ComponentRewriter1_20_5<C extends ClientboundPacketType> extends Js
}
protected Item[] itemArrayFromTag(final UserConnection connection, final ListTag<CompoundTag> tag) {
final int size = Math.min(tag.size(), 1024);
final Item[] items = new Item[size];
for (int i = 0; i < size; i++) {
final Item[] items = new Item[tag.size()];
for (int i = 0; i < tag.size(); i++) {
items[i] = itemFromTag(connection, tag.get(i));
}
return items;
@@ -54,7 +54,6 @@ 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;
@@ -225,14 +224,15 @@ 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 Key[] keys = BannerPatterns1_20_5.keys();
final RegistryEntry[] patternEntries = new RegistryEntry[keys.length];
final RegistryEntry[] patternEntries = new RegistryEntry[BannerPatterns1_20_5.keys().length];
final String[] keys = BannerPatterns1_20_5.keys();
for (int i = 0; i < keys.length; i++) {
final CompoundTag pattern = new CompoundTag();
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);
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);
}
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(UUIDUtil.randomUUID().toString());
entries[i] = first.withKey(UUID.randomUUID().toString());
}
}
}
Loaded 100 of 198 files, more files were not shown because too many files have changed in this diff. Show more