Compare commits

...
16 Commits
Author SHA1 Message Date
Nassim Jahnke b90b573f1d 5.4.1 Release 2025-06-30 16:04:55 +02:00
Nassim Jahnke 74aeadd688 1.21.7-rc1/2 2025-06-30 16:04:53 +02:00
riku6460 6f27988cf6 Ensure run_command click events start with '/' in 1.21.5->1.21.4 (#1051) 2025-06-30 16:03:42 +02:00
Nassim Jahnke 81aad86d9f Map waterlogged light to water
Fixes #1038
2025-06-30 12:32:05 +02:00
FlorianMichael 1e7dbf38fd [ci skip] Add SECURITY.md (#1031) 2025-06-29 15:37:05 +02:00
riku6460andFlorianMichael 0404184e15 Use correct tag type to write custom click actions in 1.21.6->.5 (#1050)
Co-authored-by: FlorianMichael <florian.michael07@gmail.com>
2025-06-29 09:45:13 +02:00
FlorianMichael 64a7d9f8bd Strip default namespace when reading types (#1049) 2025-06-28 13:06:10 +02:00
FlorianMichael 924346882c Fix style and remap translations for item description in 1.21.6->.5 dialogs (#1048) 2025-06-28 12:34:32 +02:00
FlorianMichael 1d7332101f Fix run_command and custom click events (#1046) 2025-06-25 09:08:45 +02:00
FlorianMichael 30e63ebc3b Improve multiple text widgets follow one another in dialogs (#1044) 2025-06-24 13:36:19 +02:00
FlorianMichael 217c9459f5 Emulate dialog screens using chest inventories (#1041) 2025-06-23 22:53:59 +02:00
Nassim Jahnke ead2c6f330 Send sneaking command on vehicle exit in 1.21.2->1.21.1
Fixes #1001
2025-06-23 12:26:04 +02:00
Nassim Jahnke 1a972da613 Fix horse saddle handling in 1.21.5->1.21.4 2025-06-23 11:10:47 +02:00
Nassim Jahnke 44dbbf37e2 Remove saddle slot group from enchantments in 1.21.5->1.21.4
Fixes #1033
2025-06-22 22:14:47 +02:00
RK_01 def010c2bf Remap difficulty data type (#1040) 2025-06-21 10:08:55 +02:00
Nassim Jahnke 75744d5525 Bump version to 5.4.1-SNAPSHOT 2025-06-21 09:46:31 +02:00
38 changed files with 2908 additions and 59 deletions

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
# Security Policy
## Reporting a Vulnerability
If you discover a **security vulnerability**, please **do not** open a public issue. Instead, use one of the following **confidential contact methods**:
* **Email:** [security@viaversion.com](mailto:security@viaversion.com)
* **Discord:** Join our [Discord server](https://discord.gg/viaversion) and visit the [#exploit-report](https://discord.com/channels/316206679014244363/1388847764137316485) channel for further instructions.
If your issue is **not security-related** (e.g., bug reports, feature requests, or usage questions), please use the [GitHub Issues page](https://github.com/ViaVersion/ViaBackwards/issues) or contact us via the [Discord server](https://discord.gg/viaversion).
@@ -21,48 +21,50 @@ package com.viaversion.viabackwards.api;
import com.viaversion.viabackwards.ViaBackwards;
import com.viaversion.viabackwards.ViaBackwardsConfig;
import com.viaversion.viabackwards.api.data.TranslatableMappings;
import com.viaversion.viabackwards.protocol.v1_11to1_10.Protocol1_11To1_10;
import com.viaversion.viabackwards.protocol.v1_12to1_11_1.Protocol1_12To1_11_1;
import com.viaversion.viabackwards.protocol.v1_10to1_9_3.Protocol1_10To1_9_3;
import com.viaversion.viabackwards.protocol.v1_11_1to1_11.Protocol1_11_1To1_11;
import com.viaversion.viabackwards.protocol.v1_12_2to1_12_1.Protocol1_12_2To1_12_1;
import com.viaversion.viabackwards.protocol.v1_13to1_12_2.Protocol1_13To1_12_2;
import com.viaversion.viabackwards.protocol.v1_11to1_10.Protocol1_11To1_10;
import com.viaversion.viabackwards.protocol.v1_12_1to1_12.Protocol1_12_1To1_12;
import com.viaversion.viabackwards.protocol.v1_13_2to1_13_1.Protocol1_13_2To1_13_1;
import com.viaversion.viabackwards.protocol.v1_14to1_13_2.Protocol1_14To1_13_2;
import com.viaversion.viabackwards.protocol.v1_12_2to1_12_1.Protocol1_12_2To1_12_1;
import com.viaversion.viabackwards.protocol.v1_12to1_11_1.Protocol1_12To1_11_1;
import com.viaversion.viabackwards.protocol.v1_13_1to1_13.Protocol1_13_1To1_13;
import com.viaversion.viabackwards.protocol.v1_13_2to1_13_1.Protocol1_13_2To1_13_1;
import com.viaversion.viabackwards.protocol.v1_13to1_12_2.Protocol1_13To1_12_2;
import com.viaversion.viabackwards.protocol.v1_14_1to1_14.Protocol1_14_1To1_14;
import com.viaversion.viabackwards.protocol.v1_14_2to1_14_1.Protocol1_14_2To1_14_1;
import com.viaversion.viabackwards.protocol.v1_14_3to1_14_2.Protocol1_14_3To1_14_2;
import com.viaversion.viabackwards.protocol.v1_14_4to1_14_3.Protocol1_14_4To1_14_3;
import com.viaversion.viabackwards.protocol.v1_15to1_14_4.Protocol1_15To1_14_4;
import com.viaversion.viabackwards.protocol.v1_14_1to1_14.Protocol1_14_1To1_14;
import com.viaversion.viabackwards.protocol.v1_15_2to1_15_1.Protocol1_15_2To1_15_1;
import com.viaversion.viabackwards.protocol.v1_16to1_15_2.Protocol1_16To1_15_2;
import com.viaversion.viabackwards.protocol.v1_14to1_13_2.Protocol1_14To1_13_2;
import com.viaversion.viabackwards.protocol.v1_15_1to1_15.Protocol1_15_1To1_15;
import com.viaversion.viabackwards.protocol.v1_15_2to1_15_1.Protocol1_15_2To1_15_1;
import com.viaversion.viabackwards.protocol.v1_15to1_14_4.Protocol1_15To1_14_4;
import com.viaversion.viabackwards.protocol.v1_16_1to1_16.Protocol1_16_1To1_16;
import com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.Protocol1_16_2To1_16_1;
import com.viaversion.viabackwards.protocol.v1_16_3to1_16_2.Protocol1_16_3To1_16_2;
import com.viaversion.viabackwards.protocol.v1_16_4to1_16_3.Protocol1_16_4To1_16_3;
import com.viaversion.viabackwards.protocol.v1_17to1_16_4.Protocol1_17To1_16_4;
import com.viaversion.viabackwards.protocol.v1_16_1to1_16.Protocol1_16_1To1_16;
import com.viaversion.viabackwards.protocol.v1_18to1_17_1.Protocol1_18To1_17_1;
import com.viaversion.viabackwards.protocol.v1_16to1_15_2.Protocol1_16To1_15_2;
import com.viaversion.viabackwards.protocol.v1_17_1to1_17.Protocol1_17_1To1_17;
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.Protocol1_19To1_18_2;
import com.viaversion.viabackwards.protocol.v1_17to1_16_4.Protocol1_17To1_16_4;
import com.viaversion.viabackwards.protocol.v1_18_2to1_18.Protocol1_18_2To1_18;
import com.viaversion.viabackwards.protocol.v1_18to1_17_1.Protocol1_18To1_17_1;
import com.viaversion.viabackwards.protocol.v1_19_1to1_19.Protocol1_19_1To1_19;
import com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.Protocol1_19_3To1_19_1;
import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.Protocol1_19_4To1_19_3;
import com.viaversion.viabackwards.protocol.v1_21_4to1_21_2.Protocol1_21_4To1_21_2;
import com.viaversion.viabackwards.protocol.v1_20to1_19_4.Protocol1_20To1_19_4;
import com.viaversion.viabackwards.protocol.v1_19_1to1_19.Protocol1_19_1To1_19;
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.Protocol1_19To1_18_2;
import com.viaversion.viabackwards.protocol.v1_20_2to1_20.Protocol1_20_2To1_20;
import com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.Protocol1_20_3To1_20_2;
import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.Protocol1_20_5To1_20_3;
import com.viaversion.viabackwards.protocol.v1_20_2to1_20.Protocol1_20_2To1_20;
import com.viaversion.viabackwards.protocol.v1_20to1_19_4.Protocol1_20To1_19_4;
import com.viaversion.viabackwards.protocol.v1_21_2to1_21.Protocol1_21_2To1_21;
import com.viaversion.viabackwards.protocol.v1_21_2to1_21.task.PlayerPacketsTickTask;
import com.viaversion.viabackwards.protocol.v1_21_4to1_21_2.Protocol1_21_4To1_21_2;
import com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.Protocol1_21_5To1_21_4;
import com.viaversion.viabackwards.protocol.v1_21to1_20_5.Protocol1_21To1_20_5;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.Protocol1_21_6To1_21_5;
import com.viaversion.viabackwards.protocol.v1_9_3to1_9_1.Protocol1_9_3To1_9_1;
import com.viaversion.viabackwards.protocol.v1_10to1_9_3.Protocol1_10To1_9_3;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.task.ChestDialogViewTask;
import com.viaversion.viabackwards.protocol.v1_21_7to1_21_6.Protocol1_21_7To1_21_6;
import com.viaversion.viabackwards.protocol.v1_21to1_20_5.Protocol1_21To1_20_5;
import com.viaversion.viabackwards.protocol.v1_9_1to1_9.Protocol1_9_1To1_9;
import com.viaversion.viabackwards.protocol.v1_9_3to1_9_1.Protocol1_9_3To1_9_1;
import com.viaversion.viabackwards.utils.VersionInfo;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.protocol.ProtocolManager;
@@ -151,6 +153,7 @@ public interface ViaBackwardsPlatform {
protocolManager.registerProtocol(new Protocol1_21_5To1_21_4(), ProtocolVersion.v1_21_4, ProtocolVersion.v1_21_5);
protocolManager.registerProtocol(new Protocol1_21_6To1_21_5(), ProtocolVersion.v1_21_5, ProtocolVersion.v1_21_6);
protocolManager.registerProtocol(new Protocol1_21_7To1_21_6(), ProtocolVersion.v1_21_6, ProtocolVersion.v1_21_7);
}
default void enable() {
@@ -158,6 +161,10 @@ public interface ViaBackwardsPlatform {
if (protocolVersion.newerThanOrEqualTo(ProtocolVersion.v1_21_2)) {
Via.getPlatform().runRepeatingSync(new PlayerPacketsTickTask(), 1L);
}
if (protocolVersion.newerThanOrEqualTo(ProtocolVersion.v1_21_6)) {
Via.getPlatform().runRepeatingSync(new ChestDialogViewTask(), 20L);
}
}
/**
@@ -190,7 +190,7 @@ public final class EntityPacketRewriter1_19 extends EntityRewriter<ClientboundPa
}
}
if (!found) {
throw new IllegalStateException("Could not find dimension " + dimensionKey + " in dimension registry");
throw new IllegalArgumentException("Could not find dimension " + dimensionKey + " in dimension registry");
}
// Add biome category and track biomes
@@ -316,14 +316,7 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
// Player input no longer sets the sneaking state on the server
// Send the change separately if needed (= when in a vehicle and player commands aren't sent by the old client)
final PlayerStorage sneakingStorage = wrapper.user().get(PlayerStorage.class);
if (sneakingStorage.setSneaking(sneaking)) {
final PacketWrapper playerCommandPacket = wrapper.create(ServerboundPackets1_21_2.PLAYER_COMMAND);
playerCommandPacket.write(Types.VAR_INT, tracker(wrapper.user()).clientEntityId());
playerCommandPacket.write(Types.VAR_INT, sneaking ? 0 : 1); // Start/stop sneaking
playerCommandPacket.write(Types.VAR_INT, 0); // Data
playerCommandPacket.sendToServer(Protocol1_21_2To1_21.class);
}
sendSneakingPlayerCommand(wrapper, sneaking);
});
protocol.registerServerbound(ServerboundPackets1_20_5.MOVE_PLAYER_POS, wrapper -> {
@@ -416,6 +409,7 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
final ClientVehicleStorage storage = wrapper.user().get(ClientVehicleStorage.class);
if (storage != null && vehicleId == storage.vehicleId()) {
wrapper.user().remove(ClientVehicleStorage.class);
sendSneakingPlayerCommand(wrapper, false);
}
final int clientEntityId = tracker(wrapper.user()).clientEntityId();
@@ -436,6 +430,7 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
for (final int entityId : entityIds) {
if (entityId == vehicleStorage.vehicleId()) {
wrapper.user().remove(ClientVehicleStorage.class);
sendSneakingPlayerCommand(wrapper, false);
break;
}
}
@@ -561,6 +556,17 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
}
}
private void sendSneakingPlayerCommand(final PacketWrapper wrapper, final boolean sneaking) {
final PlayerStorage sneakingStorage = wrapper.user().get(PlayerStorage.class);
if (sneakingStorage.setSneaking(sneaking)) {
final PacketWrapper playerCommandPacket = wrapper.create(ServerboundPackets1_21_2.PLAYER_COMMAND);
playerCommandPacket.write(Types.VAR_INT, tracker(wrapper.user()).clientEntityId());
playerCommandPacket.write(Types.VAR_INT, sneaking ? 0 : 1); // Start/stop sneaking
playerCommandPacket.write(Types.VAR_INT, 0); // Data
playerCommandPacket.sendToServer(Protocol1_21_2To1_21.class);
}
}
private int boatTypeFromEntityType(final EntityType type) {
if (type == EntityTypes1_21_2.OAK_BOAT) {
return 0;
@@ -91,7 +91,7 @@ public final class BlockItemPacketRewriter1_21_5 extends BackwardsStructuredItem
private static final int SIGN_BOCK_ENTITY_ID = 7;
private static final int HANGING_SIGN_BOCK_ENTITY_ID = 8;
private static final int SADDLE_EQUIPMENT_SLOT = 7;
private static final byte SADDLED_FLAG = 4;
static final byte SADDLED_FLAG = 4;
public BlockItemPacketRewriter1_21_5(final Protocol1_21_5To1_21_4 protocol) {
super(protocol);
@@ -187,7 +187,8 @@ public final class BlockItemPacketRewriter1_21_5 extends BackwardsStructuredItem
final int equipmentSlot = value & 0x7F;
if (equipmentSlot == SADDLE_EQUIPMENT_SLOT) {
if (trackedEntity != null && trackedEntity.entityType().isOrHasParent(EntityTypes1_21_5.ABSTRACT_HORSE)) {
sendSaddledEntityData(wrapper.user(), trackedEntity, entityId);
final Item item = wrapper.read(itemType());
sendSaddledEntityData(wrapper.user(), trackedEntity, entityId, item.identifier() == 800);
}
wrapper.cancel();
return;
@@ -268,16 +269,24 @@ public final class BlockItemPacketRewriter1_21_5 extends BackwardsStructuredItem
wrapper.write(Types.STRING, namespace + ":textures/" + path + ".png");
}
private void sendSaddledEntityData(final UserConnection connection, final TrackedEntity trackedEntity, final int entityId) {
private void sendSaddledEntityData(final UserConnection connection, final TrackedEntity trackedEntity, final int entityId, final boolean saddled) {
byte data = 0;
if (trackedEntity.hasData()) {
final HorseDataStorage horseDataStorage = trackedEntity.data().get(HorseDataStorage.class);
if (horseDataStorage != null) {
if (horseDataStorage.saddled() == saddled) {
return;
}
data = horseDataStorage.data();
}
}
trackedEntity.data().put(new HorseDataStorage(data, true));
data = (byte) (data | SADDLED_FLAG);
trackedEntity.data().put(new HorseDataStorage(data, saddled));
if (saddled) {
data = (byte) (data | SADDLED_FLAG);
}
final PacketWrapper entityDataPacket = PacketWrapper.create(ClientboundPackets1_21_2.SET_ENTITY_DATA, connection);
final List<EntityData> entityDataList = new ArrayList<>();
@@ -81,7 +81,14 @@ public final class ComponentRewriter1_21_5 extends NBTComponentRewriter<Clientbo
switch (action) {
case "open_url" -> clickEventTag.put("value", clickEventTag.getStringTag("url"));
case "change_page" -> clickEventTag.putString("value", Integer.toString(clickEventTag.getInt("page")));
case "run_command", "suggest_command" -> clickEventTag.put("value", clickEventTag.getStringTag("command"));
case "run_command" -> {
final StringTag command = clickEventTag.getStringTag("command");
if (command != null && !command.getValue().startsWith("/")) {
command.setValue("/" + command.getValue());
}
clickEventTag.put("value", command);
}
case "suggest_command" -> clickEventTag.put("value", clickEventTag.getStringTag("command"));
}
}
@@ -119,6 +119,12 @@ public final class EntityPacketRewriter1_21_5 extends EntityRewriter<Clientbound
updateTrim(entries, trimPatternRegistry ? "template_item" : "ingredient");
return super.handle(connection, key, entries);
}
if (key.equals("enchantment")) {
updateEnchantment(entries);
return super.handle(connection, key, entries);
}
if (!key.equals("wolf_variant")) {
return super.handle(connection, key, entries);
}
@@ -209,6 +215,20 @@ public final class EntityPacketRewriter1_21_5 extends EntityRewriter<Clientbound
});
}
private void updateEnchantment(final RegistryEntry[] entries) {
for (final RegistryEntry entry : entries) {
if (entry.tag() == null) {
continue;
}
final CompoundTag enchantment = (CompoundTag) entry.tag();
final ListTag<StringTag> slots = enchantment.getListTag("slots", StringTag.class);
if (slots != null) {
slots.getValue().removeIf(tag -> tag.getValue().equals("saddle")); // Remove saddle slot
}
}
}
private String visibility(final int id) {
return switch (id) {
case 0 -> "always";
@@ -286,16 +306,18 @@ public final class EntityPacketRewriter1_21_5 extends EntityRewriter<Clientbound
}));
filter().type(EntityTypes1_21_5.ABSTRACT_HORSE).index(17).handler((event, data) -> {
// Store data for later
// Store data and set saddled flag if needed
final TrackedEntity entity = event.trackedEntity();
if (entity.hasData()) {
final HorseDataStorage horseDataStorage = entity.data().get(HorseDataStorage.class);
if (horseDataStorage != null && horseDataStorage.saddled()) {
entity.data().put(new HorseDataStorage(data.value(), true));
return;
}
final byte horseData = data.value();
boolean saddled = false;
final HorseDataStorage horseDataStorage;
if (entity.hasData() && (horseDataStorage = entity.data().get(HorseDataStorage.class)) != null && horseDataStorage.saddled()) {
saddled = true;
data.setValue((byte) (horseData | BlockItemPacketRewriter1_21_5.SADDLED_FLAG));
}
entity.data().put(new HorseDataStorage(data.value(), false));
entity.data().put(new HorseDataStorage(horseData, saddled));
});
filter().type(EntityTypes1_21_5.CHICKEN).cancel(17); // Chicken variant
@@ -17,15 +17,26 @@
*/
package com.viaversion.viabackwards.protocol.v1_21_6to1_21_5;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viabackwards.api.BackwardsProtocol;
import com.viaversion.viabackwards.api.data.BackwardsMappingData;
import com.viaversion.viabackwards.api.rewriters.SoundRewriter;
import com.viaversion.viabackwards.api.rewriters.text.NBTComponentRewriter;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.Dialog;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.provider.ChestDialogViewProvider;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.provider.DialogViewProvider;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.rewriter.BlockItemPacketRewriter1_21_6;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.rewriter.ComponentRewriter1_21_6;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.rewriter.EntityPacketRewriter1_21_6;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.storage.ChestDialogStorage;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.storage.ClickEvents;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.storage.RegistryAndTags;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.storage.ServerLinks;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_6;
import com.viaversion.viaversion.api.platform.providers.ViaProviders;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.protocol.packet.provider.PacketTypesProvider;
import com.viaversion.viaversion.api.protocol.packet.provider.SimplePacketTypesProvider;
@@ -52,6 +63,7 @@ import com.viaversion.viaversion.rewriter.AttributeRewriter;
import com.viaversion.viaversion.rewriter.ParticleRewriter;
import com.viaversion.viaversion.rewriter.StatisticsRewriter;
import com.viaversion.viaversion.rewriter.TagRewriter;
import com.viaversion.viaversion.util.Key;
import com.viaversion.viaversion.util.SerializerVersion;
import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap;
@@ -73,9 +85,8 @@ public final class Protocol1_21_6To1_21_5 extends BackwardsProtocol<ClientboundP
protected void registerPackets() {
super.registerPackets();
tagRewriter.removeTags("dialog");
tagRewriter.registerGeneric(ClientboundPackets1_21_6.UPDATE_TAGS);
tagRewriter.registerGeneric(ClientboundConfigurationPackets1_21_6.UPDATE_TAGS);
registerClientbound(ClientboundPackets1_21_6.UPDATE_TAGS, this::updateTags);
registerClientbound(ClientboundConfigurationPackets1_21_6.UPDATE_TAGS, this::updateTags);
final SoundRewriter<ClientboundPacket1_21_6> soundRewriter = new SoundRewriter<>(this);
soundRewriter.registerSound1_19_3(ClientboundPackets1_21_6.SOUND);
@@ -112,17 +123,186 @@ public final class Protocol1_21_6To1_21_5 extends BackwardsProtocol<ClientboundP
particleRewriter.registerLevelParticles1_21_4(ClientboundPackets1_21_6.LEVEL_PARTICLES);
particleRewriter.registerExplode1_21_2(ClientboundPackets1_21_6.EXPLODE);
registerClientbound(ClientboundPackets1_21_6.CHANGE_DIFFICULTY, wrapper -> {
final int difficulty = wrapper.read(Types.VAR_INT);
wrapper.write(Types.UNSIGNED_BYTE, (short) difficulty);
});
registerServerbound(ServerboundPackets1_21_5.CHANGE_DIFFICULTY, wrapper -> {
final short difficulty = wrapper.read(Types.UNSIGNED_BYTE);
wrapper.write(Types.VAR_INT, (int) difficulty);
});
// Are you sure you want to see this? This is your last chance to turn back.
registerClientbound(ClientboundPackets1_21_6.SHOW_DIALOG, null, wrapper -> {
wrapper.cancel();
final RegistryAndTags registryAndTags = wrapper.user().get(RegistryAndTags.class);
final ServerLinks serverLinks = wrapper.user().get(ServerLinks.class);
final int id = wrapper.read(Types.VAR_INT) - 1;
CompoundTag tag;
if (id == -1) {
tag = (CompoundTag) wrapper.read(Types.TAG);
} else {
tag = registryAndTags.fromRegistry(id);
}
final DialogViewProvider provider = Via.getManager().getProviders().get(DialogViewProvider.class);
provider.openDialog(wrapper.user(), new Dialog(registryAndTags, serverLinks, tag));
});
registerClientbound(ClientboundConfigurationPackets1_21_6.SHOW_DIALOG, null, wrapper -> {
wrapper.cancel();
final RegistryAndTags registryAndTags = wrapper.user().get(RegistryAndTags.class);
final ServerLinks serverLinks = wrapper.user().get(ServerLinks.class);
final CompoundTag tag = (CompoundTag) wrapper.read(Types.TAG);
final DialogViewProvider provider = Via.getManager().getProviders().get(DialogViewProvider.class);
provider.openDialog(wrapper.user(), new Dialog(registryAndTags, serverLinks, tag));
});
registerClientbound(ClientboundPackets1_21_6.CLEAR_DIALOG, null, this::clearDialog);
registerClientbound(ClientboundConfigurationPackets1_21_6.CLEAR_DIALOG, null, this::clearDialog);
registerClientbound(ClientboundPackets1_21_6.SERVER_LINKS, this::storeServerLinks);
registerClientbound(ClientboundConfigurationPackets1_21_6.SERVER_LINKS, this::storeServerLinks);
registerServerbound(ServerboundPackets1_21_5.CHAT_COMMAND, wrapper -> {
final String command = wrapper.passthrough(Types.STRING);
final ClickEvents clickEvents = wrapper.user().get(ClickEvents.class);
if (clickEvents.handleChatCommand(wrapper.user(), command)) {
wrapper.cancel();
}
});
// The ones below are specific to the chest dialog view provider
registerServerbound(ServerboundPackets1_21_5.CONTAINER_CLOSE, wrapper -> {
final ChestDialogStorage storage = wrapper.user().get(ChestDialogStorage.class);
if (storage == null) {
return;
}
final ChestDialogViewProvider provider = (ChestDialogViewProvider) Via.getManager().getProviders().get(DialogViewProvider.class);
if (storage.phase() == ChestDialogStorage.Phase.ANVIL_VIEW) {
wrapper.cancel();
provider.openChestView(wrapper.user(), storage, ChestDialogStorage.Phase.DIALOG_VIEW);
return;
}
if (storage.phase() == ChestDialogStorage.Phase.WAITING_FOR_RESPONSE) {
wrapper.cancel();
if (storage.closeButtonEnabled()) {
provider.openChestView(wrapper.user(), storage, ChestDialogStorage.Phase.DIALOG_VIEW);
} else {
provider.openChestView(wrapper.user(), storage, ChestDialogStorage.Phase.WAITING_FOR_RESPONSE);
}
return;
}
final boolean allowClosing = storage.allowClosing();
if (!allowClosing) {
wrapper.cancel();
if (storage.dialog().canCloseWithEscape()) {
provider.clickButton(wrapper.user(), Dialog.AfterAction.CLOSE, storage.dialog().actionButton());
} else {
provider.openChestView(wrapper.user(), storage, ChestDialogStorage.Phase.DIALOG_VIEW);
}
}
storage.setAllowClosing(false);
});
registerServerbound(ServerboundPackets1_21_5.RENAME_ITEM, wrapper -> {
final ChestDialogStorage storage = wrapper.user().get(ChestDialogStorage.class);
if (storage == null || storage.phase() != ChestDialogStorage.Phase.ANVIL_VIEW) {
return;
}
wrapper.cancel();
final String name = wrapper.read(Types.STRING);
final ChestDialogViewProvider provider = (ChestDialogViewProvider) Via.getManager().getProviders().get(DialogViewProvider.class);
provider.updateAnvilText(wrapper.user(), name);
});
appendServerbound(ServerboundPackets1_21_5.CONTAINER_CLICK, wrapper -> {
final ChestDialogViewProvider provider = (ChestDialogViewProvider) Via.getManager().getProviders().get(DialogViewProvider.class);
if (provider == null) {
return;
}
final int containerId = wrapper.get(Types.VAR_INT, 0);
final int slot = wrapper.get(Types.SHORT, 0);
final byte button = wrapper.get(Types.BYTE, 0);
final int mode = wrapper.get(Types.VAR_INT, 2);
if (provider.clickDialog(wrapper.user(), containerId, slot, button, mode)) {
wrapper.cancel();
}
});
cancelClientbound(ClientboundPackets1_21_6.TRACKED_WAYPOINT);
cancelClientbound(ClientboundPackets1_21_6.CLEAR_DIALOG);
cancelClientbound(ClientboundPackets1_21_6.SHOW_DIALOG);
cancelClientbound(ClientboundConfigurationPackets1_21_6.CLEAR_DIALOG);
cancelClientbound(ClientboundConfigurationPackets1_21_6.SHOW_DIALOG);
}
private void clearDialog(final PacketWrapper wrapper) {
wrapper.cancel();
final DialogViewProvider provider = Via.getManager().getProviders().get(DialogViewProvider.class);
provider.closeDialog(wrapper.user());
}
private void updateTags(final PacketWrapper wrapper) {
tagRewriter.handleGeneric(wrapper);
wrapper.resetReader();
final RegistryAndTags registryAndTags = wrapper.user().get(RegistryAndTags.class);
final int length = wrapper.passthrough(Types.VAR_INT);
for (int i = 0; i < length; i++) {
final String registryKey = wrapper.read(Types.STRING);
final boolean dialog = "dialog".equals(Key.stripMinecraftNamespace(registryKey));
if (dialog) {
final int tagsSize = wrapper.read(Types.VAR_INT);
for (int j = 0; j < tagsSize; j++) {
final String key = wrapper.read(Types.STRING);
final int[] ids = wrapper.read(Types.VAR_INT_ARRAY_PRIMITIVE);
registryAndTags.storeTags(key, ids);
}
} else {
wrapper.write(Types.STRING, registryKey); // Write back
final int tagsSize = wrapper.passthrough(Types.VAR_INT);
for (int j = 0; j < tagsSize; j++) {
wrapper.passthrough(Types.STRING);
wrapper.passthrough(Types.VAR_INT_ARRAY_PRIMITIVE);
}
}
}
if (registryAndTags.tagsSent()) {
wrapper.set(Types.VAR_INT, 0, length - 1); // Dialog tags have been read, remove from size
}
}
private void storeServerLinks(final PacketWrapper wrapper) {
final ServerLinks serverLinks = new ServerLinks();
final int length = wrapper.passthrough(Types.VAR_INT);
for (int i = 0; i < length; i++) {
if (wrapper.passthrough(Types.BOOLEAN)) {
final int id = wrapper.passthrough(Types.VAR_INT);
final String url = wrapper.passthrough(Types.STRING);
serverLinks.storeLink(id, url);
} else {
final Tag tag = wrapper.passthrough(Types.COMPOUND_TAG);
final String url = wrapper.passthrough(Types.STRING);
serverLinks.storeLink(tag, url);
}
}
wrapper.user().put(serverLinks);
}
@Override
public void init(final UserConnection user) {
addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_21_6.PLAYER));
addItemHasher(user, new ItemHasherBase(this, user, SerializerVersion.V1_21_5, SerializerVersion.V1_21_5));
user.put(new RegistryAndTags());
user.put(new ClickEvents());
}
@Override
public void register(final ViaProviders providers) {
providers.register(DialogViewProvider.class, new ChestDialogViewProvider(this));
}
@Override
@@ -0,0 +1,171 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.data;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.input.Input;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.widget.Widget;
import com.viaversion.viaversion.util.Key;
import java.util.HashMap;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;
public class Button implements Widget {
public static final Button DEFAULT = defaulted();
private static final String[] CLICK_EVENTS = {
"open_url",
"run_command",
"suggest_command",
"show_dialog",
"change_page",
"copy_to_clipboard",
"custom"
};
private final Dialog dialog;
private final Tag label;
private final int width;
private final @Nullable Tag tooltip;
private @Nullable CompoundTag clickEvent;
private @Nullable Template template;
private @Nullable String id;
private @Nullable CompoundTag additions;
public Button(final Dialog dialog, final CompoundTag tag) {
this.dialog = dialog;
this.label = tag.get("label");
this.tooltip = tag.get("tooltip");
final int width = tag.getInt("width", 150);
if (width < 1 || width > 1024) {
throw new IllegalArgumentException("Width must be between 1 and 1024, got: " + width);
}
this.width = width;
final CompoundTag actionTag = tag.getCompoundTag("action");
if (actionTag == null) {
return;
}
final String type = actionTag.getString("type");
if (type == null) {
throw new IllegalArgumentException("Action type is missing in tag: " + tag);
}
for (final String event : CLICK_EVENTS) {
if (Key.stripMinecraftNamespace(type).equals(event)) {
clickEvent = actionTag;
clickEvent.put("action", clickEvent.remove("type"));
return;
}
}
if (Key.stripMinecraftNamespace(type).equals("dynamic/run_command")) {
template = Template.fromString(actionTag.getString("template"));
} else if (Key.stripMinecraftNamespace(type).equals("dynamic/custom")) {
id = actionTag.getString("id");
additions = actionTag.getCompoundTag("additions");
}
}
private static Button defaulted() {
final CompoundTag tag = new CompoundTag();
final CompoundTag label = new CompoundTag();
tag.put("label", label);
label.putString("translate", "gui.ok");
return new Button(null, tag);
}
public static Button openUrl(final Tag label, final String url) {
final CompoundTag tag = new CompoundTag();
final CompoundTag actionTag = new CompoundTag();
final CompoundTag openUrlTag = new CompoundTag();
tag.put("label", label);
tag.put("action", actionTag);
actionTag.putString("type", "open_url");
actionTag.put("action", openUrlTag);
openUrlTag.putString("url", url);
return new Button(null, tag);
}
/**
* Creates a click event for this button based on the provided inputs.
*
* @return a CompoundTag representing the click event, which can be used in a button action.
*/
public @Nullable CompoundTag clickEvent() {
if (clickEvent != null) {
return clickEvent;
}
if (dialog == null) {
return null;
}
final Input[] inputs = dialog.widgets()
.stream()
.filter(widget -> widget instanceof Input)
.toArray(Input[]::new);
if (template != null) {
final Map<String, String> substitutions = new HashMap<>();
for (final Input input : inputs) {
substitutions.put(input.key(), input.asCommandSubstitution());
}
final CompoundTag tag = new CompoundTag();
tag.putString("action", "run_command");
tag.putString("command", template.instantiate(substitutions));
return tag;
}
if (id != null) {
final CompoundTag additions = this.additions != null ? this.additions.copy() : new CompoundTag();
for (final Input input : inputs) {
additions.put(input.key(), input.asTag());
}
final CompoundTag tag = new CompoundTag();
tag.putString("action", "custom");
tag.putString("id", id);
tag.put("payload", additions);
return tag;
}
return null;
}
public Tag label() {
return label;
}
public @Nullable Tag tooltip() {
return tooltip;
}
public int width() {
return width;
}
}
@@ -0,0 +1,253 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.data;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.ListTag;
import com.viaversion.nbt.tag.StringTag;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.input.BooleanInput;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.input.NumberRangeInput;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.input.SingleOptionInput;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.input.TextInput;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.widget.ItemWidget;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.widget.TextWidget;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.widget.Widget;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.provider.ChestDialogViewProvider;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.storage.RegistryAndTags;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.storage.ServerLinks;
import com.viaversion.viaversion.util.Key;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* The dialog structure. See the used subclasses for further details on the
* specific structure. Note that the data hold here is directly from the server
* and needs to be remapped manually when used. See {@link ChestDialogViewProvider} for an example
*/
public final class Dialog implements Widget {
private final List<Widget> widgets = new ArrayList<>();
private final Tag title;
private final @Nullable Tag externalTitle;
private final boolean canCloseWithEscape;
private final AfterAction afterAction;
private @Nullable Button actionButton;
private @Nullable Button yesButton;
private @Nullable Button noButton;
private int columns;
private int buttonWidth;
public Dialog(final RegistryAndTags registryAndTags, final ServerLinks serverLinks, final CompoundTag tag) {
final String type = tag.getString("type");
if (type == null) {
throw new IllegalArgumentException("Dialog type is missing in tag: " + tag);
}
this.title = tag.get("title");
this.externalTitle = tag.get("external_title");
this.canCloseWithEscape = tag.getBoolean("can_close_with_escape", true);
this.afterAction = AfterAction.valueOf(tag.getString("after_action", "close").toUpperCase(Locale.ROOT));
ListTag<CompoundTag> bodyListTag = tag.getListTag("body", CompoundTag.class);
if (bodyListTag == null) {
CompoundTag bodyTag = tag.getCompoundTag("body");
if (bodyTag != null) {
bodyListTag = new ListTag<>(CompoundTag.class);
bodyListTag.add(bodyTag);
}
}
if (bodyListTag != null) {
for (final CompoundTag bodyTag : bodyListTag) {
fillBodyWidget(bodyTag);
}
}
final ListTag<CompoundTag> inputListTag = tag.getListTag("inputs", CompoundTag.class);
if (inputListTag != null) {
for (final CompoundTag inputTag : inputListTag) {
fillInputWidget(inputTag);
}
}
switch (Key.stripMinecraftNamespace(type)) {
case "notice" -> fillNoticeDialog(tag);
case "server_links" -> fillServerLinksDialog(serverLinks, tag);
case "dialog_list" -> fillDialogList(registryAndTags, serverLinks, tag);
case "multi_action" -> fillMultiActionDialog(tag);
case "confirmation" -> fillConfirmationDialog(tag);
default -> throw new IllegalArgumentException("Unknown dialog type: " + type + " in tag: " + tag);
}
}
private void fillBodyWidget(final CompoundTag tag) {
final String type = tag.getString("type");
if (type == null) {
throw new IllegalArgumentException("Dialog type is missing in tag: " + tag);
}
if (Key.stripMinecraftNamespace(type).equals("plain_message")) {
widgets.add(new TextWidget(tag));
} else if (Key.stripMinecraftNamespace(type).equals("item")) {
widgets.add(new ItemWidget(tag));
} else {
throw new IllegalArgumentException("Unknown dialog body type: " + type + " in tag: " + tag);
}
}
private void fillInputWidget(final CompoundTag tag) {
final String type = tag.getString("type");
if (type == null) {
throw new IllegalArgumentException("Dialog type is missing in tag: " + tag);
}
widgets.add(switch (Key.stripMinecraftNamespace(type)) {
case "boolean" -> new BooleanInput(tag);
case "number_range" -> new NumberRangeInput(tag);
case "single_option" -> new SingleOptionInput(tag);
case "text" -> new TextInput(tag);
default -> throw new IllegalArgumentException("Unknown dialog input type: " + type + " in tag: " + tag);
});
}
private void fillNoticeDialog(final CompoundTag tag) {
final CompoundTag actionTag = tag.getCompoundTag("action");
actionButton = actionTag == null ? Button.DEFAULT : actionButton;
}
private void fillServerLinksDialog(final ServerLinks serverLinks, final CompoundTag tag) {
fillDialogBase(tag);
buttonWidth = tag.getInt("button_width", 150);
if (serverLinks == null) {
return;
}
for (final Map.Entry<Tag, String> entry : serverLinks.links().entrySet()) {
final Tag label = entry.getKey();
final String url = entry.getValue();
widgets.add(Button.openUrl(label, url));
}
}
private void fillDialogList(final RegistryAndTags registryAndTags, final ServerLinks serverLinks, final CompoundTag tag) {
// Hold them as either a list of inlined, a singleton inlined, a registry entry or a tag.
ListTag<CompoundTag> dialogsTag = tag.getListTag("dialogs", CompoundTag.class);
if (dialogsTag == null) {
CompoundTag dialogTag = tag.getCompoundTag("dialogs");
if (dialogTag != null) {
dialogsTag = new ListTag<>(CompoundTag.class);
dialogsTag.add(dialogTag);
}
StringTag registryDialogTag = tag.getStringTag("dialogs");
if (registryDialogTag != null) {
dialogsTag = new ListTag<>(CompoundTag.class);
final String key = registryDialogTag.getValue();
if (key.startsWith("#")) {
for (final CompoundTag entry : registryAndTags.fromRegistryKey(key.substring(1))) {
dialogsTag.add(entry);
}
} else {
dialogsTag.add(registryAndTags.fromRegistry(key));
}
}
}
widgets.addAll(dialogsTag.stream().map(dialog -> new Dialog(registryAndTags, serverLinks, dialog)).toList());
fillDialogBase(tag);
buttonWidth = tag.getInt("button_width", 150);
}
private void fillDialogBase(final CompoundTag tag) {
final CompoundTag exitActionTag = tag.getCompoundTag("exit_action");
actionButton = exitActionTag == null ? null : new Button(this, exitActionTag);
final int columns = tag.getInt("columns", 2);
if (columns < 1) {
throw new IllegalArgumentException("Columns must be non-negative, got: " + columns);
}
this.columns = columns;
}
private void fillMultiActionDialog(final CompoundTag tag) {
final ListTag<CompoundTag> actionsTag = tag.getListTag("actions", CompoundTag.class);
if (actionsTag == null || actionsTag.isEmpty()) {
throw new IllegalArgumentException("Actions must not be empty in tag: " + tag);
}
widgets.addAll(actionsTag.stream().map(actionTag -> new Button(this, actionTag)).toList());
fillDialogBase(tag);
}
private void fillConfirmationDialog(final CompoundTag tag) {
yesButton = new Button(this, tag.getCompoundTag("yes"));
noButton = new Button(this, tag.getCompoundTag("no"));
}
public Tag title() {
return title;
}
public @Nullable Tag externalTitle() {
return externalTitle;
}
public boolean canCloseWithEscape() {
return canCloseWithEscape;
}
public AfterAction afterAction() {
return afterAction;
}
public enum AfterAction {
CLOSE,
NONE,
WAIT_FOR_RESPONSE
}
public @Nullable Button actionButton() {
return actionButton;
}
public @Nullable Button yesButton() {
return yesButton;
}
public @Nullable Button noButton() {
return noButton;
}
public int columns() {
return columns;
}
public int buttonWidth() {
return buttonWidth;
}
public List<Widget> widgets() {
return widgets;
}
}
@@ -0,0 +1,97 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.data;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import java.util.List;
import java.util.Map;
public record Template(List<String> segments, List<String> variables) {
private static final int MAX_LENGTH = 2000000;
public static Template fromString(final String string) {
final Builder<String> segmentsBuilder = ImmutableList.builder();
final Builder<String> variablesBuilder = ImmutableList.builder();
int currentIndex = 0;
int dollarIndex = string.indexOf('$');
while (dollarIndex != -1) {
if (dollarIndex != string.length() - 1 && string.charAt(dollarIndex + 1) == '(') {
segmentsBuilder.add(string.substring(currentIndex, dollarIndex));
final int closingParenIndex = string.indexOf(41, dollarIndex + 1);
if (closingParenIndex == -1) {
throw new IllegalArgumentException("Unterminated macro variable");
}
final String variableName = string.substring(dollarIndex + 2, closingParenIndex);
if (!isValidVariableName(variableName)) {
throw new IllegalArgumentException("Invalid macro variable name '" + variableName + "'");
}
variablesBuilder.add(variableName);
currentIndex = closingParenIndex + 1;
dollarIndex = string.indexOf(36, currentIndex);
} else {
dollarIndex = string.indexOf(36, dollarIndex + 1);
}
}
if (currentIndex == 0) {
throw new IllegalArgumentException("No variables in macro");
}
if (currentIndex != string.length()) {
segmentsBuilder.add(string.substring(currentIndex));
}
return new Template(segmentsBuilder.build(), variablesBuilder.build());
}
private static boolean isValidVariableName(final String string) {
for (int i = 0; i < string.length(); i++) {
final char c = string.charAt(i);
if (!Character.isLetterOrDigit(c) && c != '_') {
return false;
}
}
return true;
}
public String instantiate(final Map<String, String> map) {
return substitute(variables().stream().map(string -> map.getOrDefault(string, "")).toList());
}
private String substitute(final List<String> list) {
final StringBuilder out = new StringBuilder();
for (int i = 0; i < this.variables.size(); i++) {
out.append(this.segments.get(i)).append(list.get(i));
if (out.length() > MAX_LENGTH) {
throw new IllegalArgumentException("Output too long (> " + MAX_LENGTH + ")");
}
}
if (this.segments.size() > this.variables.size()) {
out.append(this.segments.get(this.segments.size() - 1));
if (out.length() > MAX_LENGTH) {
throw new IllegalArgumentException("Output too long (> " + MAX_LENGTH + ")");
}
}
return out.toString();
}
}
@@ -0,0 +1,82 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.data.input;
import com.viaversion.nbt.tag.ByteTag;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.Tag;
public final class BooleanInput implements Input {
private final String key;
private final Tag label;
private final boolean initial;
private final String onTrue;
private final String onFalse;
private boolean value;
public BooleanInput(final CompoundTag tag) {
this.key = tag.getString("key");
this.label = tag.get("label");
this.initial = tag.getBoolean("initial", false);
this.onTrue = tag.getString("on_true", "true");
this.onFalse = tag.getString("on_false", "false");
this.value = initial;
}
@Override
public String key() {
return key;
}
@Override
public String asCommandSubstitution() {
return value ? onTrue : onFalse;
}
@Override
public Tag asTag() {
return new ByteTag(value);
}
public Tag label() {
return label;
}
public boolean initial() {
return initial;
}
public String onTrue() {
return onTrue;
}
public String onFalse() {
return onFalse;
}
public boolean value() {
return value;
}
public void setValue(final boolean value) {
this.value = value;
}
}
@@ -0,0 +1,31 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.data.input;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.widget.Widget;
public interface Input extends Widget {
String key();
String asCommandSubstitution();
Tag asTag();
}
@@ -0,0 +1,114 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.data.input;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.FloatTag;
import com.viaversion.nbt.tag.Tag;
import static com.viaversion.viabackwards.utils.ChatUtil.text;
import static com.viaversion.viabackwards.utils.ChatUtil.translate;
public final class NumberRangeInput implements Input {
private final String key;
private final Tag label;
private final String labelFormat;
private final float start;
private final float end;
private final Float initial;
private final Float step;
private float value;
public NumberRangeInput(final CompoundTag tag) {
this.key = tag.getString("key");
this.label = tag.get("label");
this.labelFormat = tag.getString("label_format", "options.generic_value");
this.start = tag.getFloat("start");
this.end = tag.getFloat("end");
this.initial = tag.getFloat("initial", (this.start + this.end) / 2F);
final FloatTag stepTag = tag.getFloatTag("step");
if (stepTag != null && stepTag.asFloat() < 0F) {
throw new IllegalArgumentException("Step must be non-negative, got: " + stepTag.asFloat());
}
this.step = stepTag == null ? -1F : stepTag.asFloat();
this.value = initial;
}
@Override
public String key() {
return key;
}
@Override
public String asCommandSubstitution() {
return valueAsString();
}
@Override
public Tag asTag() {
return new FloatTag(value);
}
public Tag label() {
return label;
}
public String labelFormat() {
return labelFormat;
}
public float start() {
return start;
}
public float end() {
return end;
}
public float initial() {
return initial;
}
public float step() {
return step;
}
public float value() {
return value;
}
public String valueAsString() {
final int asInt = (int) value;
return asInt == value ? Integer.toString(asInt) : Float.toString(value);
}
public void setValue(final float value) {
this.value = value;
}
public void setClampedValue(final float value) {
this.value = (value < start) ? start : Math.min(value, end);
}
public Tag displayName() {
return translate(labelFormat, label, text(valueAsString()));
}
}
@@ -0,0 +1,126 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.data.input;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.ListTag;
import com.viaversion.nbt.tag.StringTag;
import com.viaversion.nbt.tag.Tag;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class SingleOptionInput implements Input {
private final String key;
private final Entry[] options;
private final @Nullable Tag label;
private int value;
public SingleOptionInput(final CompoundTag tag) {
final ListTag<CompoundTag> options = tag.getListTag("options", CompoundTag.class);
if (options == null || options.isEmpty()) {
throw new IllegalArgumentException("Options must not be empty in tag: " + tag);
}
this.key = tag.getString("key");
this.options = options.stream().map(Entry::new).toArray(Entry[]::new);
this.label = tag.getBoolean("label_visible", true) ? tag.get("label") : null;
for (int i = 0; i < this.options.length; i++) {
if (this.options[i].initial()) {
if (this.value != 0) {
throw new IllegalArgumentException("Multiple initial options found in tag: " + tag);
}
this.value = i;
}
}
}
@Override
public String key() {
return key;
}
@Override
public String asCommandSubstitution() {
return this.options[value].id;
}
@Override
public Tag asTag() {
return new StringTag(asCommandSubstitution());
}
public Entry[] options() {
return options;
}
public @Nullable Tag label() {
return label;
}
public int value() {
return value;
}
public void setValue(final int value) {
if (value < 0 || value >= options.length) {
throw new IllegalArgumentException("Value must be between 0 and " + (options.length - 1));
}
this.value = value;
}
public void setClampedValue(final int value) {
if (value < 0) {
this.value = 0;
} else if (value >= options.length) {
this.value = 0;
} else {
this.value = value;
}
}
public static class Entry {
private final String id;
private final Tag display;
private final boolean initial;
public Entry(final CompoundTag tag) {
id = tag.getString("id");
display = tag.get("display");
initial = tag.getBoolean("initial", false);
}
public String id() {
return id;
}
public Tag display() {
return display;
}
public boolean initial() {
return initial;
}
public Tag computeDisplay() {
return Objects.requireNonNullElseGet(display, () -> new StringTag(id));
}
}
}
@@ -0,0 +1,121 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.data.input;
import com.viaversion.nbt.tag.CompoundTag;
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 org.checkerframework.checker.nullness.qual.Nullable;
public final class TextInput implements Input {
private final String key;
private final @Nullable Tag label;
private final String initial;
private final int maxLength;
private final @Nullable MultilineOptions[] options;
private String value;
public TextInput(final CompoundTag tag) {
this.key = tag.getString("key");
this.label = tag.getBoolean("label_visible", true) ? tag.get("label") : null;
this.initial = tag.getString("initial", "");
this.maxLength = tag.getInt("max_length", 32);
if (this.maxLength < 1) {
throw new IllegalArgumentException("Max length must be at least 1, got: " + this.maxLength);
}
final ListTag<CompoundTag> multilineList = tag.getListTag("multiline", CompoundTag.class);
if (multilineList != null && !multilineList.isEmpty()) {
this.options = multilineList.stream().map(MultilineOptions::new).toArray(MultilineOptions[]::new);
} else {
this.options = null;
}
this.value = initial;
}
@Override
public String key() {
return key;
}
@Override
public String asCommandSubstitution() {
return value;
}
@Override
public Tag asTag() {
return new StringTag(value);
}
public @Nullable Tag label() {
return label;
}
public String initial() {
return initial;
}
public int maxLength() {
return maxLength;
}
public @Nullable MultilineOptions[] options() {
return options;
}
public String value() {
return value;
}
public void setValue(final String value) {
if (value.length() > maxLength) {
throw new IllegalArgumentException("Value exceeds max length of " + maxLength + ": " + value);
}
this.value = value;
}
public void setClampedValue(final String value) {
if (value.length() > maxLength) {
this.value = value.substring(0, maxLength);
} else {
this.value = value;
}
}
public static class MultilineOptions {
private final @Nullable Integer maxLines;
public MultilineOptions(final CompoundTag tag) {
final IntTag maxLinesTag = tag.getIntTag("max_lines");
if (maxLinesTag != null && maxLinesTag.asInt() < 1) {
throw new IllegalArgumentException("Max lines must be at least 1, got: " + maxLinesTag);
}
this.maxLines = maxLinesTag != null ? maxLinesTag.asInt() : null;
}
public @Nullable Integer maxLines() {
return maxLines;
}
}
}
@@ -0,0 +1,69 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.data.widget;
import com.viaversion.nbt.tag.CompoundTag;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class ItemWidget implements Widget {
private final CompoundTag item;
private final @Nullable TextWidget description;
private final boolean showTooltip;
private final int width;
private final int height;
public ItemWidget(final CompoundTag tag) {
this.item = tag.getCompoundTag("item");
if (this.item == null) {
throw new IllegalArgumentException("Item tag is missing in ItemWidget tag: " + tag);
}
this.description = tag.contains("description") ? new TextWidget(tag.getCompoundTag("description")) : null;
this.showTooltip = tag.getBoolean("show_tooltip", true);
final int width = tag.getInt("width", 16);
final int height = tag.getInt("height", 16);
if (width < 1 || width > 256) {
throw new IllegalArgumentException("Width must be between 1 and 256, got: " + width);
}
if (height < 1 || height > 256) {
throw new IllegalArgumentException("Height must be between 1 and 256, got: " + height);
}
this.width = width;
this.height = height;
}
public CompoundTag item() {
return item;
}
public @Nullable TextWidget description() {
return description;
}
public boolean showTooltip() {
return showTooltip;
}
public int width() {
return width;
}
public int height() {
return height;
}
}
@@ -0,0 +1,40 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.data.widget;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.Tag;
public final class TextWidget implements Widget {
private final Tag label;
private final int width;
public TextWidget(final CompoundTag tag) {
this.label = tag.get("contents");
this.width = tag.getInt("width", 200);
}
public Tag label() {
return label;
}
public int width() {
return width;
}
}
@@ -0,0 +1,21 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.data.widget;
public interface Widget {
}
@@ -0,0 +1,596 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.provider;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.Protocol1_21_6To1_21_5;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.Button;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.Dialog;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.input.BooleanInput;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.input.NumberRangeInput;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.input.SingleOptionInput;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.input.TextInput;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.widget.ItemWidget;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.widget.TextWidget;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.widget.Widget;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.storage.ChestDialogStorage;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.storage.ClickEvents;
import com.viaversion.viabackwards.utils.ChatUtil;
import com.viaversion.viaversion.api.connection.UserConnection;
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.StructuredItem;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.protocol.packet.State;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.version.VersionedTypes;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ClientboundPackets1_21_5;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPackets1_21_6;
import com.viaversion.viaversion.util.Key;
import com.viaversion.viaversion.util.MathUtil;
import java.util.ArrayList;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
import static com.viaversion.viabackwards.utils.ChatUtil.fixStyle;
import static com.viaversion.viabackwards.utils.ChatUtil.text;
import static com.viaversion.viabackwards.utils.ChatUtil.translate;
/**
* Default dialog view emulator. The layout has been stretched into a lot sub-functions for plugins to override.
*/
public class ChestDialogViewProvider implements DialogViewProvider {
private static final int INVENTORY_SIZE = 27;
private static final int INVENTORY_LAST_ROW = INVENTORY_SIZE - 9;
private final Protocol1_21_6To1_21_5 protocol;
public ChestDialogViewProvider(final Protocol1_21_6To1_21_5 protocol) {
this.protocol = protocol;
}
@Override
public void openDialog(final UserConnection connection, final Dialog dialog) {
final State state = connection.getProtocolInfo().getClientState();
if (state == State.CONFIGURATION) {
// TODO Implement by ending and re-starting the configuration phase
return;
}
// Batch text widgets following one another into a single MultiTextWidget to be display properly.
final List<Tag> texts = new ArrayList<>();
for (final Widget widget : new ArrayList<>(dialog.widgets())) {
if (widget instanceof final TextWidget textWidget) {
texts.add(textWidget.label());
dialog.widgets().remove(textWidget);
} else if (!texts.isEmpty()) {
dialog.widgets().add(dialog.widgets().indexOf(widget), new MultiTextWidget(texts.toArray(Tag[]::new)));
texts.clear();
}
}
final ChestDialogStorage previousStorage = connection.get(ChestDialogStorage.class);
final ChestDialogStorage storage = new ChestDialogStorage(this, dialog);
if (previousStorage != null) {
storage.setPreviousDialog(previousStorage.dialog());
}
connection.put(storage);
openChestView(connection, storage, ChestDialogStorage.Phase.DIALOG_VIEW);
}
public void openChestView(final UserConnection connection, final ChestDialogStorage storage, ChestDialogStorage.Phase phase) {
storage.setPhase(connection, phase);
final PacketWrapper openScreen = PacketWrapper.create(ClientboundPackets1_21_5.OPEN_SCREEN, connection);
openScreen.write(Types.VAR_INT, storage.containerId());
openScreen.write(Types.VAR_INT, 2); // Container type id
openScreen.write(Types.TAG, storage.dialog().title());
openScreen.send(Protocol1_21_6To1_21_5.class);
updateDialog(connection, storage.dialog());
}
@Override
public void closeDialog(final UserConnection connection) {
final State state = connection.getProtocolInfo().getClientState();
if (state == State.CONFIGURATION) {
// TODO Implement by ending and re-starting the configuration phase
return;
}
final ChestDialogStorage storage = connection.get(ChestDialogStorage.class);
final PacketWrapper containerClose = PacketWrapper.create(ClientboundPackets1_21_5.CONTAINER_CLOSE, connection);
containerClose.write(Types.VAR_INT, storage.containerId());
containerClose.send(Protocol1_21_6To1_21_5.class);
if (storage.previousDialog() != null) {
openDialog(connection, storage.previousDialog());
} else {
connection.remove(ChestDialogStorage.class);
}
}
public boolean clickDialog(final UserConnection connection, final int container, int slot, final byte mouse, final int mode) {
final ChestDialogStorage storage = connection.get(ChestDialogStorage.class);
if (storage == null || storage.containerId() != container) {
return false;
}
if (mode != 0 || slot < 0 || slot >= INVENTORY_SIZE) {
updateDialog(connection, storage.dialog()); // Resync inventory view
return true;
}
if (storage.phase() == ChestDialogStorage.Phase.ANVIL_VIEW) {
openChestView(connection, storage, ChestDialogStorage.Phase.DIALOG_VIEW);
return true;
}
if (storage.phase() == ChestDialogStorage.Phase.WAITING_FOR_RESPONSE) {
if (slot == storage.actionIndex() && storage.closeButtonEnabled()) {
closeDialog(connection);
} else {
updateDialog(connection, storage.dialog()); // Resync inventory view
}
return true;
}
// Page navigation
if (slot == INVENTORY_SIZE - 1) {
final int pages = MathUtil.ceil(storage.items().length / (float) INVENTORY_LAST_ROW);
if (mouse == 0) {
storage.page++;
} else if (mouse == 1) {
storage.page--;
}
storage.page = MathUtil.clamp(storage.page, 0, pages - 1);
}
slot += storage.page * INVENTORY_LAST_ROW;
// Input widgets
final List<Widget> widgets = storage.dialog().widgets();
if (slot < widgets.size()) {
final Widget widget = widgets.get(slot);
if (widget instanceof final BooleanInput booleanInput) {
clickBooleanInput(booleanInput);
} else if (widget instanceof final NumberRangeInput numberRangeInput) {
clickNumberRangeInput(numberRangeInput, mouse);
} else if (widget instanceof final TextInput textInput) {
clickTextInput(connection, textInput);
} else if (widget instanceof final SingleOptionInput singleOptionInput) {
clickSingleOptionInput(singleOptionInput);
} else if (widget instanceof final Button button) {
clickButton(connection, storage.dialog().afterAction(), button);
} else if (widget instanceof final Dialog dialog) {
clickDialogButton(connection, dialog);
}
}
// And some special cases.
if (slot == storage.confirmationYesIndex()) {
final Button yesButton = storage.dialog().yesButton();
if (yesButton != null) {
clickButton(connection, storage.dialog().afterAction(), yesButton);
}
}
if (slot == storage.confirmationNoIndex()) {
final Button noButton = storage.dialog().noButton();
if (noButton != null) {
clickButton(connection, storage.dialog().afterAction(), noButton);
}
}
if (slot == storage.actionIndex()) {
final Button actionButton = storage.dialog().actionButton();
if (actionButton != null) {
clickButton(connection, storage.dialog().afterAction(), actionButton);
}
}
// Resync inventory view if the actions above didn't close the dialog.
if (connection.has(ChestDialogStorage.class) && storage.phase() == ChestDialogStorage.Phase.DIALOG_VIEW) {
updateDialog(connection, storage.dialog());
}
return true;
}
public void updateDialog(final UserConnection connection, final Dialog dialog) {
final ChestDialogStorage storage = connection.get(ChestDialogStorage.class);
final PacketWrapper containerSetContent = PacketWrapper.create(ClientboundPackets1_21_5.CONTAINER_SET_CONTENT, connection);
containerSetContent.write(Types.VAR_INT, storage.containerId());
containerSetContent.write(Types.VAR_INT, 0); // Revision
containerSetContent.write(VersionedTypes.V1_21_5.itemArray, getItems(connection, storage, dialog));
containerSetContent.write(VersionedTypes.V1_21_5.item, StructuredItem.empty());
containerSetContent.send(Protocol1_21_6To1_21_5.class);
}
protected Item createPageNavigationItem() {
return createItem(
"minecraft:arrow",
text("§9§lPage navigation"),
"§9Left click: §6Go to next page",
"§9Right click: §6Go to previous page"
);
}
protected Item createActionButtonItem(final UserConnection connection, final Button button) {
final Tag label = handleTag(connection, button.label());
if (button.tooltip() == null) {
return createItem("minecraft:oak_button", label);
} else {
return createItem("minecraft:oak_button", label, handleTag(connection, button.tooltip()));
}
}
protected Item createCloseButtonItem(final Tag label) {
return createItem("minecraft:oak_button", label);
}
protected Item getItemWidget(final UserConnection connection, final ItemWidget itemWidget) {
final String identifier = itemWidget.item().getString("id");
final int count = itemWidget.item().getInt("count", 1);
final Tag label = text(Key.stripMinecraftNamespace(identifier));
final Item item = createItem(identifier, label);
item.setAmount(count);
if (itemWidget.description() != null) {
item.dataContainer().set(StructuredDataKey.LORE, new Tag[]{
handleTag(connection, fixStyle(itemWidget.description().label()))
});
}
if (!itemWidget.showTooltip()) {
item.dataContainer().set(StructuredDataKey.HIDE_TOOLTIP);
}
// If we were to parse item components from NBT for chat items they would be parsed here and stored into the data container.
// In VV, chat items are rewritten manually at the time being and therefore no conversion code exists.
return item;
}
protected Item getMultiTextWidget(final UserConnection connection, final MultiTextWidget multiTextWidget) {
final Tag name = handleTag(connection, multiTextWidget.labels()[0]);
final int length = multiTextWidget.labels().length;
if (length == 1) {
return createItem("minecraft:paper", name);
}
final Tag[] lore = new Tag[length - 1];
for (int i = 1; i < length; i++) {
lore[i - 1] = handleTag(connection, fixStyle(multiTextWidget.labels()[i]));
}
return createItem("minecraft:paper", name, lore);
}
protected Item getBooleanInput(final UserConnection connection, final BooleanInput booleanInput) {
final String item = booleanInput.value() ? "minecraft:lime_dye" : "minecraft:gray_dye";
final Tag[] label = ChatUtil.split(booleanInput.label(), "\n");
// The only one that supports newlines in the label
if (label.length == 1) {
return createItem(
item,
handleTag(connection, booleanInput.label()),
text("§9Left click: §6Toggle value")
);
} else {
final Tag[] lore = new Tag[label.length];
for (int i = 1; i < label.length; i++) {
lore[i - 1] = handleTag(connection, fixStyle(label[i]));
}
lore[lore.length - 1] = text("§9Left click: §6Toggle value");
return createItem(
item,
handleTag(connection, label[0]),
lore
);
}
}
protected void clickBooleanInput(final BooleanInput booleanInput) {
booleanInput.setValue(!booleanInput.value());
}
protected Item getNumberRangeInput(final UserConnection connection, final NumberRangeInput numberRangeInput) {
final Tag label = handleTag(connection, numberRangeInput.displayName());
return createItem(
"minecraft:clock",
label,
"§9Left click: §6Increase value by " + numberRangeInput.step(),
"§9Right click: §6Decrease value by " + numberRangeInput.step(),
"§7(Value between §a" + numberRangeInput.start() + " §7and §a" + numberRangeInput.end() + "§7)"
);
}
protected void clickNumberRangeInput(final NumberRangeInput numberRangeInput, final int mouse) {
float value = numberRangeInput.value();
if (mouse == 0) { // Left click
value += numberRangeInput.step();
} else if (mouse == 1) { // Right click
value -= numberRangeInput.step();
}
numberRangeInput.setClampedValue(value);
}
protected Item getTextInput(final UserConnection connection, final TextInput textInput) {
final Tag currentValue = text("§7Current value: §a" + textInput.value());
if (textInput.label() == null) {
return createItem("minecraft:writable_book", currentValue);
} else {
final Tag label = handleTag(connection, textInput.label());
return createItem("minecraft:writable_book", label, currentValue, text("§9Left click: §6Edit text"));
}
}
protected void clickTextInput(final UserConnection connection, final TextInput textInput) {
final ChestDialogStorage storage = connection.get(ChestDialogStorage.class);
openAnvilView(connection, storage, text("§7Edit text"), textInput.value(), textInput);
}
protected Item getSingleOptionInput(final UserConnection connection, final SingleOptionInput singleOptionInput) {
final Tag displayName = singleOptionInput.options()[singleOptionInput.value()].computeDisplay();
final Tag label;
if (singleOptionInput.label() != null) {
label = translate("options.generic_value", singleOptionInput.label(), displayName);
} else {
label = displayName;
}
return createItem(
"minecraft:bookshelf",
handleTag(connection, label),
"§9Left click: §6Go to next option",
"§9Right click: §6Go to previous option"
);
}
protected void clickSingleOptionInput(final SingleOptionInput singleOptionInput) {
singleOptionInput.setClampedValue(singleOptionInput.value() + 1);
}
protected Item getButton(final UserConnection connection, final Button button) {
return createItem("minecraft:oak_button", handleTag(connection, button.label()));
}
public void clickButton(final UserConnection connection, final Dialog.AfterAction afterAction, @Nullable final Button button) {
final ChestDialogStorage storage = connection.get(ChestDialogStorage.class);
switch (afterAction) {
case CLOSE -> closeDialog(connection);
case WAIT_FOR_RESPONSE -> storage.setPhase(null, ChestDialogStorage.Phase.WAITING_FOR_RESPONSE);
}
if (button == null || button.clickEvent() == null) {
return;
}
final CompoundTag clickEvent = button.clickEvent();
final String action = Key.stripMinecraftNamespace(clickEvent.getString("action"));
switch (action) {
case "open_url" -> {
// We can't open a URL for the client, so roughly emulate by opening an Anvil containing the URL.
final String url = clickEvent.getString("url");
openAnvilView(connection, storage, text("Open URL"), url, null);
}
case "run_command" -> {
// The vanilla client validates for signed argument types and has more requirements for this click event,
// but we can't do this here and therefore just always send the packet assuming this is correct...
final PacketWrapper chatCommand = PacketWrapper.create(ServerboundPackets1_21_6.CHAT_COMMAND, connection);
String command = clickEvent.getString("command");
if (command.startsWith("/")) {
command = command.substring(1);
}
chatCommand.write(Types.STRING, command);
chatCommand.sendToServer(Protocol1_21_6To1_21_5.class);
}
case "copy_to_clipboard" -> {
// Same as above, we can't access the clipboard
final String value = clickEvent.getString("value");
openAnvilView(connection, storage, text("Copy to clipboard"), value, null);
}
}
ClickEvents.handleClickEvent(connection, clickEvent); // Handle show_dialog and custom
}
protected Item createTextInputItem(final String value) {
return createItem("minecraft:paper", text(value), "§9Left click/close: §6Set text");
}
protected Item createTextCopyItem(final String value) {
return createItem("minecraft:paper", text(value), "§9Left click: §6Close");
}
private void openAnvilView(
final UserConnection connection,
final ChestDialogStorage storage,
final Tag title,
final String value,
final TextInput textInput
) {
storage.setPhase(connection, ChestDialogStorage.Phase.ANVIL_VIEW);
final PacketWrapper openScreen = PacketWrapper.create(ClientboundPackets1_21_5.OPEN_SCREEN, connection);
openScreen.write(Types.VAR_INT, storage.containerId());
openScreen.write(Types.VAR_INT, 8); // Container type id
openScreen.write(Types.TAG, title);
openScreen.send(Protocol1_21_6To1_21_5.class);
final Item[] items = new Item[1];
items[0] = textInput != null ? createTextInputItem(value) : createTextCopyItem(value);
storage.setCurrentTextInput(textInput);
final PacketWrapper containerSetContent = PacketWrapper.create(ClientboundPackets1_21_5.CONTAINER_SET_CONTENT, connection);
containerSetContent.write(Types.VAR_INT, storage.containerId());
containerSetContent.write(Types.VAR_INT, 0); // Revision
containerSetContent.write(VersionedTypes.V1_21_5.itemArray, items);
containerSetContent.write(VersionedTypes.V1_21_5.item, StructuredItem.empty());
containerSetContent.send(Protocol1_21_6To1_21_5.class);
}
public void updateAnvilText(final UserConnection connection, final String value) {
if (value.isEmpty()) {
return;
}
final ChestDialogStorage storage = connection.get(ChestDialogStorage.class);
if (storage.currentTextInput() != null) {
storage.currentTextInput().setClampedValue(value);
}
}
protected Item getDialog(final UserConnection connection, final Dialog dialog) {
final Tag title = dialog.externalTitle() != null ? dialog.externalTitle() : dialog.title();
return createItem("minecraft:command_block", handleTag(connection, title));
}
protected void clickDialogButton(final UserConnection connection, final Dialog dialog) {
closeDialog(connection);
openDialog(connection, dialog);
}
protected Item getItem(final UserConnection connection, final Widget widget) {
if (widget instanceof final ItemWidget itemWidget) {
return getItemWidget(connection, itemWidget);
} else if (widget instanceof final MultiTextWidget multiTextWidget) {
return getMultiTextWidget(connection, multiTextWidget);
} else if (widget instanceof final BooleanInput booleanInput) {
return getBooleanInput(connection, booleanInput);
} else if (widget instanceof final NumberRangeInput numberRangeInput) {
return getNumberRangeInput(connection, numberRangeInput);
} else if (widget instanceof TextInput textInput) {
return getTextInput(connection, textInput);
} else if (widget instanceof SingleOptionInput singleOptionInput) {
return getSingleOptionInput(connection, singleOptionInput);
} else if (widget instanceof Button button) {
return getButton(connection, button);
} else if (widget instanceof Dialog dialog) {
return getDialog(connection, dialog);
}
throw new IllegalArgumentException("Unknown widget type: " + widget.getClass().getName());
}
protected Item[] getItems(final UserConnection connection, final ChestDialogStorage storage, final Dialog dialog) {
final Item[] items = StructuredItem.emptyArray(INVENTORY_SIZE);
int confirmationYesIndex = -1;
int confirmationNoIndex = -1;
int actionIndex = -1;
if (storage.phase() == ChestDialogStorage.Phase.WAITING_FOR_RESPONSE) {
actionIndex = 13;
items[actionIndex] = createCloseButtonItem(storage.closeButtonLabel());
storage.setItems(items, confirmationYesIndex, confirmationNoIndex, actionIndex);
return items;
}
final List<Widget> widgets = dialog.widgets();
if (widgets.size() > INVENTORY_LAST_ROW) {
final int begin = storage.page * INVENTORY_LAST_ROW;
final int end = Math.min((storage.page + 1) * INVENTORY_LAST_ROW, widgets.size());
for (int i = 0; i < end - begin; i++) {
items[i] = getItem(connection, widgets.get(begin + i));
}
items[INVENTORY_SIZE - 1] = createPageNavigationItem();
} else {
for (int i = 0; i < widgets.size(); i++) {
items[i] = getItem(connection, widgets.get(i));
}
}
// And some special cases.
if (dialog.yesButton() != null && dialog.noButton() != null) {
confirmationYesIndex = widgets.isEmpty() ? 11 : INVENTORY_SIZE - 7;
confirmationNoIndex = widgets.isEmpty() ? 15 : INVENTORY_SIZE - 3;
items[confirmationYesIndex] = createActionButtonItem(connection, dialog.yesButton());
items[confirmationNoIndex] = createActionButtonItem(connection, dialog.noButton());
}
if (dialog.actionButton() != null) {
actionIndex = widgets.isEmpty() ? 13 : INVENTORY_SIZE - 9;
items[actionIndex] = createActionButtonItem(connection, dialog.actionButton());
}
storage.setItems(items, confirmationYesIndex, confirmationNoIndex, actionIndex);
return items;
}
/**
* Handles the 1.21.6 tag inside the Dialog structure and rewrites it to the 1.21.5 format. This is necessary
* for replacing translations used by Dialogs which only exist in 1.21.6+. Call this function for every text component
* you display from the server.
*
* @param connection the user connection
* @param tag the text component as a tag, or null if no tag is present
* @return the rewritten tag, or null if the input tag was null
*/
private @Nullable Tag handleTag(final UserConnection connection, final @Nullable Tag tag) {
if (tag == null) {
return null;
}
protocol.getComponentRewriter().processTag(connection, tag);
return tag;
}
/**
* This doesn't actually exist in Minecraft but is created if multiple {@link TextWidget} follow one another in order
* to improve readability in chest inventories.
*/
public record MultiTextWidget(Tag[] labels) implements Widget {
}
// -------------------------------------------------------------------------------------
protected Item createItem(final String identifier, final Tag name) {
return createItem(identifier, name, new String[0]);
}
protected Item createItem(final String identifier, final Tag name, final Tag... description) {
final int id = protocol.getMappingData().getFullItemMappings().mappedId(identifier);
final StructuredDataContainer data = new StructuredDataContainer();
data.setIdLookup(protocol, true);
data.set(StructuredDataKey.ITEM_NAME, name);
if (description != null) {
data.set(StructuredDataKey.LORE, description);
}
return new StructuredItem(id, 1, data);
}
protected Item createItem(final String identifier, final Tag name, final String... description) {
final int id = protocol.getMappingData().getFullItemMappings().mappedId(identifier);
final StructuredDataContainer data = new StructuredDataContainer();
data.setIdLookup(protocol, true);
data.set(StructuredDataKey.ITEM_NAME, name);
if (description.length > 0) {
final List<Tag> lore = new ArrayList<>();
for (final String s : description) {
lore.add(text(s));
}
data.set(StructuredDataKey.LORE, lore.toArray(new Tag[0]));
}
return new StructuredItem(id, 1, data);
}
}
@@ -0,0 +1,38 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.provider;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.Dialog;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.platform.providers.Provider;
/**
* Interface for providing dialog view functionality to this protocol. Requires a storage
* to save per-user data while being able to have open and close logic static.
* <p>
* See {@link Dialog} for the structure of a dialog.
* <p>
* See {@link ChestDialogViewProvider} for the protocol level emulation using a chest.
*/
public interface DialogViewProvider extends Provider {
void openDialog(final UserConnection connection, final Dialog dialog);
void closeDialog(final UserConnection connection);
}
@@ -20,6 +20,8 @@ package com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.rewriter;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.viabackwards.api.BackwardsProtocol;
import com.viaversion.viabackwards.api.rewriters.text.NBTComponentRewriter;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.storage.ChestDialogStorage;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.storage.ClickEvents;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundPacket1_21_6;
@@ -39,8 +41,32 @@ public final class ComponentRewriter1_21_6 extends NBTComponentRewriter<Clientbo
}
final String action = clickEvent.getString("action");
if ("custom".equals(action)) {
tag.remove("click_event");
final ChestDialogStorage chestDialogStorage = connection.get(ChestDialogStorage.class);
if (chestDialogStorage != null) {
// Short way out - remove when in dialog, buttons are handled separately
if ("show_dialog".equals(action) || "custom".equals(action)) {
tag.remove("click_event");
return;
}
}
// Make them run a command generated by us executing the actual logic
if ("show_dialog".equals(action)) {
final ClickEvents clickEvents = connection.get(ClickEvents.class);
final String command = clickEvents.storeClickEvent(clickEvent.copy());
clickEvent.putString("action", "run_command");
clickEvent.putString("command", command);
clickEvent.remove("dialog");
} else if ("custom".equals(action)) {
final ClickEvents clickEvents = connection.get(ClickEvents.class);
final String command = clickEvents.storeClickEvent(clickEvent.copy());
clickEvent.putString("action", "run_command");
clickEvent.putString("command", command);
clickEvent.remove("id");
clickEvent.remove("payload");
}
}
}
@@ -17,19 +17,27 @@
*/
package com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.rewriter;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter;
import com.viaversion.viabackwards.api.rewriters.EntityRewriter;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.Protocol1_21_6To1_21_5;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.storage.RegistryAndTags;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.RegistryEntry;
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_6;
import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_5;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.version.VersionedTypes;
import com.viaversion.viaversion.libs.fastutil.objects.Object2ObjectArrayMap;
import com.viaversion.viaversion.libs.fastutil.objects.Object2ObjectMap;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ServerboundPackets1_21_5;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundConfigurationPackets1_21_6;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundPacket1_21_6;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundPackets1_21_6;
import com.viaversion.viaversion.rewriter.RegistryDataRewriter;
import com.viaversion.viaversion.util.Key;
import com.viaversion.viaversion.util.KeyMappings;
public final class EntityPacketRewriter1_21_6 extends EntityRewriter<ClientboundPacket1_21_6, Protocol1_21_6To1_21_5> {
@@ -47,8 +55,29 @@ public final class EntityPacketRewriter1_21_6 extends EntityRewriter<Clientbound
registerLogin1_20_5(ClientboundPackets1_21_6.LOGIN);
registerRespawn1_20_5(ClientboundPackets1_21_6.RESPAWN);
final RegistryDataRewriter registryDataRewriter = new BackwardsRegistryRewriter(protocol);
registryDataRewriter.remove("dialog");
final RegistryDataRewriter registryDataRewriter = new BackwardsRegistryRewriter(protocol) {
@Override
public RegistryEntry[] handle(final UserConnection connection, final String key, final RegistryEntry[] entries) {
if (Key.stripMinecraftNamespace(key).equals("dialog")) {
final String[] keys = new String[entries.length];
for (int i = 0; i < entries.length; i++) {
keys[i] = Key.stripMinecraftNamespace(entries[i].key());
}
final Object2ObjectMap<String, CompoundTag> dialogs = new Object2ObjectArrayMap<>();
for (final RegistryEntry entry : entries) {
if (entry.tag() instanceof final CompoundTag tag) {
dialogs.put(Key.stripMinecraftNamespace(entry.key()), tag);
}
}
final RegistryAndTags registryAndTags = connection.get(RegistryAndTags.class);
registryAndTags.storeRegistry(new KeyMappings(keys), dialogs);
}
return super.handle(connection, key, entries);
}
};
registryDataRewriter.remove("dialog"); // Tracked and now removed
protocol.registerClientbound(ClientboundConfigurationPackets1_21_6.REGISTRY_DATA, registryDataRewriter::handle);
protocol.registerServerbound(ServerboundPackets1_21_5.PLAYER_COMMAND, wrapper -> {
@@ -0,0 +1,193 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.storage;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.Protocol1_21_6To1_21_5;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.Dialog;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.input.TextInput;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.provider.ChestDialogViewProvider;
import com.viaversion.viaversion.api.connection.StorableObject;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ClientboundPackets1_21_5;
import java.util.concurrent.atomic.AtomicInteger;
import org.checkerframework.checker.nullness.qual.Nullable;
import static com.viaversion.viabackwards.utils.ChatUtil.text;
import static com.viaversion.viabackwards.utils.ChatUtil.translate;
/**
* Per-user storage for {@link ChestDialogViewProvider}
*/
public final class ChestDialogStorage implements StorableObject {
private static final byte MIN_FAKE_ID = Byte.MAX_VALUE / 2 + 1;
private static final byte MAX_FAKE_ID = Byte.MAX_VALUE - 1;
private static final AtomicInteger FAKE_ID_COUNTER = new AtomicInteger(MIN_FAKE_ID);
private static final Tag[] RESPONSE_BUTTON_LABELS = new Tag[] {
text(""),
translate("gui.waitingForResponse.button.inactive", 4),
translate("gui.waitingForResponse.button.inactive", 3),
translate("gui.waitingForResponse.button.inactive", 2),
translate("gui.waitingForResponse.button.inactive", 1),
translate("gui.back")
};
private final ChestDialogViewProvider provider;
private final Dialog dialog;
private int containerId;
private Dialog previousDialog;
public int page;
private Item[] items;
private int confirmationYesIndex = -1;
private int confirmationNoIndex = -1;
private int actionIndex = -1;
private @Nullable Phase phase;
private int ticksWaitingForResponse = 0;
private boolean closeButtonEnabled;
private Tag closeButtonLabel;
private TextInput currentTextInput;
private boolean allowClosing;
public ChestDialogStorage(final ChestDialogViewProvider provider, final Dialog dialog) {
this.provider = provider;
this.dialog = dialog;
}
public void tick(final UserConnection connection) {
if (phase != Phase.WAITING_FOR_RESPONSE) {
return;
}
final int index = ticksWaitingForResponse++ / 20;
if (index > RESPONSE_BUTTON_LABELS.length - 1) {
return;
}
closeButtonEnabled = index >= 1;
closeButtonLabel = RESPONSE_BUTTON_LABELS[index];
provider.updateDialog(connection, dialog);
}
public Dialog dialog() {
return dialog;
}
public @Nullable Dialog previousDialog() {
return previousDialog;
}
public void setPreviousDialog(final Dialog previousDialog) {
this.previousDialog = previousDialog;
}
public int containerId() {
return containerId;
}
public Item[] items() {
return items;
}
public void setItems(final Item[] items, final int confirmationYesIndex, final int confirmationNoIndex, final int actionIndex) {
this.items = items;
this.confirmationYesIndex = confirmationYesIndex;
this.confirmationNoIndex = confirmationNoIndex;
this.actionIndex = actionIndex;
}
public int confirmationYesIndex() {
return confirmationYesIndex;
}
public int confirmationNoIndex() {
return confirmationNoIndex;
}
public int actionIndex() {
return actionIndex;
}
public @Nullable Phase phase() {
return phase;
}
public boolean closeButtonEnabled() {
return closeButtonEnabled;
}
public Tag closeButtonLabel() {
return closeButtonLabel;
}
public TextInput currentTextInput() {
return currentTextInput;
}
public boolean allowClosing() {
return allowClosing;
}
public void setPhase(final UserConnection connection, final @Nullable Phase phase) {
if (phase == Phase.DIALOG_VIEW || phase == Phase.ANVIL_VIEW) {
if (this.phase != null) {
final PacketWrapper containerClose = PacketWrapper.create(ClientboundPackets1_21_5.CONTAINER_CLOSE, connection);
containerClose.write(Types.VAR_INT, containerId);
containerClose.send(Protocol1_21_6To1_21_5.class);
}
currentTextInput = null;
final int id = FAKE_ID_COUNTER.getAndIncrement();
if (id > MAX_FAKE_ID) {
FAKE_ID_COUNTER.set(MIN_FAKE_ID);
containerId = MIN_FAKE_ID;
} else {
containerId = (byte) id;
}
}
this.phase = phase;
}
public void setCurrentTextInput(final TextInput currentTextInput) {
this.currentTextInput = currentTextInput;
}
public void setAllowClosing(final boolean allowClosing) {
this.allowClosing = allowClosing;
}
public enum Phase {
DIALOG_VIEW,
ANVIL_VIEW,
WAITING_FOR_RESPONSE
}
}
@@ -0,0 +1,87 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.storage;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.StringTag;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.Protocol1_21_6To1_21_5;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.Dialog;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.provider.DialogViewProvider;
import com.viaversion.viaversion.api.Via;
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.libs.fastutil.objects.Object2ObjectArrayMap;
import com.viaversion.viaversion.libs.fastutil.objects.Object2ObjectMap;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPackets1_21_6;
import com.viaversion.viaversion.util.Key;
import java.util.UUID;
public final class ClickEvents implements StorableObject {
private final Object2ObjectMap<String, CompoundTag> clickEvents = new Object2ObjectArrayMap<>();
public String storeClickEvent(final CompoundTag clickEvent) {
final String id = "vv_" + UUID.randomUUID();
if (clickEvents.containsKey(id)) {
return storeClickEvent(clickEvent); // Ensure unique ID
}
clickEvents.put(id, clickEvent);
return id;
}
public boolean handleChatCommand(final UserConnection connection, final String command) {
final CompoundTag clickEvent = clickEvents.get(command);
if (clickEvent != null) {
handleClickEvent(connection, clickEvent);
return true;
} else {
return false;
}
}
public static void handleClickEvent(final UserConnection connection, final CompoundTag clickEvent) {
final String action = Key.stripMinecraftNamespace(clickEvent.getString("action"));
if ("show_dialog".equals(action)) {
final RegistryAndTags registryAndTags = connection.get(RegistryAndTags.class);
final ServerLinks serverLinks = connection.get(ServerLinks.class);
CompoundTag dialogTag = clickEvent.getCompoundTag("dialog");
if (dialogTag == null) {
final StringTag dialogReferenceTag = clickEvent.getStringTag("dialog");
if (dialogReferenceTag != null) { // No tags here
dialogTag = registryAndTags.fromRegistry(Key.stripMinecraftNamespace(dialogReferenceTag.getValue()));
}
}
final DialogViewProvider provider = Via.getManager().getProviders().get(DialogViewProvider.class);
provider.openDialog(connection, new Dialog(registryAndTags, serverLinks, dialogTag));
} else if ("custom".equals(action)) {
final String id = clickEvent.getString("id");
final CompoundTag payload = clickEvent.getCompoundTag("payload");
final PacketWrapper customClickAction = PacketWrapper.create(ServerboundPackets1_21_6.CUSTOM_CLICK_ACTION, connection);
customClickAction.write(Types.STRING, id);
customClickAction.write(Types.CUSTOM_CLICK_ACTION_TAG, payload);
customClickAction.sendToServer(Protocol1_21_6To1_21_5.class);
}
}
}
@@ -0,0 +1,74 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.storage;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.viaversion.api.connection.StorableObject;
import com.viaversion.viaversion.libs.fastutil.objects.Object2ObjectArrayMap;
import com.viaversion.viaversion.libs.fastutil.objects.Object2ObjectMap;
import com.viaversion.viaversion.util.Key;
import com.viaversion.viaversion.util.KeyMappings;
public final class RegistryAndTags implements StorableObject {
private KeyMappings dialogMappings;
private Object2ObjectMap<String, CompoundTag> dialogs;
private Object2ObjectMap<String, int[]> dialogTags;
public void storeRegistry(final KeyMappings dialogMappings, final Object2ObjectMap<String, CompoundTag> dialogs) {
this.dialogMappings = dialogMappings;
this.dialogs = dialogs;
}
public CompoundTag fromRegistry(final int id) {
return dialogs.get(dialogMappings.idToKey(id));
}
public CompoundTag fromRegistry(final String key) {
return dialogs.get(Key.stripMinecraftNamespace(key));
}
public boolean tagsSent() {
return dialogTags != null && !dialogTags.isEmpty();
}
public void storeTags(final String key, final int[] ids) {
if (dialogTags == null) {
dialogTags = new Object2ObjectArrayMap<>();
}
dialogTags.put(Key.stripMinecraftNamespace(key), ids);
}
public int[] fromKey(final String key) {
return dialogTags.get(Key.stripMinecraftNamespace(key));
}
public CompoundTag[] fromRegistryKey(final String key) {
final int[] ids = fromKey(key);
if (ids == null) {
return null;
}
final CompoundTag[] tags = new CompoundTag[ids.length];
for (int i = 0; i < ids.length; i++) {
tags[i] = fromRegistry(ids[i]);
}
return tags;
}
}
@@ -0,0 +1,65 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.storage;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viaversion.api.connection.StorableObject;
import com.viaversion.viaversion.libs.fastutil.objects.Object2ObjectArrayMap;
import com.viaversion.viaversion.libs.fastutil.objects.Object2ObjectMap;
import static com.viaversion.viabackwards.utils.ChatUtil.translate;
public final class ServerLinks implements StorableObject {
private static final CompoundTag REPORT_BUG = translate("known_server_link.report_bug");
private static final CompoundTag COMMUNITY_GUIDELINES = translate("known_server_link.community_guidelines");
private static final CompoundTag SUPPORT = translate("known_server_link.support");
private static final CompoundTag STATUS = translate("known_server_link.status");
private static final CompoundTag FEEDBACK = translate("known_server_link.feedback");
private static final CompoundTag COMMUNITY = translate("known_server_link.community");
private static final CompoundTag WEBSITE = translate("known_server_link.website");
private static final CompoundTag FORUMS = translate("known_server_link.forums");
private static final CompoundTag NEWS = translate("known_server_link.news");
private static final CompoundTag ANNOUNCEMENTS = translate("known_server_link.announcements");
private final Object2ObjectMap<Tag, String> links = new Object2ObjectArrayMap<>();
public void storeLink(final Tag tag, final String uri) {
links.put(tag, uri);
}
public void storeLink(final int id, final String uri) {
switch (id) {
case 1 -> storeLink(COMMUNITY_GUIDELINES, uri);
case 2 -> storeLink(SUPPORT, uri);
case 3 -> storeLink(STATUS, uri);
case 4 -> storeLink(FEEDBACK, uri);
case 5 -> storeLink(COMMUNITY, uri);
case 6 -> storeLink(WEBSITE, uri);
case 7 -> storeLink(FORUMS, uri);
case 8 -> storeLink(NEWS, uri);
case 9 -> storeLink(ANNOUNCEMENTS, uri);
default -> storeLink(REPORT_BUG, uri);
}
}
public Object2ObjectMap<Tag, String> links() {
return links;
}
}
@@ -0,0 +1,38 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_6to1_21_5.task;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.Protocol1_21_6To1_21_5;
import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.storage.ChestDialogStorage;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.protocol.ProtocolRunnable;
public final class ChestDialogViewTask extends ProtocolRunnable {
public ChestDialogViewTask() {
super(Protocol1_21_6To1_21_5.class);
}
@Override
public void run(final UserConnection connection) {
final ChestDialogStorage storage = connection.get(ChestDialogStorage.class);
if (storage != null) {
storage.tick(connection);
}
}
}
@@ -0,0 +1,150 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_7to1_21_6;
import com.viaversion.viabackwards.api.BackwardsProtocol;
import com.viaversion.viabackwards.api.data.BackwardsMappingData;
import com.viaversion.viabackwards.api.rewriters.SoundRewriter;
import com.viaversion.viabackwards.api.rewriters.text.NBTComponentRewriter;
import com.viaversion.viabackwards.api.rewriters.text.TranslatableRewriter;
import com.viaversion.viabackwards.protocol.v1_21_7to1_21_6.rewriter.BlockItemPacketRewriter1_21_7;
import com.viaversion.viabackwards.protocol.v1_21_7to1_21_6.rewriter.EntityPacketRewriter1_21_7;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_21_5;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_6;
import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_5;
import com.viaversion.viaversion.api.protocol.packet.provider.PacketTypesProvider;
import com.viaversion.viaversion.api.protocol.packet.provider.SimplePacketTypesProvider;
import com.viaversion.viaversion.api.type.types.version.Types1_20_5;
import com.viaversion.viaversion.api.type.types.version.VersionedTypes;
import com.viaversion.viaversion.data.entity.EntityTrackerBase;
import com.viaversion.viaversion.data.item.ItemHasherBase;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundConfigurationPackets1_21_6;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundPacket1_21_6;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundPackets1_21_6;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundConfigurationPackets1_21_6;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPacket1_21_6;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPackets1_21_6;
import com.viaversion.viaversion.protocols.v1_21_6to1_21_7.Protocol1_21_6To1_21_7;
import com.viaversion.viaversion.rewriter.ParticleRewriter;
import com.viaversion.viaversion.rewriter.StatisticsRewriter;
import com.viaversion.viaversion.rewriter.TagRewriter;
import com.viaversion.viaversion.util.SerializerVersion;
import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap;
public final class Protocol1_21_7To1_21_6 extends BackwardsProtocol<ClientboundPacket1_21_6, ClientboundPacket1_21_6, ServerboundPacket1_21_6, ServerboundPacket1_21_6> {
public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.21.7", "1.21.6", Protocol1_21_6To1_21_7.class);
private final EntityPacketRewriter1_21_7 entityRewriter = new EntityPacketRewriter1_21_7(this);
private final BlockItemPacketRewriter1_21_7 itemRewriter = new BlockItemPacketRewriter1_21_7(this);
private final ParticleRewriter<ClientboundPacket1_21_6> particleRewriter = new ParticleRewriter<>(this);
private final NBTComponentRewriter<ClientboundPacket1_21_6> translatableRewriter = new NBTComponentRewriter<>(this);
private final TagRewriter<ClientboundPacket1_21_6> tagRewriter = new TagRewriter<>(this);
public Protocol1_21_7To1_21_6() {
super(ClientboundPacket1_21_6.class, ClientboundPacket1_21_6.class, ServerboundPacket1_21_6.class, ServerboundPacket1_21_6.class);
}
@Override
protected void registerPackets() {
super.registerPackets();
tagRewriter.registerGeneric(ClientboundPackets1_21_6.UPDATE_TAGS);
tagRewriter.registerGeneric(ClientboundConfigurationPackets1_21_6.UPDATE_TAGS);
final SoundRewriter<ClientboundPacket1_21_6> soundRewriter = new SoundRewriter<>(this);
soundRewriter.registerSound1_19_3(ClientboundPackets1_21_6.SOUND);
soundRewriter.registerSound1_19_3(ClientboundPackets1_21_6.SOUND_ENTITY);
soundRewriter.registerStopSound(ClientboundPackets1_21_6.STOP_SOUND);
new StatisticsRewriter<>(this).register(ClientboundPackets1_21_6.AWARD_STATS);
translatableRewriter.registerComponentPacket(ClientboundPackets1_21_6.SET_ACTION_BAR_TEXT);
translatableRewriter.registerComponentPacket(ClientboundPackets1_21_6.SET_TITLE_TEXT);
translatableRewriter.registerComponentPacket(ClientboundPackets1_21_6.SET_SUBTITLE_TEXT);
translatableRewriter.registerBossEvent(ClientboundPackets1_21_6.BOSS_EVENT);
translatableRewriter.registerComponentPacket(ClientboundPackets1_21_6.DISCONNECT);
translatableRewriter.registerTabList(ClientboundPackets1_21_6.TAB_LIST);
translatableRewriter.registerPlayerCombatKill1_20(ClientboundPackets1_21_6.PLAYER_COMBAT_KILL);
translatableRewriter.registerPlayerInfoUpdate1_21_4(ClientboundPackets1_21_6.PLAYER_INFO_UPDATE);
translatableRewriter.registerComponentPacket(ClientboundPackets1_21_6.SYSTEM_CHAT);
translatableRewriter.registerDisguisedChat(ClientboundPackets1_21_6.DISGUISED_CHAT);
translatableRewriter.registerPlayerChat1_21_5(ClientboundPackets1_21_6.PLAYER_CHAT);
translatableRewriter.registerPing();
particleRewriter.registerLevelParticles1_21_4(ClientboundPackets1_21_6.LEVEL_PARTICLES);
particleRewriter.registerExplode1_21_2(ClientboundPackets1_21_6.EXPLODE);
}
@Override
public void init(final UserConnection connection) {
addEntityTracker(connection, new EntityTrackerBase(connection, EntityTypes1_21_6.PLAYER));
addItemHasher(connection, new ItemHasherBase(this, connection, SerializerVersion.V1_21_6, SerializerVersion.V1_21_6));
}
@Override
public BackwardsMappingData getMappingData() {
return MAPPINGS;
}
@Override
public Types1_20_5<StructuredDataKeys1_21_5, EntityDataTypes1_21_5> types() {
return VersionedTypes.V1_21_6;
}
@Override
public Types1_20_5<StructuredDataKeys1_21_5, EntityDataTypes1_21_5> mappedTypes() {
return VersionedTypes.V1_21_6;
}
@Override
public TranslatableRewriter getComponentRewriter() {
return translatableRewriter;
}
@Override
public EntityPacketRewriter1_21_7 getEntityRewriter() {
return entityRewriter;
}
@Override
public BlockItemPacketRewriter1_21_7 getItemRewriter() {
return itemRewriter;
}
@Override
public ParticleRewriter<ClientboundPacket1_21_6> getParticleRewriter() {
return particleRewriter;
}
@Override
public TagRewriter<ClientboundPacket1_21_6> getTagRewriter() {
return tagRewriter;
}
@Override
protected PacketTypesProvider<ClientboundPacket1_21_6, ClientboundPacket1_21_6, ServerboundPacket1_21_6, ServerboundPacket1_21_6> createPacketTypesProvider() {
return new SimplePacketTypesProvider<>(
packetTypeMap(unmappedClientboundPacketType, ClientboundPackets1_21_6.class, ClientboundConfigurationPackets1_21_6.class),
packetTypeMap(mappedClientboundPacketType, ClientboundPackets1_21_6.class, ClientboundConfigurationPackets1_21_6.class),
packetTypeMap(mappedServerboundPacketType, ServerboundPackets1_21_6.class, ServerboundConfigurationPackets1_21_6.class),
packetTypeMap(unmappedServerboundPacketType, ServerboundPackets1_21_6.class, ServerboundConfigurationPackets1_21_6.class)
);
}
}
@@ -0,0 +1,63 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_7to1_21_6.rewriter;
import com.viaversion.viabackwards.api.rewriters.BackwardsStructuredItemRewriter;
import com.viaversion.viabackwards.protocol.v1_21_7to1_21_6.Protocol1_21_7To1_21_6;
import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_21_5;
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.ClientboundPacket1_21_6;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundPackets1_21_6;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPacket1_21_6;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPackets1_21_6;
import com.viaversion.viaversion.rewriter.BlockRewriter;
import com.viaversion.viaversion.rewriter.RecipeDisplayRewriter;
public final class BlockItemPacketRewriter1_21_7 extends BackwardsStructuredItemRewriter<ClientboundPacket1_21_6, ServerboundPacket1_21_6, Protocol1_21_7To1_21_6> {
public BlockItemPacketRewriter1_21_7(final Protocol1_21_7To1_21_6 protocol) {
super(protocol);
}
@Override
public void registerPackets() {
final BlockRewriter<ClientboundPacket1_21_6> blockRewriter = BlockRewriter.for1_20_2(protocol);
blockRewriter.registerBlockEvent(ClientboundPackets1_21_6.BLOCK_EVENT);
blockRewriter.registerBlockUpdate(ClientboundPackets1_21_6.BLOCK_UPDATE);
blockRewriter.registerSectionBlocksUpdate1_20(ClientboundPackets1_21_6.SECTION_BLOCKS_UPDATE);
blockRewriter.registerLevelEvent1_21(ClientboundPackets1_21_6.LEVEL_EVENT, 2001);
blockRewriter.registerLevelChunk1_19(ClientboundPackets1_21_6.LEVEL_CHUNK_WITH_LIGHT, ChunkType1_21_5::new);
blockRewriter.registerBlockEntityData(ClientboundPackets1_21_6.BLOCK_ENTITY_DATA);
registerSetCursorItem(ClientboundPackets1_21_6.SET_CURSOR_ITEM);
registerSetPlayerInventory(ClientboundPackets1_21_6.SET_PLAYER_INVENTORY);
registerCooldown1_21_2(ClientboundPackets1_21_6.COOLDOWN);
registerSetContent1_21_2(ClientboundPackets1_21_6.CONTAINER_SET_CONTENT);
registerSetSlot1_21_2(ClientboundPackets1_21_6.CONTAINER_SET_SLOT);
registerAdvancements1_20_3(ClientboundPackets1_21_6.UPDATE_ADVANCEMENTS);
registerSetEquipment(ClientboundPackets1_21_6.SET_EQUIPMENT);
registerMerchantOffers1_20_5(ClientboundPackets1_21_6.MERCHANT_OFFERS);
registerContainerClick1_21_5(ServerboundPackets1_21_6.CONTAINER_CLICK);
registerSetCreativeModeSlot1_21_5(ServerboundPackets1_21_6.SET_CREATIVE_MODE_SLOT);
final RecipeDisplayRewriter<ClientboundPacket1_21_6> recipeRewriter = new RecipeDisplayRewriter1_21_5<>(protocol);
recipeRewriter.registerUpdateRecipes(ClientboundPackets1_21_6.UPDATE_RECIPES);
recipeRewriter.registerRecipeBookAdd(ClientboundPackets1_21_6.RECIPE_BOOK_ADD);
recipeRewriter.registerPlaceGhostRecipe(ClientboundPackets1_21_6.PLACE_GHOST_RECIPE);
}
}
@@ -0,0 +1,71 @@
/*
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
* Copyright (C) 2016-2025 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_21_7to1_21_6.rewriter;
import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter;
import com.viaversion.viabackwards.api.rewriters.EntityRewriter;
import com.viaversion.viabackwards.protocol.v1_21_7to1_21_6.Protocol1_21_7To1_21_6;
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_4;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_6;
import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_5;
import com.viaversion.viaversion.api.type.types.version.VersionedTypes;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundConfigurationPackets1_21_6;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundPacket1_21_6;
import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundPackets1_21_6;
import com.viaversion.viaversion.rewriter.RegistryDataRewriter;
public final class EntityPacketRewriter1_21_7 extends EntityRewriter<ClientboundPacket1_21_6, Protocol1_21_7To1_21_6> {
public EntityPacketRewriter1_21_7(final Protocol1_21_7To1_21_6 protocol) {
super(protocol, VersionedTypes.V1_21_6.entityDataTypes.optionalComponentType, VersionedTypes.V1_21_6.entityDataTypes.booleanType);
}
@Override
public void registerPackets() {
registerTrackerWithData1_19(ClientboundPackets1_21_6.ADD_ENTITY, EntityTypes1_21_4.FALLING_BLOCK);
registerSetEntityData(ClientboundPackets1_21_6.SET_ENTITY_DATA);
registerRemoveEntities(ClientboundPackets1_21_6.REMOVE_ENTITIES);
registerPlayerAbilities(ClientboundPackets1_21_6.PLAYER_ABILITIES);
registerGameEvent(ClientboundPackets1_21_6.GAME_EVENT);
registerLogin1_20_5(ClientboundPackets1_21_6.LOGIN);
registerRespawn1_20_5(ClientboundPackets1_21_6.RESPAWN);
final RegistryDataRewriter registryDataRewriter = new BackwardsRegistryRewriter(protocol);
protocol.registerClientbound(ClientboundConfigurationPackets1_21_6.REGISTRY_DATA, registryDataRewriter::handle);
}
@Override
protected void registerRewrites() {
final EntityDataTypes1_21_5 mappedEntityDataTypes = VersionedTypes.V1_21_6.entityDataTypes;
registerEntityDataTypeHandler1_20_3(
mappedEntityDataTypes.itemType,
mappedEntityDataTypes.blockStateType,
mappedEntityDataTypes.optionalBlockStateType,
mappedEntityDataTypes.particleType,
mappedEntityDataTypes.particlesType,
mappedEntityDataTypes.componentType,
mappedEntityDataTypes.optionalComponentType
);
}
@Override
public EntityType typeFromId(final int type) {
return EntityTypes1_21_6.getTypeFromId(type);
}
}
@@ -17,13 +17,18 @@
*/
package com.viaversion.viabackwards.utils;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viaversion.libs.mcstructs.text.Style;
import com.viaversion.viaversion.libs.mcstructs.text.TextComponent;
import com.viaversion.viaversion.libs.mcstructs.text.TextFormatting;
import com.viaversion.viaversion.libs.mcstructs.text.components.TranslationComponent;
import com.viaversion.viaversion.libs.mcstructs.text.stringformat.StringFormat;
import com.viaversion.viaversion.libs.mcstructs.text.stringformat.handling.ColorHandling;
import com.viaversion.viaversion.libs.mcstructs.text.stringformat.handling.DeserializerUnknownHandling;
import com.viaversion.viaversion.libs.mcstructs.text.utils.TextUtils;
import com.viaversion.viaversion.util.SerializerVersion;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@@ -34,6 +39,49 @@ public final class ChatUtil {
private static final Pattern UNUSED_COLOR_PATTERN = Pattern.compile("(?>(?>§[0-fk-or])*(§r|\\Z))|(?>(?>§[0-f])*(§[0-f]))");
private static final Pattern UNUSED_COLOR_PATTERN_PREFIX = Pattern.compile("(?>(?>§[0-fk-or])*(§r))|(?>(?>§[0-f])*(§[0-f]))");
// 1.21.6 methods mainly for dialog screens
public static Tag translate(final String key, final Tag... arguments) {
// Same as below but converting "our" text components to MCStructs components
final TextComponent component = new TranslationComponent(key, Arrays.stream(arguments).map(SerializerVersion.V1_21_6::toComponent).toArray());
return SerializerVersion.V1_21_6.toTag(component);
}
public static Tag translate(final String key, final Object... arguments) {
final TextComponent component = new TranslationComponent(key, arguments);
return SerializerVersion.V1_21_6.toTag(component);
}
public static Tag[] split(final Tag tag, final String delimiter) {
final TextComponent component = SerializerVersion.V1_21_6.toComponent(tag);
return Arrays.stream(TextUtils.split(component, delimiter, false)).map(SerializerVersion.V1_21_6::toTag).toArray(Tag[]::new);
}
public static Tag fixStyle(final Tag tag) {
final TextComponent component = SerializerVersion.V1_21_6.toComponent(tag);
if (component.getStyle().getColor() == null) {
component.getStyle().setFormatting(TextFormatting.WHITE);
}
component.getStyle().setItalic(false);
return SerializerVersion.V1_21_6.toTag(component);
}
public static CompoundTag translate(final String key) {
final CompoundTag tag = new CompoundTag();
tag.putString("translate", key);
return tag;
}
public static CompoundTag text(final String text) {
final CompoundTag tag = new CompoundTag();
tag.putString("text", text);
tag.putString("color", "white");
tag.putBoolean("italic", false);
return tag;
}
// Sub 1.12 methods
public static String removeUnusedColor(String legacy, char defaultColor) {
return removeUnusedColor(legacy, defaultColor, false);
}
@@ -1,4 +1,9 @@
{
"1.21.7": {
"item.minecraft.music_disc_lava_chicken.desc": "Hyper Potions - Lava Chicken",
"jukebox_song.minecraft.lava_chicken": "Hyper Potions - Lava Chicken",
"painting.minecraft.dennis.title": "Dennis"
},
"1.21.6": {
"advancements.adventure.heart_transplanter.description": "Place a Creaking Heart with the correct alignment between two Pale Oak Log blocks",
"advancements.adventure.heart_transplanter.title": "Heart Transplanter",
+2 -2
View File
@@ -1,7 +1,7 @@
projectVersion=5.4.0
projectVersion=5.4.1
# Smile emoji
mcVersions=1.21.6, 1.21.5, 1.21.4, 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
mcVersions=1.21.7, 1.21.6, 1.21.5, 1.21.4, 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
mcVersionRange=1.10-1.21.6
velocityVersion=3.4
+2 -2
View File
@@ -3,7 +3,7 @@ metadata.format.version = "1.1"
[versions]
# ViaVersion
viaver = "5.4.0"
viaver = "5.4.1-mc1.21.7-rc2-SNAPSHOT"
# Common provided
netty = "4.0.20.Final"
@@ -16,7 +16,7 @@ checkerQual = "3.49.1"
paper = "1.16.5-R0.1-SNAPSHOT"
velocity = "3.1.1"
fabricLoader = "0.11.6"
viaProxy = "3.4.2-SNAPSHOT"
viaProxy = "3.4.3-SNAPSHOT"
[libraries]