Compare commits

...
18 Commits
Author SHA1 Message Date
Nassim Jahnke b67d7c076f Fix dust/dust_color_transition particles in biome data
Fixes #926
2024-12-05 17:16:57 +01:00
Nassim Jahnke 9d5cd04d4b Always update storage data 2024-11-27 21:50:46 +01:00
Nassim Jahnke 30499c8b30 Fix entity teleport for non-self in 1.21.2->1.21.1
Fixes #922
2024-11-27 21:47:40 +01:00
Nassim Jahnke fb328d4753 Fix recipe removal 2024-11-26 18:16:42 +01:00
Nassim Jahnke 5347820c28 Fix typo 2024-11-25 10:18:16 +01:00
Nassim Jahnke c4af653e42 Fix 1.21 client-sent food using_converts_to handling 2024-11-25 10:17:10 +01:00
github-actions[bot]andgradle-update-robot 5240487a35 Update Gradle Wrapper from 8.11 to 8.11.1 (#920)
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
Co-authored-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2024-11-24 02:09:33 +01:00
EnZaXDandNassim Jahnke 276953f6cb Backup inconvertible item component data for creative clients in 1.21+ (#907)
Co-authored-by: Nassim Jahnke <nassim@njahnke.dev>
2024-11-22 10:36:42 +01:00
EnZaXD 1dfef5b97e Emulate block and item display entities in 1.19.4->1.19.3 (#871) 2024-11-18 14:38:20 +01:00
Nassim Jahnke 4a9c98aa1b Address Gradle deprecation warnings 2024-11-17 13:45:25 +01:00
github-actions[bot]andgradle-update-robot 07e2848e5b Update Gradle Wrapper from 8.10.2 to 8.11 (#917)
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
Co-authored-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2024-11-17 12:23:33 +01:00
Gero 1db6824391 Fix remapping of 1.21.3 PLAYER_INFO_UPDATE list order (#915) 2024-11-11 17:01:35 +01:00
Nassim Jahnke 82a7c9bc3d Update VV usage 2024-11-11 15:00:34 +01:00
Nassim Jahnke 3a67be7fe5 Don't forget to rewrite item in set cursor and set player inventory 2024-11-08 17:25:40 +01:00
EnZaXD 8cb285db69 Change darkness->blindness in 1.19->1.18.2 (#908) 2024-11-07 09:18:33 +01:00
EnZaXD 04a692022a Move config option check for player face fix into packet handlers (#913) 2024-11-07 09:11:38 +01:00
Nassim Jahnke 498bec9065 Remove 'has recipe error' container data from smithing table
Fixes #909
2024-11-05 20:20:27 +01:00
Nassim Jahnke ab2a407742 Bump version to 5.1.2-SNAPSHOT 2024-11-05 20:18:30 +01:00
26 changed files with 924 additions and 76 deletions

No files matched your search

+11 -22
View File
@@ -1,33 +1,23 @@
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import java.io.ByteArrayOutputStream
import org.gradle.jvm.toolchain.JavaLanguageVersion
fun Project.latestCommitHash(): String {
val byteOut = ByteArrayOutputStream()
exec {
commandLine = listOf("git", "rev-parse", "--short", "HEAD")
standardOutput = byteOut
}
return byteOut.toString(Charsets.UTF_8.name()).trim()
return runGitCommand(listOf("rev-parse", "--short", "HEAD"))
}
fun Project.latestCommitMessage(): String {
val byteOut = ByteArrayOutputStream()
exec {
commandLine = listOf("git", "log", "-1", "--pretty=%B")
standardOutput = byteOut
}
return byteOut.toString(Charsets.UTF_8.name()).trim()
return runGitCommand(listOf("log", "-1", "--pretty=%B"))
}
fun Project.branchName(): String {
val byteOut = ByteArrayOutputStream()
exec {
commandLine = listOf("git", "rev-parse", "--abbrev-ref", "HEAD")
standardOutput = byteOut
}
return byteOut.toString(Charsets.UTF_8.name()).trim()
return runGitCommand(listOf("rev-parse", "--abbrev-ref", "HEAD"))
}
fun Project.runGitCommand(args: List<String>): String {
return providers.exec {
commandLine = listOf("git") + args
}.standardOutput.asBytes.get().toString(Charsets.UTF_8).trim()
}
fun Project.parseMinecraftSnapshotVersion(version: String): String? {
@@ -40,6 +30,5 @@ fun Project.parseMinecraftSnapshotVersion(version: String): String? {
}
fun JavaPluginExtension.javaTarget(version: Int) {
sourceCompatibility = JavaVersion.toVersion(version)
targetCompatibility = JavaVersion.toVersion(version)
toolchain.languageVersion.set(JavaLanguageVersion.of(version))
}
+2 -2
View File
@@ -16,10 +16,10 @@ val main = setOf(
projects.viabackwardsVelocity,
projects.viabackwardsSponge,
projects.viabackwardsFabric
).map { it.dependencyProject }
).map { it.path }
subprojects {
when (this) {
when (path) {
in main -> plugins.apply("vb.shadow-conventions")
else -> plugins.apply("vb.base-conventions")
}
@@ -36,6 +36,7 @@ public class ViaBackwardsConfig extends Config implements com.viaversion.viaback
private boolean handlePingsAsInvAcknowledgements;
private boolean bedrockAtY0;
private boolean sculkShriekersToCryingObsidian;
private boolean mapDarknessEffect;
private boolean suppressEmulationWarnings;
public ViaBackwardsConfig(File configFile, Logger logger) {
@@ -57,6 +58,7 @@ public class ViaBackwardsConfig extends Config implements com.viaversion.viaback
handlePingsAsInvAcknowledgements = getBoolean("handle-pings-as-inv-acknowledgements", false);
bedrockAtY0 = getBoolean("bedrock-at-y-0", false);
sculkShriekersToCryingObsidian = getBoolean("sculk-shriekers-to-crying-obsidian", false);
mapDarknessEffect = getBoolean("map-darkness-effect", true);
suppressEmulationWarnings = getBoolean("suppress-emulation-warnings", false);
}
@@ -100,6 +102,11 @@ public class ViaBackwardsConfig extends Config implements com.viaversion.viaback
return sculkShriekersToCryingObsidian;
}
@Override
public boolean mapDarknessEffect() {
return mapDarknessEffect;
}
@Override
public boolean suppressEmulationWarnings() {
return suppressEmulationWarnings;
@@ -78,6 +78,13 @@ public interface ViaBackwardsConfig extends Config {
*/
boolean sculkShriekerToCryingObsidian();
/**
* Maps the darkness effect to blindness for 1.18.2 clients on 1.19+ servers.
*
* @return true if enabled
*/
boolean mapDarknessEffect();
/**
* Suppresses warnings of missing emulations for certain features that are not supported (e.g. world height in 1.17+).
*
@@ -71,7 +71,7 @@ import java.util.logging.Logger;
public interface ViaBackwardsPlatform {
String MINIMUM_VV_VERSION = "5.1.1";
String MINIMUM_VV_VERSION = "5.1.2";
/**
* Initialize ViaBackwards.
@@ -18,13 +18,17 @@
package com.viaversion.viabackwards.api.rewriters;
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.viabackwards.api.BackwardsProtocol;
import com.viaversion.viabackwards.api.data.BackwardsMappingData;
import com.viaversion.viabackwards.api.data.MappedItem;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.Holder;
import com.viaversion.viaversion.api.minecraft.HolderSet;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.item.Item;
@@ -34,6 +38,8 @@ import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.rewriter.StructuredItemRewriter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import org.checkerframework.checker.nullness.qual.Nullable;
public class BackwardsStructuredItemRewriter<C extends ClientboundPacketType, S extends ServerboundPacketType,
@@ -165,6 +171,54 @@ public class BackwardsStructuredItemRewriter<C extends ClientboundPacketType, S
return new ArrayList<>(data.values());
}
protected Tag holderSetToTag(final HolderSet set) {
if (set.hasIds()) {
return new IntArrayTag(set.ids());
} else {
return new StringTag(set.tagKey());
}
}
protected HolderSet restoreHolderSet(final CompoundTag tag, final String key) {
final Tag savedTag = tag.get(key);
if (savedTag == null) {
return HolderSet.of(new int[0]);
}
if (savedTag instanceof StringTag tagKey) {
return HolderSet.of(tagKey.getValue());
} else if (savedTag instanceof IntArrayTag idsTag) {
return HolderSet.of(idsTag.getValue());
} else {
return HolderSet.of(new int[0]);
}
}
protected <V> Tag holderToTag(final Holder<V> holder, final BiConsumer<V, CompoundTag> valueSaveFunction) {
if (holder.hasId()) {
return new IntTag(holder.id());
} else {
final CompoundTag savedTag = new CompoundTag();
valueSaveFunction.accept(holder.value(), savedTag);
return savedTag;
}
}
protected <V> Holder<V> restoreHolder(final CompoundTag tag, final String key, final Function<CompoundTag, V> valueRestoreFunction) {
final Tag savedTag = tag.get(key);
if (savedTag == null) {
return Holder.of(0);
}
if (savedTag instanceof IntTag idTag) {
return Holder.of(idTag.asInt());
} else if (savedTag instanceof CompoundTag compoundTag) {
return Holder.of(valueRestoreFunction.apply(compoundTag));
} else {
return Holder.of(0);
}
}
@Override
public String nbtTagName() {
return "VB|" + protocol.getClass().getSimpleName();
@@ -240,7 +240,7 @@ public abstract class EntityRewriterBase<C extends ClientboundPacketType, T exte
}
protected PacketHandler getTrackerHandler(EntityType entityType) {
return wrapper -> tracker(wrapper.user()).addEntity((int) wrapper.get(Types.VAR_INT, 0), entityType);
return wrapper -> tracker(wrapper.user()).addEntity(wrapper.get(Types.VAR_INT, 0), entityType);
}
protected PacketHandler getPlayerTrackerHandler() {
@@ -237,17 +237,18 @@ public class EntityPacketRewriter1_13 extends LegacyEntityRewriter<ClientboundPa
positionAndLook.send(Protocol1_13To1_12_2.class);
});
if (ViaBackwards.getConfig().isFix1_13FacePlayer()) {
PacketHandler movementRemapper = wrapper -> {
final double x = wrapper.passthrough(Types.DOUBLE);
final double y = wrapper.passthrough(Types.DOUBLE);
final double z = wrapper.passthrough(Types.DOUBLE);
wrapper.user().get(PlayerPositionStorage1_13.class).setPosition(x, y, z);
};
protocol.registerServerbound(ServerboundPackets1_12_1.MOVE_PLAYER_POS, movementRemapper); // Player Position
protocol.registerServerbound(ServerboundPackets1_12_1.MOVE_PLAYER_POS_ROT, movementRemapper); // Player Position And Look (serverbound)
protocol.registerServerbound(ServerboundPackets1_12_1.MOVE_VEHICLE, movementRemapper); // Vehicle Move (serverbound)
}
PacketHandler movementRemapper = wrapper -> {
if (!ViaBackwards.getConfig().isFix1_13FacePlayer()) {
return;
}
final double x = wrapper.passthrough(Types.DOUBLE);
final double y = wrapper.passthrough(Types.DOUBLE);
final double z = wrapper.passthrough(Types.DOUBLE);
wrapper.user().get(PlayerPositionStorage1_13.class).setPosition(x, y, z);
};
protocol.registerServerbound(ServerboundPackets1_12_1.MOVE_PLAYER_POS, movementRemapper); // Player Position
protocol.registerServerbound(ServerboundPackets1_12_1.MOVE_PLAYER_POS_ROT, movementRemapper); // Player Position And Look (serverbound)
protocol.registerServerbound(ServerboundPackets1_12_1.MOVE_VEHICLE, movementRemapper); // Vehicle Move (serverbound)
}
@Override
@@ -469,7 +469,7 @@ public class PlayerPacketRewriter1_13 extends RewriterBase<Protocol1_13To1_12_2>
wrapper.write(Types.STRING, newChannel);
if (newChannel.equals("minecraft:register") || newChannel.equals("minecraft:unregister")) {
String[] channels = new String(wrapper.read(Types.REMAINING_BYTES), StandardCharsets.UTF_8).split("\0");
String[] channels = new String(wrapper.read(Types.SERVERBOUND_CUSTOM_PAYLOAD_DATA), StandardCharsets.UTF_8).split("\0");
List<String> rewrittenChannels = new ArrayList<>();
for (String s : channels) {
String rewritten = ItemPacketRewriter1_13.getNewPluginChannelId(s);
@@ -480,7 +480,7 @@ public class PlayerPacketRewriter1_13 extends RewriterBase<Protocol1_13To1_12_2>
}
}
if (!rewrittenChannels.isEmpty()) {
wrapper.write(Types.REMAINING_BYTES, Joiner.on('\0').join(rewrittenChannels).getBytes(StandardCharsets.UTF_8));
wrapper.write(Types.SERVERBOUND_CUSTOM_PAYLOAD_DATA, Joiner.on('\0').join(rewrittenChannels).getBytes(StandardCharsets.UTF_8));
} else {
wrapper.cancel();
}
@@ -23,11 +23,10 @@ import com.viaversion.viabackwards.api.rewriters.SoundRewriter;
import com.viaversion.viabackwards.api.rewriters.TranslatableRewriter;
import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.rewriter.BlockItemPacketRewriter1_19_4;
import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.rewriter.EntityPacketRewriter1_19_4;
import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.storage.EntityTracker1_19_4;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_4;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.data.entity.EntityTrackerBase;
import com.viaversion.viaversion.libs.gson.JsonElement;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ServerboundPackets1_19_3;
@@ -114,7 +113,7 @@ public final class Protocol1_19_4To1_19_3 extends BackwardsProtocol<ClientboundP
@Override
public void init(final UserConnection user) {
addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_19_4.PLAYER));
addEntityTracker(user, new EntityTracker1_19_4(user));
}
@Override
@@ -20,17 +20,24 @@ package com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.rewriter;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.ListTag;
import com.viaversion.nbt.tag.NumberTag;
import com.viaversion.viabackwards.api.entities.storage.EntityPositionHandler;
import com.viaversion.viabackwards.api.entities.storage.EntityReplacement;
import com.viaversion.viabackwards.api.rewriters.EntityRewriter;
import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.Protocol1_19_4To1_19_3;
import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.storage.EntityTracker1_19_4;
import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.storage.LinkedEntityStorage;
import com.viaversion.viaversion.api.data.entity.StoredEntityData;
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_3;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_4;
import com.viaversion.viaversion.api.minecraft.entitydata.EntityData;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.protocol.remapper.PacketHandler;
import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.version.Types1_19_3;
import com.viaversion.viaversion.api.type.types.version.Types1_19_4;
import com.viaversion.viaversion.libs.gson.JsonElement;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3;
import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ClientboundPackets1_19_4;
import com.viaversion.viaversion.util.TagUtil;
@@ -43,10 +50,45 @@ public final class EntityPacketRewriter1_19_4 extends EntityRewriter<Clientbound
@Override
public void registerPackets() {
registerTrackerWithData1_19(ClientboundPackets1_19_4.ADD_ENTITY, EntityTypes1_19_4.FALLING_BLOCK);
registerRemoveEntities(ClientboundPackets1_19_4.REMOVE_ENTITIES);
registerSetEntityData(ClientboundPackets1_19_4.SET_ENTITY_DATA, Types1_19_4.ENTITY_DATA_LIST, Types1_19_3.ENTITY_DATA_LIST);
protocol.registerClientbound(ClientboundPackets1_19_4.ADD_ENTITY, new PacketHandlers() {
@Override
public void register() {
map(Types.VAR_INT); // Entity id
map(Types.UUID); // Entity UUID
map(Types.VAR_INT); // Entity type
map(Types.DOUBLE); // X
map(Types.DOUBLE); // Y
map(Types.DOUBLE); // Z
map(Types.BYTE); // Pitch
map(Types.BYTE); // Yaw
map(Types.BYTE); // Head yaw
map(Types.VAR_INT); // Data
handler(wrapper -> {
final int entityId = wrapper.get(Types.VAR_INT, 0);
final int entityType = wrapper.get(Types.VAR_INT, 1);
// First track (and remap) entity, then put storage for block display entity
getSpawnTrackerWithDataHandler1_19(EntityTypes1_19_4.FALLING_BLOCK).handle(wrapper);
if (entityType != EntityTypes1_19_4.BLOCK_DISPLAY.getId()) {
return;
}
final StoredEntityData data = tracker(wrapper.user()).entityData(entityId);
if (data != null) {
final LinkedEntityStorage storage = new LinkedEntityStorage();
final double x = wrapper.get(Types.DOUBLE, 0);
final double y = wrapper.get(Types.DOUBLE, 1);
final double z = wrapper.get(Types.DOUBLE, 2);
storage.setPosition(x, y, z);
data.put(storage);
}
});
}
});
protocol.registerClientbound(ClientboundPackets1_19_4.LOGIN, new PacketHandlers() {
@Override
public void register() {
@@ -137,6 +179,40 @@ public final class EntityPacketRewriter1_19_4 extends EntityRewriter<Clientbound
final int duration = wrapper.read(Types.VAR_INT);
wrapper.write(Types.VAR_INT, duration == -1 ? 999999 : duration);
});
// Track the position of block display entities to later spawn the linked entities, we will put them
// as passengers but the spawn position needs to be in the players view distance
protocol.registerClientbound(ClientboundPackets1_19_4.TELEPORT_ENTITY, wrapper -> {
final int entityId = wrapper.passthrough(Types.VAR_INT);
final double x = wrapper.passthrough(Types.DOUBLE);
final double y = wrapper.passthrough(Types.DOUBLE);
final double z = wrapper.passthrough(Types.DOUBLE);
final EntityTracker1_19_4 tracker = tracker(wrapper.user());
final LinkedEntityStorage storage = tracker.linkedEntityStorage(entityId);
if (storage == null) {
return;
}
storage.setPosition(x, y, z);
});
final PacketHandler entityPositionHandler = wrapper -> {
final int entityId = wrapper.passthrough(Types.VAR_INT);
final double x = wrapper.passthrough(Types.SHORT) / EntityPositionHandler.RELATIVE_MOVE_FACTOR;
final double y = wrapper.passthrough(Types.SHORT) / EntityPositionHandler.RELATIVE_MOVE_FACTOR;
final double z = wrapper.passthrough(Types.SHORT) / EntityPositionHandler.RELATIVE_MOVE_FACTOR;
final EntityTracker1_19_4 tracker = tracker(wrapper.user());
final LinkedEntityStorage storage = tracker.linkedEntityStorage(entityId);
if (storage == null) {
return;
}
storage.addRelativePosition(x, y, z);
};
protocol.registerClientbound(ClientboundPackets1_19_4.MOVE_ENTITY_POS, entityPositionHandler);
protocol.registerClientbound(ClientboundPackets1_19_4.MOVE_ENTITY_POS_ROT, entityPositionHandler);
}
@Override
@@ -168,8 +244,35 @@ public final class EntityPacketRewriter1_19_4 extends EntityRewriter<Clientbound
data.setDataType(Types1_19_3.ENTITY_DATA_TYPES.optionalComponentType);
event.createExtraData(new EntityData(3, Types1_19_3.ENTITY_DATA_TYPES.booleanType, true)); // Show custom name
}));
filter().type(EntityTypes1_19_4.BLOCK_DISPLAY).index(22).handler((event, data) -> {
final int value = data.value();
final EntityTracker1_19_4 tracker = tracker(event.user());
tracker.clearLinkedEntities(event.entityId());
final LinkedEntityStorage storage = tracker.linkedEntityStorage(event.entityId());
if (storage == null) {
return;
}
final int linkedEntity = tracker.spawnEntity(EntityTypes1_19_3.FALLING_BLOCK, storage.x(), storage.y(), storage.z(), value);
storage.setEntities(linkedEntity);
final PacketWrapper wrapper = PacketWrapper.create(ClientboundPackets1_19_3.SET_PASSENGERS, event.user());
wrapper.write(Types.VAR_INT, event.entityId()); // Entity id
wrapper.write(Types.VAR_INT_ARRAY_PRIMITIVE, new int[] { linkedEntity }); // Passenger entity ids
wrapper.send(Protocol1_19_4To1_19_3.class);
});
filter().type(EntityTypes1_19_4.ITEM_DISPLAY).index(22).handler((event, data) -> {
final Item value = data.value();
final PacketWrapper setEquipment = PacketWrapper.create(ClientboundPackets1_19_3.SET_EQUIPMENT, event.user());
setEquipment.write(Types.VAR_INT, event.entityId()); // Entity id
setEquipment.write(Types.BYTE, (byte) 5); // Slot - head
setEquipment.write(Types.ITEM1_13_2, value);
setEquipment.send(Protocol1_19_4To1_19_3.class);
});
filter().type(EntityTypes1_19_4.DISPLAY).handler((event, data) -> {
// TODO Maybe spawn an extra entity to ride the armor stand for blocks and items
// Remove a large heap of display entity data
if (event.index() > 7) {
event.cancel();
@@ -0,0 +1,104 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2024 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.viabackwards.protocol.v1_19_4to1_19_3.storage;
import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.Protocol1_19_4To1_19_3;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.entity.TrackedEntity;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_3;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_4;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.data.entity.EntityTrackerBase;
import com.viaversion.viaversion.libs.fastutil.ints.IntOpenHashSet;
import com.viaversion.viaversion.libs.fastutil.ints.IntSet;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
public final class EntityTracker1_19_4 extends EntityTrackerBase {
private final IntSet generatedEntities = new IntOpenHashSet(); // Track entities spawned to prevent duplicated entity ids
public EntityTracker1_19_4(final UserConnection connection) {
super(connection, EntityTypes1_19_4.PLAYER);
}
public int spawnEntity(final EntityTypes1_19_3 entityType, final double x, final double y, final double z, final int data) {
final int entityId = nextEntityId();
final PacketWrapper addEntity = PacketWrapper.create(ClientboundPackets1_19_3.ADD_ENTITY, user());
addEntity.write(Types.VAR_INT, entityId); // Entity id
addEntity.write(Types.UUID, UUID.randomUUID()); // Entity UUID
addEntity.write(Types.VAR_INT, entityType.getId()); // Entity type
addEntity.write(Types.DOUBLE, x); // X
addEntity.write(Types.DOUBLE, y); // Y
addEntity.write(Types.DOUBLE, z); // Z
addEntity.write(Types.BYTE, (byte) 0); // Pitch
addEntity.write(Types.BYTE, (byte) 0); // Yaw
addEntity.write(Types.BYTE, (byte) 0); // Head yaw
addEntity.write(Types.VAR_INT, data); // Data
addEntity.write(Types.SHORT, (short) 0); // Velocity X
addEntity.write(Types.SHORT, (short) 0); // Velocity Y
addEntity.write(Types.SHORT, (short) 0); // Velocity Z
addEntity.send(Protocol1_19_4To1_19_3.class);
generatedEntities.add(entityId);
return entityId;
}
@Override
public void clearEntities() {
for (final int id : entities.keySet()) {
clearLinkedEntities(id);
}
super.clearEntities();
}
@Override
public void removeEntity(final int id) {
clearLinkedEntities(id);
super.removeEntity(id);
}
public void clearLinkedEntities(final int id) {
final LinkedEntityStorage storage = linkedEntityStorage(id);
if (storage != null && storage.entities() != null) {
storage.remove(user());
generatedEntities.remove(id);
}
}
public LinkedEntityStorage linkedEntityStorage(final int id) {
final TrackedEntity entity = entity(id);
if (entity != null && entity.hasData()) {
return entity.data().get(LinkedEntityStorage.class);
}
return null;
}
private int nextEntityId() {
final int entityId = -ThreadLocalRandom.current().nextInt(10_000);
if (generatedEntities.contains(entityId)) {
return nextEntityId();
}
return entityId;
}
}
@@ -0,0 +1,46 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2024 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.viabackwards.protocol.v1_19_4to1_19_3.storage;
import com.viaversion.viabackwards.api.entities.storage.EntityPositionStorage;
import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.Protocol1_19_4To1_19_3;
import com.viaversion.viaversion.api.connection.StorableObject;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3;
public class LinkedEntityStorage extends EntityPositionStorage implements StorableObject {
private int[] entities;
public int[] entities() {
return entities;
}
public void setEntities(final int... entities) {
this.entities = entities;
}
public void remove(final UserConnection connection) {
final PacketWrapper wrapper = PacketWrapper.create(ClientboundPackets1_19_3.REMOVE_ENTITIES, connection);
wrapper.write(Types.VAR_INT_ARRAY_PRIMITIVE, entities);
wrapper.send(Protocol1_19_4To1_19_3.class);
}
}
@@ -28,11 +28,11 @@ import com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter.BlockItemPack
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter.CommandRewriter1_19;
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter.EntityPacketRewriter1_19;
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.DimensionRegistryStorage;
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.EntityTracker1_19;
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.NonceStorage;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.RegistryType;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19;
import com.viaversion.viaversion.api.minecraft.signature.SignableCommandArgumentsProvider;
import com.viaversion.viaversion.api.minecraft.signature.model.DecoratableMessage;
import com.viaversion.viaversion.api.minecraft.signature.model.MessageMetadata;
@@ -40,7 +40,6 @@ import com.viaversion.viaversion.api.minecraft.signature.storage.ChatSession1_19
import com.viaversion.viaversion.api.protocol.packet.State;
import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.data.entity.EntityTrackerBase;
import com.viaversion.viaversion.libs.gson.JsonElement;
import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets;
import com.viaversion.viaversion.protocols.base.ServerboundLoginPackets;
@@ -364,7 +363,7 @@ public final class Protocol1_19To1_18_2 extends BackwardsProtocol<ClientboundPac
@Override
public void init(final UserConnection user) {
user.put(new DimensionRegistryStorage());
addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_19.PLAYER));
addEntityTracker(user, new EntityTracker1_19(user));
}
@Override
@@ -20,9 +20,11 @@ package com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.ListTag;
import com.viaversion.nbt.tag.NumberTag;
import com.viaversion.viabackwards.ViaBackwards;
import com.viaversion.viabackwards.api.rewriters.EntityRewriter;
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.Protocol1_19To1_18_2;
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.DimensionRegistryStorage;
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.EntityTracker1_19;
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.LastDeathPosition;
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.StoredPainting;
import com.viaversion.viaversion.api.data.ParticleMappings;
@@ -111,6 +113,50 @@ public final class EntityPacketRewriter1_19 extends EntityRewriter<ClientboundPa
handler(wrapper -> {
// Remove factor data
wrapper.read(Types.OPTIONAL_NAMED_COMPOUND_TAG);
if (!ViaBackwards.getConfig().mapDarknessEffect()) {
return;
}
final EntityTracker1_19 tracker = tracker(wrapper.user());
final int entityId = wrapper.get(Types.VAR_INT, 0);
final int effectId = wrapper.get(Types.VAR_INT, 1);
if (effectId == 33) { // Newly added darkness, rewrite to blindness
tracker.getAffectedByDarkness().add(entityId);
wrapper.set(Types.VAR_INT, 1, 15);
} else if (effectId == 15) { // Track actual blindness effect for removal later
tracker.getAffectedByBlindness().add(entityId);
}
});
}
});
protocol.registerClientbound(ClientboundPackets1_19.REMOVE_MOB_EFFECT, new PacketHandlers() {
@Override
protected void register() {
map(Types.VAR_INT); // Entity id
map(Types.VAR_INT); // Effect id
handler(wrapper -> {
if (!ViaBackwards.getConfig().mapDarknessEffect()) {
return;
}
final int entityId = wrapper.get(Types.VAR_INT, 0);
final int effectId = wrapper.get(Types.VAR_INT, 1);
final EntityTracker1_19 tracker = tracker(wrapper.user());
if (effectId == 33) { // Remove darkness and the fake blindness effect if the client doesn't have actual blindness
tracker.getAffectedByDarkness().rem(entityId);
if (!tracker.getAffectedByBlindness().contains(entityId)) {
wrapper.set(Types.VAR_INT, 1, 15);
}
} else if (effectId == 15) { // Remove blindness and cancel if the client has darkness (will be removed by darkness removal)
tracker.getAffectedByBlindness().rem(entityId);
if (tracker.getAffectedByDarkness().contains(entityId)) {
wrapper.cancel();
}
}
});
}
});
@@ -0,0 +1,49 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2024 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.viabackwards.protocol.v1_19to1_18_2.storage;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19;
import com.viaversion.viaversion.data.entity.EntityTrackerBase;
import com.viaversion.viaversion.libs.fastutil.ints.IntArrayList;
import com.viaversion.viaversion.libs.fastutil.ints.IntList;
public final class EntityTracker1_19 extends EntityTrackerBase {
private final IntList affectedByBlindness = new IntArrayList();
private final IntList affectedByDarkness = new IntArrayList();
public EntityTracker1_19(final UserConnection connection) {
super(connection, EntityTypes1_19.PLAYER);
}
@Override
public void removeEntity(final int id) {
super.removeEntity(id);
this.affectedByBlindness.rem(id);
this.affectedByDarkness.rem(id);
}
public IntList getAffectedByBlindness() {
return affectedByBlindness;
}
public IntList getAffectedByDarkness() {
return affectedByDarkness;
}
}
@@ -86,7 +86,6 @@ public final class Protocol1_21_2To1_21 extends BackwardsProtocol<ClientboundPac
new StatisticsRewriter<>(this).register(ClientboundPackets1_21_2.AWARD_STATS);
new AttributeRewriter<>(this).register1_21(ClientboundPackets1_21_2.UPDATE_ATTRIBUTES);
translatableRewriter.registerOpenScreen(ClientboundPackets1_21_2.OPEN_SCREEN);
translatableRewriter.registerComponentPacket(ClientboundPackets1_21_2.SET_ACTION_BAR_TEXT);
translatableRewriter.registerComponentPacket(ClientboundPackets1_21_2.SET_TITLE_TEXT);
translatableRewriter.registerComponentPacket(ClientboundPackets1_21_2.SET_SUBTITLE_TEXT);
@@ -17,14 +17,33 @@
*/
package com.viaversion.viabackwards.protocol.v1_21_2to1_21.rewriter;
import com.viaversion.nbt.tag.ByteTag;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.FloatTag;
import com.viaversion.nbt.tag.IntTag;
import com.viaversion.nbt.tag.ListTag;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viabackwards.api.rewriters.BackwardsStructuredItemRewriter;
import com.viaversion.viabackwards.protocol.v1_21_2to1_21.Protocol1_21_2To1_21;
import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.InventoryStateIdStorage;
import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.RecipeStorage;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.minecraft.Holder;
import com.viaversion.viaversion.api.minecraft.HolderSet;
import com.viaversion.viaversion.api.minecraft.Particle;
import com.viaversion.viaversion.api.minecraft.SoundEvent;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.minecraft.item.data.Consumable1_21_2;
import com.viaversion.viaversion.api.minecraft.item.data.DeathProtection;
import com.viaversion.viaversion.api.minecraft.item.data.Equippable;
import com.viaversion.viaversion.api.minecraft.item.data.FoodProperties1_20_5;
import com.viaversion.viaversion.api.minecraft.item.data.Instrument1_21_2;
import com.viaversion.viaversion.api.minecraft.item.data.PotionEffect;
import com.viaversion.viaversion.api.minecraft.item.data.PotionEffectData;
import com.viaversion.viaversion.api.minecraft.item.data.UseCooldown;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2;
@@ -38,6 +57,8 @@ import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacke
import com.viaversion.viaversion.rewriter.BlockRewriter;
import com.viaversion.viaversion.rewriter.SoundRewriter;
import com.viaversion.viaversion.util.Key;
import com.viaversion.viaversion.util.Limit;
import com.viaversion.viaversion.util.Unit;
import static com.viaversion.viaversion.protocols.v1_21to1_21_2.rewriter.BlockItemPacketRewriter1_21_2.downgradeItemData;
import static com.viaversion.viaversion.protocols.v1_21to1_21_2.rewriter.BlockItemPacketRewriter1_21_2.updateItemData;
@@ -82,8 +103,19 @@ public final class BlockItemPacketRewriter1_21_2 extends BackwardsStructuredItem
wrapper.write(Types.UNSIGNED_BYTE, (short) -1); // Player inventory
wrapper.write(Types.VAR_INT, wrapper.user().get(InventoryStateIdStorage.class).stateId()); // State id; re-use the last known one
wrapper.write(Types.SHORT, (short) -1); // Cursor
final Item item = wrapper.passthrough(Types1_21_2.ITEM);
handleItemToClient(wrapper.user(), item);
passthroughClientboundItem(wrapper);
});
protocol.registerClientbound(ClientboundPackets1_21_2.OPEN_SCREEN, wrapper -> {
wrapper.passthrough(Types.VAR_INT); // Id
final int containerType = wrapper.passthrough(Types.VAR_INT);
if (containerType == 21) {
// Track smithing table to remove new data
wrapper.user().get(InventoryStateIdStorage.class).setSmithingTableOpen(true);
}
protocol.getComponentRewriter().passthroughAndProcess(wrapper);
});
protocol.registerClientbound(ClientboundPackets1_21_2.CONTAINER_SET_CONTENT, wrapper -> {
@@ -108,18 +140,31 @@ public final class BlockItemPacketRewriter1_21_2 extends BackwardsStructuredItem
wrapper.passthrough(Types.SHORT); // Slot id
passthroughClientboundItem(wrapper);
});
protocol.registerClientbound(ClientboundPackets1_21_2.CONTAINER_SET_DATA, wrapper -> {
updateContainerId(wrapper);
if (wrapper.user().get(InventoryStateIdStorage.class).smithingTableOpen()) {
// Cancel new data for smithing table
wrapper.cancel();
}
});
protocol.registerClientbound(ClientboundPackets1_21_2.CONTAINER_CLOSE, wrapper -> {
updateContainerId(wrapper);
wrapper.user().get(InventoryStateIdStorage.class).setSmithingTableOpen(false);
});
protocol.registerClientbound(ClientboundPackets1_21_2.SET_HELD_SLOT, ClientboundPackets1_21.SET_CARRIED_ITEM);
protocol.registerClientbound(ClientboundPackets1_21_2.CONTAINER_CLOSE, this::updateContainerId);
protocol.registerClientbound(ClientboundPackets1_21_2.CONTAINER_SET_DATA, this::updateContainerId);
protocol.registerClientbound(ClientboundPackets1_21_2.HORSE_SCREEN_OPEN, this::updateContainerId);
protocol.registerServerbound(ServerboundPackets1_20_5.CONTAINER_CLOSE, this::updateContainerIdServerbound);
protocol.registerServerbound(ServerboundPackets1_20_5.CONTAINER_CLOSE, wrapper -> {
updateContainerIdServerbound(wrapper);
wrapper.user().get(InventoryStateIdStorage.class).setSmithingTableOpen(false);
});
protocol.registerServerbound(ServerboundPackets1_20_5.CONTAINER_CLICK, wrapper -> {
updateContainerIdServerbound(wrapper);
wrapper.passthrough(Types.VAR_INT); // State id
wrapper.passthrough(Types.SHORT); // Slot
wrapper.passthrough(Types.BYTE); // Button
wrapper.passthrough(Types.VAR_INT); // Mode
final int length = wrapper.passthrough(Types.VAR_INT);
final int length = Limit.max(wrapper.passthrough(Types.VAR_INT), 128);
for (int i = 0; i < length; i++) {
wrapper.passthrough(Types.SHORT); // Slot
passthroughServerboundItem(wrapper);
@@ -143,6 +188,7 @@ public final class BlockItemPacketRewriter1_21_2 extends BackwardsStructuredItem
wrapper.write(Types.VAR_INT, 0); // 0 state id
final int slot = wrapper.read(Types.VAR_INT);
wrapper.write(Types.SHORT, (short) slot);
passthroughClientboundItem(wrapper);
});
protocol.registerClientbound(ClientboundPackets1_21_2.EXPLODE, wrapper -> {
@@ -250,6 +296,7 @@ public final class BlockItemPacketRewriter1_21_2 extends BackwardsStructuredItem
@Override
public Item handleItemToClient(final UserConnection connection, final Item item) {
backupInconvertibleData(item);
super.handleItemToClient(connection, item);
downgradeItemData(item);
return item;
@@ -258,7 +305,276 @@ public final class BlockItemPacketRewriter1_21_2 extends BackwardsStructuredItem
@Override
public Item handleItemToServer(final UserConnection connection, final Item item) {
super.handleItemToServer(connection, item);
// Handle food properties item manually here - the only protocol that has it
// The other way around it's handled by the super handleItemToClient method
final StructuredDataContainer data = item.dataContainer();
final FoodProperties1_20_5 food = data.get(StructuredDataKey.FOOD1_21);
if (food != null && food.usingConvertsTo() != null) {
this.handleItemToServer(connection, food.usingConvertsTo());
}
updateItemData(item);
restoreInconvertibleData(item);
return item;
}
// Backup inconvertible data and later restore to prevent data loss for creative mode clients
private void backupInconvertibleData(final Item item) {
final StructuredDataContainer data = item.dataContainer();
data.setIdLookup(protocol, true);
final CompoundTag backupTag = new CompoundTag();
final Holder<Instrument1_21_2> instrument = data.get(StructuredDataKey.INSTRUMENT1_21_2);
if (instrument != null && instrument.isDirect()) {
backupTag.put("instrument_description", instrument.value().description());
}
final HolderSet repairable = data.get(StructuredDataKey.REPAIRABLE);
if (repairable != null) {
backupTag.put("repairable", holderSetToTag(repairable));
}
final Integer enchantable = data.get(StructuredDataKey.ENCHANTABLE);
if (enchantable != null) {
backupTag.putInt("enchantable", enchantable);
}
final UseCooldown useCooldown = data.get(StructuredDataKey.USE_COOLDOWN);
if (useCooldown != null) {
final CompoundTag tag = new CompoundTag();
tag.putFloat("seconds", useCooldown.seconds());
if (useCooldown.cooldownGroup() != null) {
tag.putString("cooldown_group", useCooldown.cooldownGroup());
}
backupTag.put("use_cooldown", tag);
}
final String itemModel = data.get(StructuredDataKey.ITEM_MODEL);
if (itemModel != null) {
backupTag.putString("item_model", itemModel);
}
final Equippable equippable = data.get(StructuredDataKey.EQUIPPABLE);
if (equippable != null) {
final CompoundTag tag = new CompoundTag();
tag.putInt("equipment_slot", equippable.equipmentSlot());
convertSoundEventHolder(tag, equippable.soundEvent());
final String model = equippable.model();
if (model != null) {
tag.putString("model", model);
}
final String cameraOverlay = equippable.cameraOverlay();
if (cameraOverlay != null) {
tag.putString("camera_overlay", cameraOverlay);
}
if (equippable.allowedEntities() != null) {
tag.put("allowed_entities", holderSetToTag(equippable.allowedEntities()));
}
tag.putBoolean("dispensable", equippable.dispensable());
tag.putBoolean("swappable", equippable.swappable());
tag.putBoolean("damage_on_hurt", equippable.damageOnHurt());
backupTag.put("equippable", tag);
}
final Unit glider = data.get(StructuredDataKey.GLIDER);
if (glider != null) {
backupTag.putBoolean("glider", true);
}
final String tooltipStyle = data.get(StructuredDataKey.TOOLTIP_STYLE);
if (tooltipStyle != null) {
backupTag.putString("tooltip_style", tooltipStyle);
}
final DeathProtection deathProtection = data.get(StructuredDataKey.DEATH_PROTECTION);
if (deathProtection != null) {
final ListTag<CompoundTag> tag = new ListTag<>(CompoundTag.class);
for (final Consumable1_21_2.ConsumeEffect<?> effect : deathProtection.deathEffects()) {
final CompoundTag effectTag = new CompoundTag();
convertConsumableEffect(effectTag, effect);
tag.add(effectTag);
}
backupTag.put("death_protection", tag);
}
if (!backupTag.isEmpty()) {
saveTag(createCustomTag(item), backupTag, "inconvertible_data");
}
}
private void convertConsumableEffect(final CompoundTag tag, Consumable1_21_2.ConsumeEffect<?> effect) {
tag.putInt("id", effect.id());
if (effect.type() == Consumable1_21_2.ApplyStatusEffects.TYPE && effect.value() instanceof Consumable1_21_2.ApplyStatusEffects value) {
tag.putString("type", "apply_effects");
final ListTag<CompoundTag> effects = new ListTag<>(CompoundTag.class);
for (final PotionEffect potionEffect : value.effects()) {
final CompoundTag effectTag = new CompoundTag();
effectTag.putInt("effect", potionEffect.effect());
convertPotionEffectData(effectTag, potionEffect.effectData());
effects.add(effectTag);
}
tag.put("effects", effects);
tag.putFloat("probability", value.probability());
} else if (effect.type() == Types.HOLDER_SET && effect.value() instanceof HolderSet set) {
tag.putString("type", "remove_effects");
tag.put("remove_effects", holderSetToTag(set));
} else if (effect.type() == Types.EMPTY) {
tag.putString("type", "clear_all_effects");
} else if (effect.type() == Types.FLOAT) {
tag.putString("type", "teleport_randomly");
tag.putFloat("probability", (Float) effect.value());
} else if (effect.type() == Types.SOUND_EVENT && effect.value() instanceof Holder sound) {
tag.putString("type", "play_sound");
convertSoundEventHolder(tag, sound);
}
}
private void convertPotionEffectData(final CompoundTag tag, final PotionEffectData data) {
tag.putInt("amplifier", data.amplifier());
tag.putInt("duration", data.duration());
tag.putBoolean("ambient", data.ambient());
tag.putBoolean("show_particles", data.showParticles());
tag.putBoolean("show_icon", data.showIcon());
if (data.hiddenEffect() != null) {
final CompoundTag hiddenEffect = new CompoundTag();
convertPotionEffectData(hiddenEffect, data.hiddenEffect());
tag.put("hidden_effect", hiddenEffect);
}
}
private void convertSoundEventHolder(final CompoundTag tag, final Holder<SoundEvent> holder) {
tag.put("sound_event", holderToTag(holder, (event, soundEventTag) -> {
soundEventTag.putString("identifier", event.identifier());
if (event.fixedRange() != null) {
soundEventTag.putFloat("fixed_range", event.fixedRange());
}
}));
}
private Consumable1_21_2.ConsumeEffect<?> convertConsumableEffect(final CompoundTag tag) {
final int id = tag.getInt("id");
final String type = tag.getString("type");
if ("apply_effects".equals(type)) {
final ListTag<CompoundTag> effects = tag.getListTag("effects", CompoundTag.class);
final PotionEffect[] potionEffects = new PotionEffect[effects.size()];
for (int i = 0; i < effects.size(); i++) {
final CompoundTag effectTag = effects.get(i);
final int effect = effectTag.getInt("effect");
final PotionEffectData data = convertPotionEffectData(effectTag.getCompoundTag("data"));
potionEffects[i] = new PotionEffect(effect, data);
}
final float probability = tag.getFloat("probability");
return new Consumable1_21_2.ConsumeEffect<>(id, Consumable1_21_2.ApplyStatusEffects.TYPE, new Consumable1_21_2.ApplyStatusEffects(potionEffects, probability));
} else if ("remove_effects".equals(type)) {
final HolderSet set = restoreHolderSet(tag, "remove_effects");
return new Consumable1_21_2.ConsumeEffect<>(id, Types.HOLDER_SET, set);
} else if ("clear_all_effects".equals(type)) {
return new Consumable1_21_2.ConsumeEffect<>(id, Types.EMPTY, Unit.INSTANCE);
} else if ("teleport_randomly".equals(type)) {
final float probability = tag.getFloat("probability");
return new Consumable1_21_2.ConsumeEffect<>(id, Types.FLOAT, probability);
} else if ("play_sound".equals(type)) {
final Holder<SoundEvent> sound = convertSoundEventHolder(tag);
return new Consumable1_21_2.ConsumeEffect<>(id, Types.SOUND_EVENT, sound);
}
return null;
}
private PotionEffectData convertPotionEffectData(final CompoundTag tag) {
final int amplifier = tag.getInt("amplifier");
final int duration = tag.getInt("duration");
final boolean ambient = tag.getBoolean("ambient");
final boolean showParticles = tag.getBoolean("show_particles");
final boolean showIcon = tag.getBoolean("show_icon");
final CompoundTag hiddenEffect = tag.getCompoundTag("hidden_effect");
return new PotionEffectData(amplifier, duration, ambient, showParticles, showIcon, hiddenEffect != null ? convertPotionEffectData(hiddenEffect) : null);
}
private Holder<SoundEvent> convertSoundEventHolder(final CompoundTag tag) {
return restoreHolder(tag, "sound_event", soundEventTag -> {
final String identifier = soundEventTag.getString("identifier");
final FloatTag fixedRange = soundEventTag.getFloatTag("fixed_range");
return new SoundEvent(identifier, fixedRange != null ? fixedRange.asFloat() : null);
});
}
private void restoreInconvertibleData(final Item item) {
final StructuredDataContainer data = item.dataContainer();
final CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA);
if (customData == null || !(customData.remove(nbtTagName("inconvertible_data")) instanceof CompoundTag backupTag)) {
return;
}
final Holder<Instrument1_21_2> instrument = data.get(StructuredDataKey.INSTRUMENT1_21_2);
if (instrument != null && instrument.isDirect()) {
final Tag description = backupTag.get("instrument_description");
if (description != null) {
final Instrument1_21_2 delegate = instrument.value();
data.set(StructuredDataKey.INSTRUMENT1_21_2, Holder.of(new Instrument1_21_2(delegate.soundEvent(), delegate.useDuration(), delegate.range(), description)));
}
}
if (backupTag.contains("repairable")) {
data.set(StructuredDataKey.REPAIRABLE, restoreHolderSet(backupTag, "repairable"));
}
final IntTag enchantable = backupTag.getIntTag("enchantable");
if (enchantable != null) {
data.set(StructuredDataKey.ENCHANTABLE, enchantable.asInt());
}
final CompoundTag useCooldown = backupTag.getCompoundTag("use_cooldown");
if (useCooldown != null) {
final float seconds = useCooldown.getFloat("seconds");
final String cooldownGroup = useCooldown.getString("cooldown_group");
data.set(StructuredDataKey.USE_COOLDOWN, new UseCooldown(seconds, cooldownGroup));
}
final String itemModel = backupTag.getString("item_model");
if (itemModel != null) {
data.set(StructuredDataKey.ITEM_MODEL, itemModel);
}
final CompoundTag equippable = backupTag.getCompoundTag("equippable");
if (equippable != null) {
final int equipmentSlot = equippable.getInt("equipment_slot");
final Holder<SoundEvent> soundEvent = convertSoundEventHolder(equippable);
final String model = equippable.getString("model");
final String cameraOverlay = equippable.getString("camera_overlay");
final HolderSet allowedEntities = equippable.contains("allowed_entities") ? restoreHolderSet(equippable, "allowed_entities") : null;
final boolean dispensable = equippable.getBoolean("dispensable");
final boolean swappable = equippable.getBoolean("swappable");
final boolean damageOnHurt = equippable.getBoolean("damage_on_hurt");
data.set(StructuredDataKey.EQUIPPABLE, new Equippable(equipmentSlot, soundEvent, model, cameraOverlay, allowedEntities, dispensable, swappable, damageOnHurt));
}
final ByteTag glider = backupTag.getByteTag("glider");
if (glider != null) {
data.set(StructuredDataKey.GLIDER, Unit.INSTANCE);
}
final String tooltipStyle = backupTag.getString("tooltip_style");
if (tooltipStyle != null) {
data.set(StructuredDataKey.TOOLTIP_STYLE, tooltipStyle);
}
final ListTag<CompoundTag> deathProtection = backupTag.getListTag("death_protection", CompoundTag.class);
if (deathProtection != null) {
final Consumable1_21_2.ConsumeEffect<?>[] effects = new Consumable1_21_2.ConsumeEffect[deathProtection.size()];
for (int i = 0; i < deathProtection.size(); i++) {
effects[i] = convertConsumableEffect(deathProtection.get(i));
}
data.set(StructuredDataKey.DEATH_PROTECTION, new DeathProtection(effects));
}
removeCustomTag(data, customData);
}
}
@@ -17,6 +17,10 @@
*/
package com.viaversion.viabackwards.protocol.v1_21_2to1_21.rewriter;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.FloatTag;
import com.viaversion.nbt.tag.IntTag;
import com.viaversion.nbt.tag.ListTag;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viabackwards.ViaBackwards;
import com.viaversion.viabackwards.api.rewriters.EntityRewriter;
@@ -46,6 +50,7 @@ import com.viaversion.viaversion.util.Key;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class EntityPacketRewriter1_21_2 extends EntityRewriter<ClientboundPacket1_21_2, Protocol1_21_2To1_21> {
@@ -102,12 +107,30 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
registryDataRewriter.addEnchantmentEffectRewriter("change_item_damage", tag -> tag.putString("type", "damage_item"));
protocol.registerClientbound(ClientboundConfigurationPackets1_21.REGISTRY_DATA, wrapper -> {
final String registryKey = Key.stripMinecraftNamespace(wrapper.passthrough(Types.STRING));
final RegistryEntry[] entries = wrapper.read(Types.REGISTRY_ENTRY_ARRAY);
if (registryKey.equals("instrument")) {
wrapper.cancel();
return;
}
final RegistryEntry[] entries = wrapper.read(Types.REGISTRY_ENTRY_ARRAY);
if (registryKey.equals("worldgen/biome")) {
for (final RegistryEntry entry : entries) {
if (entry.tag() == null) {
continue;
}
final CompoundTag effects = ((CompoundTag) entry.tag()).getCompoundTag("effects");
final CompoundTag particle = effects.getCompoundTag("particle");
if (particle == null) {
continue;
}
final CompoundTag particleOptions = particle.getCompoundTag("options");
final String particleType = particleOptions.getString("type");
updateParticleFormat(particleOptions, Key.stripMinecraftNamespace(particleType));
}
}
wrapper.write(Types.REGISTRY_ENTRY_ARRAY, registryDataRewriter.handle(wrapper.user(), registryKey, entries));
});
@@ -196,7 +219,7 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
});
protocol.registerClientbound(ClientboundPackets1_21_2.TELEPORT_ENTITY, wrapper -> {
wrapper.passthrough(Types.VAR_INT); // Entity ID
final int entityId = wrapper.passthrough(Types.VAR_INT);
final double x = wrapper.passthrough(Types.DOUBLE);
final double y = wrapper.passthrough(Types.DOUBLE);
final double z = wrapper.passthrough(Types.DOUBLE);
@@ -215,7 +238,7 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
wrapper.send(Protocol1_21_2To1_21.class);
wrapper.cancel();
handleRelativeArguments(wrapper, x, y, z, yaw, pitch, relativeArguments, movementX, movementY, movementZ);
handleRelativeArguments(wrapper, x, y, z, yaw, pitch, relativeArguments, movementX, movementY, movementZ, entityId);
});
protocol.registerClientbound(ClientboundPackets1_21_2.PLAYER_POSITION, wrapper -> {
@@ -241,7 +264,7 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
wrapper.send(Protocol1_21_2To1_21.class);
wrapper.cancel();
handleRelativeArguments(wrapper, x, y, z, yaw, pitch, relativeArguments, movementX, movementY, movementZ);
handleRelativeArguments(wrapper, x, y, z, yaw, pitch, relativeArguments, movementX, movementY, movementZ, null);
});
protocol.registerServerbound(ServerboundPackets1_20_5.PLAYER_COMMAND, wrapper -> {
@@ -390,7 +413,6 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
// New one
if (actions.get(6)) {
actions.clear(6);
wrapper.read(Types.VAR_INT); // List order
}
}
@@ -427,6 +449,29 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
});
}
private void updateParticleFormat(final CompoundTag options, final String particleType) {
// Have to be float lists in older versions
if ("dust_color_transition".equals(particleType)) {
replaceColor(options, "from_color");
replaceColor(options, "to_color");
} else if ("dust".equals(particleType)) {
replaceColor(options, "color");
}
}
private void replaceColor(final CompoundTag options, final String to_color) {
final IntTag toColorTag = options.getIntTag(to_color);
if (toColorTag == null) {
return;
}
final int rgb = toColorTag.asInt();
final float r = ((rgb >> 16) & 0xFF) / 255F;
final float g = ((rgb >> 8) & 0xFF) / 255F;
final float b = (rgb & 0xFF) / 255F;
options.put(to_color, new ListTag<>(List.of(new FloatTag(r), new FloatTag(g), new FloatTag(b))));
}
private void writePackedRotation(final PacketWrapper wrapper, final float yaw, final float pitch) {
// Pack y and x rot
wrapper.write(Types.BYTE, (byte) Math.floor(yaw * 256F / 360F));
@@ -438,7 +483,8 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
double x, double y, double z,
float yaw, float pitch,
final int relativeArguments,
double movementX, double movementY, double movementZ
double movementX, double movementY, double movementZ,
@Nullable final Integer entityId
) {
// Position and rotation
final PlayerStorage storage = wrapper.user().get(PlayerStorage.class);
@@ -477,8 +523,17 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
final boolean relativeDeltaY = (relativeArguments & 1 << REL_DELTA_Y) != 0;
final boolean relativeDeltaZ = (relativeArguments & 1 << REL_DELTA_Z) != 0;
// Update after having used its previous data
storage.setPosition(x, y, z);
storage.setRotation(yaw, pitch);
// Movement
if (relativeDeltaX && relativeDeltaY && relativeDeltaZ) {
if (entityId != null && entityId != tracker(wrapper.user()).clientEntityId()) {
// Can only handle the client entity
return;
}
final PacketWrapper explosionPacket = wrapper.create(ClientboundPackets1_21.EXPLODE);
explosionPacket.write(Types.DOUBLE, 0.0); // Center X
explosionPacket.write(Types.DOUBLE, 0.0); // Center Y
@@ -496,7 +551,7 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
explosionPacket.send(Protocol1_21_2To1_21.class);
} else if (!relativeDeltaX && !relativeDeltaY && !relativeDeltaZ) {
final PacketWrapper entityMotionPacket = wrapper.create(ClientboundPackets1_21.SET_ENTITY_MOTION);
entityMotionPacket.write(Types.VAR_INT, tracker(wrapper.user()).clientEntityId());
entityMotionPacket.write(Types.VAR_INT, entityId != null ? entityId : tracker(wrapper.user()).clientEntityId());
entityMotionPacket.write(Types.SHORT, (short) (movementX * 8000));
entityMotionPacket.write(Types.SHORT, (short) (movementY * 8000));
entityMotionPacket.write(Types.SHORT, (short) (movementZ * 8000));
@@ -511,10 +566,6 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
"This will result in incorrect movement for the player. ");
warned = true;
}
// Update at the end
storage.setPosition(x, y, z);
storage.setRotation(yaw, pitch);
}
private int boatTypeFromEntityType(final EntityType type) {
@@ -21,6 +21,7 @@ import com.viaversion.viaversion.api.connection.StorableObject;
public final class InventoryStateIdStorage implements StorableObject {
private boolean smithingTableOpen;
private int stateId = -1;
public int stateId() {
@@ -30,4 +31,12 @@ public final class InventoryStateIdStorage implements StorableObject {
public void setStateId(final int stateId) {
this.stateId = stateId;
}
public boolean smithingTableOpen() {
return smithingTableOpen;
}
public void setSmithingTableOpen(final boolean smithingTableOpen) {
this.smithingTableOpen = smithingTableOpen;
}
}
@@ -40,8 +40,8 @@ public final class RecipeStorage implements StorableObject {
// Pairs of open + filtering for: Crafting, furnace, blast furnace, smoker
public static final int RECIPE_BOOK_SETTINGS = 4 * 2;
private static final String[] EMPTY_STRINGS = new String[0];
private final List<Recipe> recipes = new ArrayList<>();
private final List<Recipe> tempRecipes = new ArrayList<>();
private final List<Recipe> recipes = new ArrayList<>(); // Recipes, excluding stone cutter recipes, in the order they were received
private final List<Recipe> tempRecipes = new ArrayList<>(); // Recipes received in the current batch
private final List<StoneCutterRecipe> stoneCutterRecipes = new ArrayList<>();
private boolean[] recipeBookSettings = new boolean[RECIPE_BOOK_SETTINGS];
private final Protocol1_21_2To1_21 protocol;
@@ -58,7 +58,6 @@ public final class RecipeStorage implements StorableObject {
private Integer group;
private int category;
private boolean highlight;
private boolean locked;
abstract void write(PacketWrapper wrapper);
@@ -142,7 +141,7 @@ public final class RecipeStorage implements StorableObject {
public void lockRecipes(final PacketWrapper wrapper, final int[] ids) {
for (final int id : ids) {
recipes.get(id).locked = true;
recipes.removeIf(recipe -> recipe.index == id);
}
wrapper.write(Types.VAR_INT, 2); // Remove recipes
@@ -29,10 +29,13 @@ import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.EnchantmentsPa
import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.OpenScreenStorage;
import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.PlayerRotationStorage;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.Holder;
import com.viaversion.viaversion.api.minecraft.SoundEvent;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.minecraft.item.data.Enchantments;
import com.viaversion.viaversion.api.minecraft.item.data.JukeboxPlayable;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2;
import com.viaversion.viaversion.api.type.types.version.Types1_20_5;
@@ -49,6 +52,7 @@ import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPacke
import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21;
import com.viaversion.viaversion.rewriter.BlockRewriter;
import com.viaversion.viaversion.rewriter.IdRewriteFunction;
import com.viaversion.viaversion.util.Either;
import com.viaversion.viaversion.util.SerializerVersion;
import java.util.ArrayList;
import java.util.List;
@@ -187,6 +191,7 @@ public final class BlockItemPacketRewriter1_21 extends BackwardsStructuredItemRe
final int identifier = item.identifier();
// Order is important
backupInconvertibleData(item);
super.handleItemToClient(connection, item);
downgradeItemData(item);
@@ -222,6 +227,7 @@ public final class BlockItemPacketRewriter1_21 extends BackwardsStructuredItemRe
// Order is important
super.handleItemToServer(connection, item);
updateItemData(item);
restoreInconvertibleData(item);
final CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA);
if (customData == null) {
@@ -264,6 +270,67 @@ public final class BlockItemPacketRewriter1_21 extends BackwardsStructuredItemRe
}
}
private void backupInconvertibleData(final Item item) {
final StructuredDataContainer data = item.dataContainer();
data.setIdLookup(protocol, true);
final JukeboxPlayable jukeboxPlayable = data.get(StructuredDataKey.JUKEBOX_PLAYABLE);
if (jukeboxPlayable == null) {
return;
}
final CompoundTag tag = new CompoundTag();
if (jukeboxPlayable.song().isLeft()) {
final Holder<JukeboxPlayable.JukeboxSong> songHolder = jukeboxPlayable.song().left();
tag.put("song", holderToTag(songHolder, (song, songTag) -> {
songTag.put("sound_event", holderToTag(song.soundEvent(), (soundEvent, soundEventTag) -> {
soundEventTag.putString("identifier", soundEvent.identifier());
if (soundEvent.fixedRange() != null) {
soundEventTag.putFloat("fixed_range", soundEvent.fixedRange());
}
}));
songTag.put("description", song.description());
songTag.putFloat("length_in_seconds", song.lengthInSeconds());
songTag.putInt("comparator_output", song.comparatorOutput());
}));
} else {
tag.putString("song_identifier", jukeboxPlayable.song().right());
}
tag.putBoolean("show_in_tooltip", jukeboxPlayable.showInTooltip());
saveTag(createCustomTag(item), tag, "jukebox_playable");
}
private void restoreInconvertibleData(final Item item) {
final StructuredDataContainer data = item.dataContainer();
final CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA);
if (customData == null || !(customData.remove(nbtTagName("jukebox_playable")) instanceof CompoundTag tag)) {
return;
}
final Either<Holder<JukeboxPlayable.JukeboxSong>, String> song;
final String songIdentifier = tag.getString("song_identifier");
if (songIdentifier != null) {
song = Either.right(tag.getString(songIdentifier));
} else {
song = Either.left(restoreHolder(tag, "song", songTag -> {
final Holder<SoundEvent> soundEvent = restoreHolder(songTag, "sound_event", soundTag -> {
final String identifier = soundTag.getString("identifier");
final Float fixedRange = soundTag.contains("fixed_range") ? soundTag.getFloat("fixed_range") : null;
return new SoundEvent(identifier, fixedRange);
});
final Tag description = songTag.get("description");
final float lengthInSeconds = songTag.getFloat("length_in_seconds");
final int comparatorOutput = songTag.getInt("comparator_output");
return new JukeboxPlayable.JukeboxSong(soundEvent, description, lengthInSeconds, comparatorOutput);
}));
}
final JukeboxPlayable jukeboxPlayable = new JukeboxPlayable(song, tag.getBoolean("show_in_tooltip"));
data.set(StructuredDataKey.JUKEBOX_PLAYABLE, jukeboxPlayable);
removeCustomTag(data, customData);
}
private record PendingIdChange(int id, int mappedId, int level) {
}
}
@@ -28,5 +28,8 @@ bedrock-at-y-0: false
# If disabled, the client will see them as end portal frames.
sculk-shriekers-to-crying-obsidian: true
#
# Maps the darkness effect to blindness for 1.18.2 clients on 1.19+ servers.
map-darkness-effect: true
#
# Suppresses warnings of missing emulations for certain features that are not supported (e.g. world height in 1.17+).
suppress-emulation-warnings: false
suppress-emulation-warnings: false
+1 -1
View File
@@ -1,4 +1,4 @@
projectVersion=5.1.1
projectVersion=5.1.2-SNAPSHOT
# Smile emoji
mcVersions=1.21.3,1.21.2,1.21.1,1.21,1.20.6,1.20.5,1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2, 1.18.1, 1.18, 1.17.1, 1.17, 1.16.5, 1.16.4, 1.16.3, 1.16.2, 1.16.1, 1.16, 1.15.2, 1.15.1, 1.15, 1.14.4, 1.14.3, 1.14.2, 1.14.1, 1.14, 1.13.2, 1.13.1, 1.13, 1.12.2, 1.12.1, 1.12, 1.11.2, 1.11.1, 1.11, 1.10.2, 1.10.1, 1.10
+2 -2
View File
@@ -3,7 +3,7 @@ metadata.format.version = "1.1"
[versions]
# ViaVersion
viaver = "5.1.1-SNAPSHOT"
viaver = "5.1.2-SNAPSHOT"
# Common provided
netty = "4.0.20.Final"
@@ -16,7 +16,7 @@ checkerQual = "3.39.0"
paper = "1.16.5-R0.1-SNAPSHOT"
velocity = "3.1.1"
fabricLoader = "0.11.6"
viaProxy = "3.3.5-SNAPSHOT"
viaProxy = "3.3.5"
[libraries]
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME