Repository: ViaVersion/ViaBackwards Branch: master Commit: 63afb2d06302 Files: 377 Total size: 2.1 MB Directory structure: gitextract_ph8u3uww/ ├── .editorconfig ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ └── feature_request.yml │ ├── dependabot.yml │ └── workflows/ │ ├── build.yml │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── SECURITY.md ├── build-logic/ │ ├── build.gradle.kts │ └── src/ │ └── main/ │ └── kotlin/ │ ├── extensions.kt │ ├── vb.base-conventions.gradle.kts │ ├── vb.build-logic.gradle.kts │ └── vb.shadow-conventions.gradle.kts ├── build.gradle.kts ├── bukkit/ │ ├── build.gradle.kts │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── viaversion/ │ │ └── viabackwards/ │ │ ├── BukkitPlugin.java │ │ ├── listener/ │ │ │ ├── DurabilitySync1_11.java │ │ │ ├── FireExtinguish1_16.java │ │ │ ├── ItemDropSync1_17.java │ │ │ ├── LecternInteract1_14.java │ │ │ ├── PlayerHurtSound1_12.java │ │ │ └── SpearAttack1_21_11.java │ │ └── provider/ │ │ └── BukkitAdvancementCriteriaProvider.java │ └── resources/ │ └── plugin.yml ├── common/ │ ├── build.gradle.kts │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── viaversion/ │ │ └── viabackwards/ │ │ ├── ViaBackwards.java │ │ ├── ViaBackwardsConfig.java │ │ ├── ViaBackwardsPlatformImpl.java │ │ ├── api/ │ │ │ ├── BackwardsProtocol.java │ │ │ ├── DialogStyleConfig.java │ │ │ ├── ViaBackwardsConfig.java │ │ │ ├── ViaBackwardsPlatform.java │ │ │ ├── data/ │ │ │ │ ├── BackwardsMappingData.java │ │ │ │ ├── BackwardsMappingDataLoader.java │ │ │ │ ├── ItemMappings.java │ │ │ │ ├── MappedItem.java │ │ │ │ ├── MappedLegacyBlockItem.java │ │ │ │ └── TranslatableMappings.java │ │ │ ├── entities/ │ │ │ │ └── storage/ │ │ │ │ ├── EntityObjectData.java │ │ │ │ ├── EntityPositionHandler.java │ │ │ │ ├── EntityPositionStorage.java │ │ │ │ ├── EntityReplacement.java │ │ │ │ ├── PlayerPositionStorage.java │ │ │ │ └── WrappedEntityData.java │ │ │ └── rewriters/ │ │ │ ├── BackwardsItemRewriter.java │ │ │ ├── BackwardsItemRewriterBase.java │ │ │ ├── BackwardsRegistryRewriter.java │ │ │ ├── BackwardsStructuredItemRewriter.java │ │ │ ├── EnchantmentRewriter.java │ │ │ ├── EntityRewriter.java │ │ │ ├── EntityRewriterBase.java │ │ │ ├── LegacyBlockItemRewriter.java │ │ │ ├── LegacyEnchantmentRewriter.java │ │ │ ├── LegacyEntityRewriter.java │ │ │ ├── LegacySoundRewriter.java │ │ │ ├── MapColorRewriter.java │ │ │ ├── SoundRewriter.java │ │ │ ├── StructuredEnchantmentRewriter.java │ │ │ └── text/ │ │ │ ├── JsonNBTComponentRewriter.java │ │ │ ├── NBTComponentRewriter.java │ │ │ └── TranslatableRewriter.java │ │ ├── item/ │ │ │ └── DataItemWithExtras.java │ │ ├── protocol/ │ │ │ ├── registration/ │ │ │ │ ├── BackwardsRegistrations.java │ │ │ │ └── RegistryRegistrations.java │ │ │ ├── template/ │ │ │ │ ├── BlockItemPacketRewriter99_1.java │ │ │ │ ├── ComponentRewriter99_1.java │ │ │ │ ├── EntityPacketRewriter99_1.java │ │ │ │ └── Protocol99_1To98_1.java │ │ │ ├── v1_10to1_9_3/ │ │ │ │ ├── Protocol1_10To1_9_3.java │ │ │ │ └── rewriter/ │ │ │ │ ├── BlockItemPacketRewriter1_10.java │ │ │ │ └── EntityPacketRewriter1_10.java │ │ │ ├── v1_11_1to1_11/ │ │ │ │ ├── Protocol1_11_1To1_11.java │ │ │ │ └── rewriter/ │ │ │ │ ├── EntityPacketRewriter1_11_1.java │ │ │ │ └── ItemPacketRewriter1_11_1.java │ │ │ ├── v1_11to1_10/ │ │ │ │ ├── Protocol1_11To1_10.java │ │ │ │ ├── data/ │ │ │ │ │ └── SplashPotionMappings1_10.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_11.java │ │ │ │ │ ├── EntityPacketRewriter1_11.java │ │ │ │ │ └── PlayerPacketRewriter1_11.java │ │ │ │ └── storage/ │ │ │ │ ├── ChestedHorseStorage.java │ │ │ │ └── WindowTracker.java │ │ │ ├── v1_12_1to1_12/ │ │ │ │ └── Protocol1_12_1To1_12.java │ │ │ ├── v1_12_2to1_12_1/ │ │ │ │ ├── Protocol1_12_2To1_12_1.java │ │ │ │ └── storage/ │ │ │ │ └── KeepAliveTracker.java │ │ │ ├── v1_12to1_11_1/ │ │ │ │ ├── Protocol1_12To1_11_1.java │ │ │ │ ├── data/ │ │ │ │ │ ├── BlockColors1_11_1.java │ │ │ │ │ └── MapColorMappings1_11_1.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_12.java │ │ │ │ │ ├── ComponentRewriter1_12.java │ │ │ │ │ ├── EntityPacketRewriter1_12.java │ │ │ │ │ └── SoundPacketRewriter1_12.java │ │ │ │ └── storage/ │ │ │ │ ├── ParrotStorage.java │ │ │ │ └── ShoulderTracker.java │ │ │ ├── v1_13_1to1_13/ │ │ │ │ ├── Protocol1_13_1To1_13.java │ │ │ │ └── rewriter/ │ │ │ │ ├── CommandRewriter1_13_1.java │ │ │ │ ├── EntityPacketRewriter1_13_1.java │ │ │ │ ├── ItemPacketRewriter1_13_1.java │ │ │ │ └── WorldPacketRewriter1_13_1.java │ │ │ ├── v1_13_2to1_13_1/ │ │ │ │ ├── Protocol1_13_2To1_13_1.java │ │ │ │ └── rewriter/ │ │ │ │ ├── EntityPacketRewriter1_13_2.java │ │ │ │ ├── ItemPacketRewriter1_13_2.java │ │ │ │ └── WorldPacketRewriter1_13_2.java │ │ │ ├── v1_13to1_12_2/ │ │ │ │ ├── Protocol1_13To1_12_2.java │ │ │ │ ├── block_entity_handlers/ │ │ │ │ │ ├── BannerHandler.java │ │ │ │ │ ├── BedHandler.java │ │ │ │ │ ├── FlowerPotHandler.java │ │ │ │ │ ├── PistonHandler.java │ │ │ │ │ ├── SkullHandler.java │ │ │ │ │ └── SpawnerHandler.java │ │ │ │ ├── data/ │ │ │ │ │ ├── BackwardsMappingData1_13.java │ │ │ │ │ ├── EntityIdMappings1_12_2.java │ │ │ │ │ ├── EntityNameMappings1_12_2.java │ │ │ │ │ ├── NamedSoundMappings1_12_2.java │ │ │ │ │ ├── PaintingNames1_13.java │ │ │ │ │ └── ParticleIdMappings1_12_2.java │ │ │ │ ├── provider/ │ │ │ │ │ └── BackwardsBlockEntityProvider.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_13.java │ │ │ │ │ ├── EntityPacketRewriter1_13.java │ │ │ │ │ ├── PlayerPacketRewriter1_13.java │ │ │ │ │ └── SoundPacketRewriter1_13.java │ │ │ │ └── storage/ │ │ │ │ ├── BackwardsBlockStorage.java │ │ │ │ ├── NoteBlockStorage.java │ │ │ │ ├── PlayerPositionStorage1_13.java │ │ │ │ └── TabCompleteStorage.java │ │ │ ├── v1_14_1to1_14/ │ │ │ │ ├── Protocol1_14_1To1_14.java │ │ │ │ └── rewriter/ │ │ │ │ └── EntityPacketRewriter1_14_1.java │ │ │ ├── v1_14_2to1_14_1/ │ │ │ │ └── Protocol1_14_2To1_14_1.java │ │ │ ├── v1_14_3to1_14_2/ │ │ │ │ └── Protocol1_14_3To1_14_2.java │ │ │ ├── v1_14_4to1_14_3/ │ │ │ │ └── Protocol1_14_4To1_14_3.java │ │ │ ├── v1_14to1_13_2/ │ │ │ │ ├── Protocol1_14To1_13_2.java │ │ │ │ ├── data/ │ │ │ │ │ └── BackwardsMappingData1_14.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_14.java │ │ │ │ │ ├── CommandRewriter1_14.java │ │ │ │ │ ├── EntityPacketRewriter1_14.java │ │ │ │ │ ├── PlayerPacketRewriter1_14.java │ │ │ │ │ └── SoundPacketRewriter1_14.java │ │ │ │ └── storage/ │ │ │ │ ├── ChunkLightStorage.java │ │ │ │ ├── DifficultyStorage.java │ │ │ │ └── EntityPositionStorage1_14.java │ │ │ ├── v1_15_1to1_15/ │ │ │ │ └── Protocol1_15_1To1_15.java │ │ │ ├── v1_15_2to1_15_1/ │ │ │ │ └── Protocol1_15_2To1_15_1.java │ │ │ ├── v1_15to1_14_4/ │ │ │ │ ├── Protocol1_15To1_14_4.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_15.java │ │ │ │ │ └── EntityPacketRewriter1_15.java │ │ │ │ └── storage/ │ │ │ │ └── ImmediateRespawnStorage.java │ │ │ ├── v1_16_1to1_16/ │ │ │ │ └── Protocol1_16_1To1_16.java │ │ │ ├── v1_16_2to1_16_1/ │ │ │ │ ├── Protocol1_16_2To1_16_1.java │ │ │ │ ├── data/ │ │ │ │ │ └── BiomeMappings1_16_1.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_16_2.java │ │ │ │ │ ├── CommandRewriter1_16_2.java │ │ │ │ │ └── EntityPacketRewriter1_16_2.java │ │ │ │ └── storage/ │ │ │ │ └── BiomeStorage.java │ │ │ ├── v1_16_3to1_16_2/ │ │ │ │ └── Protocol1_16_3To1_16_2.java │ │ │ ├── v1_16_4to1_16_3/ │ │ │ │ ├── Protocol1_16_4To1_16_3.java │ │ │ │ └── storage/ │ │ │ │ └── PlayerHandStorage.java │ │ │ ├── v1_16to1_15_2/ │ │ │ │ ├── Protocol1_16To1_15_2.java │ │ │ │ ├── data/ │ │ │ │ │ ├── BackwardsMappingData1_16.java │ │ │ │ │ └── MapColorMappings1_15_2.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_16.java │ │ │ │ │ ├── CommandRewriter1_16.java │ │ │ │ │ ├── EntityPacketRewriter1_16.java │ │ │ │ │ └── TranslatableRewriter1_16.java │ │ │ │ └── storage/ │ │ │ │ ├── PlayerAttributesStorage.java │ │ │ │ ├── PlayerSneakStorage.java │ │ │ │ ├── WolfDataMaskStorage.java │ │ │ │ └── WorldNameTracker.java │ │ │ ├── v1_17_1to1_17/ │ │ │ │ ├── Protocol1_17_1To1_17.java │ │ │ │ └── storage/ │ │ │ │ └── InventoryStateIds.java │ │ │ ├── v1_17to1_16_4/ │ │ │ │ ├── Protocol1_17To1_16_4.java │ │ │ │ ├── data/ │ │ │ │ │ └── MapColorMappings1_16_4.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_17.java │ │ │ │ │ └── EntityPacketRewriter1_17.java │ │ │ │ └── storage/ │ │ │ │ └── PlayerLastCursorItem.java │ │ │ ├── v1_18_2to1_18/ │ │ │ │ ├── Protocol1_18_2To1_18.java │ │ │ │ └── rewriter/ │ │ │ │ └── CommandRewriter1_18_2.java │ │ │ ├── v1_18to1_17_1/ │ │ │ │ ├── Protocol1_18To1_17_1.java │ │ │ │ ├── data/ │ │ │ │ │ ├── BackwardsMappingData1_18.java │ │ │ │ │ └── BlockEntityMappings1_17_1.java │ │ │ │ └── rewriter/ │ │ │ │ ├── BlockItemPacketRewriter1_18.java │ │ │ │ └── EntityPacketRewriter1_18.java │ │ │ ├── v1_19_1to1_19/ │ │ │ │ ├── Protocol1_19_1To1_19.java │ │ │ │ ├── rewriter/ │ │ │ │ │ └── EntityPacketRewriter1_19_1.java │ │ │ │ └── storage/ │ │ │ │ ├── ChatRegistryStorage.java │ │ │ │ ├── ChatRegistryStorage1_19_1.java │ │ │ │ ├── NonceStorage.java │ │ │ │ └── ReceivedMessagesStorage.java │ │ │ ├── v1_19_3to1_19_1/ │ │ │ │ ├── Protocol1_19_3To1_19_1.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_19_3.java │ │ │ │ │ └── EntityPacketRewriter1_19_3.java │ │ │ │ └── storage/ │ │ │ │ ├── ChatSessionStorage.java │ │ │ │ ├── ChatTypeStorage1_19_3.java │ │ │ │ └── NonceStorage.java │ │ │ ├── v1_19_4to1_19_3/ │ │ │ │ ├── Protocol1_19_4To1_19_3.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_19_4.java │ │ │ │ │ └── EntityPacketRewriter1_19_4.java │ │ │ │ └── storage/ │ │ │ │ ├── EntityTracker1_19_4.java │ │ │ │ └── LinkedEntityStorage.java │ │ │ ├── v1_19to1_18_2/ │ │ │ │ ├── Protocol1_19To1_18_2.java │ │ │ │ ├── data/ │ │ │ │ │ └── BackwardsMappingData1_19.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_19.java │ │ │ │ │ ├── CommandRewriter1_19.java │ │ │ │ │ └── EntityPacketRewriter1_19.java │ │ │ │ └── storage/ │ │ │ │ ├── DimensionRegistryStorage.java │ │ │ │ ├── EntityTracker1_19.java │ │ │ │ ├── LastDeathPosition.java │ │ │ │ ├── NonceStorage.java │ │ │ │ └── StoredPainting.java │ │ │ ├── v1_20_2to1_20/ │ │ │ │ ├── Protocol1_20_2To1_20.java │ │ │ │ ├── provider/ │ │ │ │ │ └── AdvancementCriteriaProvider.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_20_2.java │ │ │ │ │ ├── BlockRewriter1_20_2.java │ │ │ │ │ └── EntityPacketRewriter1_20_2.java │ │ │ │ └── storage/ │ │ │ │ └── ConfigurationPacketStorage.java │ │ │ ├── v1_20_3to1_20_2/ │ │ │ │ ├── Protocol1_20_3To1_20_2.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_20_3.java │ │ │ │ │ ├── BlockPacketRewriter1_20_3.java │ │ │ │ │ └── EntityPacketRewriter1_20_3.java │ │ │ │ └── storage/ │ │ │ │ ├── ResourcepackIDStorage.java │ │ │ │ └── SpawnPositionStorage.java │ │ │ ├── v1_20_5to1_20_3/ │ │ │ │ ├── Protocol1_20_5To1_20_3.java │ │ │ │ ├── Types1_20_3.java │ │ │ │ ├── provider/ │ │ │ │ │ ├── NoopTransferProvider.java │ │ │ │ │ └── TransferProvider.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_20_5.java │ │ │ │ │ ├── BlockPacketRewriter1_20_5.java │ │ │ │ │ ├── ComponentRewriter1_20_5.java │ │ │ │ │ └── EntityPacketRewriter1_20_5.java │ │ │ │ └── storage/ │ │ │ │ ├── CookieStorage.java │ │ │ │ ├── RegistryDataStorage.java │ │ │ │ └── SecureChatStorage.java │ │ │ ├── v1_20to1_19_4/ │ │ │ │ ├── Protocol1_20To1_19_4.java │ │ │ │ ├── data/ │ │ │ │ │ └── BackwardsMappingData1_20.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_20.java │ │ │ │ │ ├── BlockPacketRewriter1_20.java │ │ │ │ │ └── EntityPacketRewriter1_20.java │ │ │ │ └── storage/ │ │ │ │ └── BackSignEditStorage.java │ │ │ ├── v1_21_11to1_21_9/ │ │ │ │ ├── Protocol1_21_11To1_21_9.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_21_11.java │ │ │ │ │ ├── ComponentRewriter1_21_11.java │ │ │ │ │ └── EntityPacketRewriter1_21_11.java │ │ │ │ └── storage/ │ │ │ │ └── GameTimeStorage.java │ │ │ ├── v1_21_2to1_21/ │ │ │ │ ├── Protocol1_21_2To1_21.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_21_2.java │ │ │ │ │ ├── ComponentRewriter1_21_2.java │ │ │ │ │ ├── EntityPacketRewriter1_21_2.java │ │ │ │ │ └── ParticleRewriter1_21_2.java │ │ │ │ ├── storage/ │ │ │ │ │ ├── InventoryStateIdStorage.java │ │ │ │ │ ├── ItemTagStorage.java │ │ │ │ │ ├── PlayerStorage.java │ │ │ │ │ ├── RecipeStorage.java │ │ │ │ │ └── SignStorage.java │ │ │ │ └── task/ │ │ │ │ └── PlayerPacketsTickTask.java │ │ │ ├── v1_21_4to1_21_2/ │ │ │ │ ├── Protocol1_21_4To1_21_2.java │ │ │ │ └── rewriter/ │ │ │ │ ├── BlockItemPacketRewriter1_21_4.java │ │ │ │ ├── ComponentRewriter1_21_4.java │ │ │ │ ├── EntityPacketRewriter1_21_4.java │ │ │ │ └── ParticleRewriter1_21_4.java │ │ │ ├── v1_21_5to1_21_4/ │ │ │ │ ├── Protocol1_21_5To1_21_4.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_21_5.java │ │ │ │ │ ├── BlockPacketRewriter1_21_5.java │ │ │ │ │ ├── ComponentRewriter1_21_5.java │ │ │ │ │ ├── EntityPacketRewriter1_21_5.java │ │ │ │ │ └── RegistryDataRewriter1_21_5.java │ │ │ │ └── storage/ │ │ │ │ ├── HashedItemConverterStorage.java │ │ │ │ └── HorseDataStorage.java │ │ │ ├── v1_21_6to1_21_5/ │ │ │ │ ├── Protocol1_21_6To1_21_5.java │ │ │ │ ├── data/ │ │ │ │ │ ├── Button.java │ │ │ │ │ ├── Dialog.java │ │ │ │ │ ├── Template.java │ │ │ │ │ ├── input/ │ │ │ │ │ │ ├── BooleanInput.java │ │ │ │ │ │ ├── Input.java │ │ │ │ │ │ ├── NumberRangeInput.java │ │ │ │ │ │ ├── SingleOptionInput.java │ │ │ │ │ │ └── TextInput.java │ │ │ │ │ └── widget/ │ │ │ │ │ ├── ItemWidget.java │ │ │ │ │ ├── TextWidget.java │ │ │ │ │ └── Widget.java │ │ │ │ ├── provider/ │ │ │ │ │ ├── ChestDialogViewProvider.java │ │ │ │ │ └── DialogViewProvider.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_21_6.java │ │ │ │ │ ├── ComponentRewriter1_21_6.java │ │ │ │ │ ├── EntityPacketRewriter1_21_6.java │ │ │ │ │ └── RegistryDataRewriter1_21_6.java │ │ │ │ ├── storage/ │ │ │ │ │ ├── ChestDialogStorage.java │ │ │ │ │ ├── ClickEvents.java │ │ │ │ │ ├── RegistryAndTags.java │ │ │ │ │ └── ServerLinks.java │ │ │ │ └── task/ │ │ │ │ └── ChestDialogViewTask.java │ │ │ ├── v1_21_7to1_21_6/ │ │ │ │ ├── Protocol1_21_7To1_21_6.java │ │ │ │ └── rewriter/ │ │ │ │ ├── BlockItemPacketRewriter1_21_7.java │ │ │ │ └── EntityPacketRewriter1_21_7.java │ │ │ ├── v1_21_9to1_21_7/ │ │ │ │ ├── Protocol1_21_9To1_21_7.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_21_9.java │ │ │ │ │ ├── ComponentRewriter1_21_9.java │ │ │ │ │ ├── EntityPacketRewriter1_21_9.java │ │ │ │ │ ├── ParticleRewriter1_21_9.java │ │ │ │ │ └── RegistryDataRewriter1_21_9.java │ │ │ │ ├── storage/ │ │ │ │ │ ├── DimensionScaleStorage.java │ │ │ │ │ ├── MannequinData.java │ │ │ │ │ └── PlayerRotationStorage.java │ │ │ │ └── tracker/ │ │ │ │ └── EntityTracker1_21_9.java │ │ │ ├── v1_21to1_20_5/ │ │ │ │ ├── Protocol1_21To1_20_5.java │ │ │ │ ├── rewriter/ │ │ │ │ │ ├── BlockItemPacketRewriter1_21.java │ │ │ │ │ ├── ComponentRewriter1_21.java │ │ │ │ │ └── EntityPacketRewriter1_21.java │ │ │ │ └── storage/ │ │ │ │ ├── EnchantmentsPaintingsStorage.java │ │ │ │ ├── OpenScreenStorage.java │ │ │ │ └── PlayerRotationStorage.java │ │ │ ├── v1_9_1to1_9/ │ │ │ │ └── Protocol1_9_1To1_9.java │ │ │ ├── v1_9_3to1_9_1/ │ │ │ │ ├── Protocol1_9_3To1_9_1.java │ │ │ │ └── data/ │ │ │ │ └── BlockEntity1_9_1.java │ │ │ └── v26_1to1_21_11/ │ │ │ ├── Protocol26_1To1_21_11.java │ │ │ ├── rewriter/ │ │ │ │ ├── BlockItemPacketRewriter26_1.java │ │ │ │ ├── ComponentRewriter26_1.java │ │ │ │ └── EntityPacketRewriter26_1.java │ │ │ └── storage/ │ │ │ ├── DayTimeStorage.java │ │ │ └── GameModeStorage.java │ │ └── utils/ │ │ ├── BackwardsProtocolLogger.java │ │ ├── ChatUtil.java │ │ └── VelocityUtil.java │ ├── java-templates/ │ │ └── com/ │ │ └── viaversion/ │ │ └── viabackwards/ │ │ └── utils/ │ │ └── VersionInfo.java │ └── resources/ │ └── assets/ │ └── viabackwards/ │ ├── config.yml │ └── data/ │ ├── biome-mappings.json │ ├── chat-types-1.19.1.nbt │ ├── item-mappings-1.10.json │ ├── item-mappings-1.11.1.json │ ├── item-mappings-1.11.json │ ├── item-mappings-1.12.json │ ├── mappings-1.10to1.9.4.nbt │ ├── mappings-1.11to1.10.nbt │ ├── mappings-1.12to1.11.nbt │ ├── mappings-1.13.2to1.13.nbt │ ├── mappings-1.13to1.12.nbt │ ├── mappings-1.14to1.13.2.nbt │ ├── mappings-1.15to1.14.nbt │ ├── mappings-1.16.2to1.16.nbt │ ├── mappings-1.16to1.15.nbt │ ├── mappings-1.17to1.16.2.nbt │ ├── mappings-1.18to1.17.nbt │ ├── mappings-1.19.3to1.19.nbt │ ├── mappings-1.19.4to1.19.3.nbt │ ├── mappings-1.19to1.18.nbt │ ├── mappings-1.20.2to1.20.nbt │ ├── mappings-1.20.3to1.20.2.nbt │ ├── mappings-1.20.5to1.20.3.nbt │ ├── mappings-1.20to1.19.4.nbt │ ├── mappings-1.21.11to1.21.9.nbt │ ├── mappings-1.21.2to1.21.nbt │ ├── mappings-1.21.4to1.21.2.nbt │ ├── mappings-1.21.5to1.21.4.nbt │ ├── mappings-1.21.6to1.21.5.nbt │ ├── mappings-1.21.7to1.21.6.nbt │ ├── mappings-1.21.9to1.21.7.nbt │ ├── mappings-1.21to1.20.5.nbt │ ├── mappings-26.1to1.21.11.nbt │ ├── translation-mappings.json │ └── trim_pattern-1.19.4.nbt ├── fabric/ │ ├── build.gradle.kts │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── viaversion/ │ │ └── viabackwards/ │ │ ├── ViaFabricAddon.java │ │ └── fabric/ │ │ └── util/ │ │ └── LoggerWrapper.java │ └── resources/ │ └── fabric.mod.json ├── gradle/ │ ├── libs.versions.toml │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts ├── sponge/ │ ├── build.gradle.kts │ └── src/ │ └── main/ │ └── resources/ │ └── META-INF/ │ └── sponge_plugins.json ├── universal/ │ └── build.gradle.kts └── velocity/ ├── build.gradle.kts └── src/ └── main/ └── java/ └── com/ └── viaversion/ └── viabackwards/ └── VelocityPlugin.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ [*] charset = utf-8 indent_size = 4 indent_style = space insert_final_newline = true tab_width = 4 [*.java] ij_java_class_count_to_use_import_on_demand = 999999 ij_java_names_count_to_use_import_on_demand = 999999 ij_java_imports_layout = *, |, $* ij_java_generate_final_locals = true ij_java_generate_final_parameters = true [{*.json,*.yml}] indent_size = 2 ================================================ FILE: .github/FUNDING.yml ================================================ github: kennytv patreon: kennytv custom: ['https://florianreuth.de/donate'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug Report description: Report a bug or console error labels: [unconfirmed] body: - type: markdown attributes: value: "**Before reporting a bug, please see if using master/dev builds from https://ci.viaversion.com/ fixes your issue.**" - type: input attributes: label: "'/viaversion dump' Output" description: | Run `/viaversion dump` in the console or in the chat, then copy and paste the given link here. placeholder: | https://dump.viaversion.com/... validations: required: true - type: textarea attributes: label: Server/Client Error description: | If you encounter warnings/errors in your console, **paste them with https://mclo.gs/ and put the paste link here**. If the error is small/less than 10 lines, you may put it directly into this field. **Important**: If you are kicked for `Network Protocol Error` or an encoder/decoder exception, please click the `Open Report Directory` button on your client and paste the newest disconnect file contents. value: | ``` Put the mclo.gs link or text here. ``` placeholder: Please do not remove the grave accents; simply replace the line of text in the middle. validations: required: false - type: textarea attributes: label: Bug Description description: | Describe the unexpected behavior. If you want to attach screenshots, use the comment field at the bottom of the page. placeholder: | Example: "Placing signs on 1.16.5 causes text to disappear." validations: required: true - type: textarea attributes: label: Steps to Reproduce description: | List the steps on how we can reproduce the issue. Make sure we can easily understand what you mean with each step. **Please always include the client version(s) with which the issue happens. Ideally also try to find out from what specific version downward/in what version range the issue happens.** placeholder: | Example: 1. Login with a 1.16.5 client 2. Place a sign 3. The sign text disappears validations: required: true - type: textarea attributes: label: Additional Server Info description: | Do you use a proxy (eg. Velocity)? What software do you use and what plugins? placeholder: | Example: "I also use Velocity with the following plugins: x, y, z" validations: required: false - type: checkboxes attributes: label: Checklist description: Make sure you have followed each of the steps outlined here. options: - label: Via plugins are only running on **EITHER** the backend servers (e.g. Paper) **OR** the proxy (e.g. Velocity), **not on both**. required: true - label: I have included a ViaVersion dump. required: true - label: If applicable, I have included a paste (**not a screenshot**) of the error. required: true - label: I have tried a build from https://ci.viaversion.com/ and the issue still persists. required: true - type: markdown attributes: value: | ## Comments And Screenshots If needed, add **screenshots to help explain your problem** in the comment field below. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Dev builds url: https://ci.viaversion.com/ about: Before reporting a bug, please check if using master/dev builds from our ci fixes your issue. - name: ViaVersion Discord url: https://discord.gg/viaversion about: For smaller issues or questions, you can also join our Discord server. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: Feature Request description: Suggest a feature to be added labels: [Feature Request] body: - type: textarea attributes: label: Problem Description description: | Describe the issue you are facing or why you need the feature to be added. placeholder: | I am always frustrated with... validations: required: true - type: textarea attributes: label: Solution Description description: | Describe the solution you would like to see. validations: required: true - type: textarea attributes: label: Alternatives description: | Describe alternatives you have considered. validations: required: false - type: textarea attributes: label: Additional Info description: | Does the feature apply to any specific version or environment? validations: required: false ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" cooldown: default-days: 7 directory: "/" schedule: interval: "monthly" - package-ecosystem: "gradle" cooldown: default-days: 7 directory: "/" schedule: interval: "monthly" ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: [pull_request, push, workflow_dispatch] permissions: contents: read jobs: build: # Only run on PRs if the source branch is on a different repo. We do not need to run everything twice. if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }} runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - name: Checkout Repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2 with: persist-credentials: false - name: Set up Gradle uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # 5.0.1 - name: Set up JDK 17 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # 5.2.0 with: distribution: 'temurin' java-version: 17 check-latest: true - name: Build with Gradle run: ./gradlew build --refresh-dependencies ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish to Hangar and Modrinth on: push: branches: - master - dev workflow_dispatch: permissions: contents: read jobs: publish: if: github.repository_owner == 'ViaVersion' runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - name: Checkout Repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2 with: persist-credentials: false - name: Set up Gradle uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # 5.0.1 - name: Set up JDK 17 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # 5.2.0 with: distribution: 'temurin' java-version: 17 check-latest: true - name: Build with Gradle run: ./gradlew build --refresh-dependencies - name: Publish to Hangar env: HANGAR_TOKEN: ${{ secrets.HANGAR_TOKEN }} run: ./gradlew publishAllPublicationsToHangar --stacktrace continue-on-error: true - name: Publish to Modrinth env: MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} run: ./gradlew modrinth --stacktrace continue-on-error: true ================================================ FILE: .gitignore ================================================ # Gradle .gradle/ build/ out/ classes/ .kotlin/ # Eclipse *.launch # Idea .idea/ !.idea/copyright/* !.idea/scopes/* *.iml *.ipr *.iws # VSCode .settings/ .vscode/ bin/ .classpath .project # macOS .DS_Store # Misc run/ ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ # ViaBackwards [![Latest Release](https://img.shields.io/github/v/release/ViaVersion/ViaBackwards)](https://github.com/ViaVersion/ViaBackwards/releases) [![Build Status](https://github.com/ViaVersion/ViaBackwards/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/ViaVersion/ViaBackwards/actions) [![Discord](https://img.shields.io/badge/chat-on%20discord-blue.svg)](https://viaversion.com/discord) **Allows the connection of older clients to newer server versions for Minecraft servers.** Requires [ViaVersion](https://hangar.papermc.io/ViaVersion/ViaVersion) to be installed. Supported Versions - As a plugin, ViaBackwards runs on servers on releases 1.10-latest. You can also use ViaBackwards in ViaFabric or ViaFabricPlus. - in **ViaFabric**, put ViaBackwards into the `mods` folder - in **ViaFabricPlus**, put ViaBackwards into the `config/viafabriclus/jars` folder See [HERE](https://viaversion.com) for an overview of the different Via* projects. Snapshot support -------- **ViaBackwards will only be released a few days *after* a Minecraft update** unless the protocol changes of the update were trivial. If you want early-access, usually days or even weeks before the final release, you can subscribe to either: - [GitHub Sponsors](https://github.com/sponsors/kennytv/sponsorships?sponsor=kennytv&tier_id=385613&preview=false) (preferred option. Use the `/verify` command on this Discord after), or alternatively - [Patreon](https://www.patreon.com/kennytv/membership) (see the highest tier and make sure to link Patreon to your Discord account under Settings->Connections) This also includes access to a private repository with the code, which will be pushed to the public repository after the given delay on a Minecraft update. Releases/Dev Builds - You can find releases in the following places: - **Hangar (for our plugins)**: https://hangar.papermc.io/ViaVersion/ViaBackwards - **Modrinth (for our mods)**: https://modrinth.com/mod/viabackwards - **GitHub**: https://github.com/ViaVersion/ViaBackwards/releases Dev builds for **all** of our projects are on our Jenkins server: - **Jenkins**: https://ci.viaversion.com/view/ViaBackwards/ Known issues - * 1.17+ min_y and height world values that are not 0/256 **are not supported**. Clients older than 1.17 will not be able to see or interact with blocks below y=0 and above y=255 * <1.17 clients on 1.17+ servers might experience inventory desyncs on certain inventory click actions * Sound mappings are incomplete ([see here](https://github.com/ViaVersion/ViaBackwards/issues/326)) * <1.19.4 clients on 1.20+ servers won't be able to use the smithing table. This can be fixed by installing [AxSmithing](https://github.com/ViaVersionAddons/AxSmithing) Other Links - **Maven:** https://repo.viaversion.com **List of contributors:** https://github.com/ViaVersion/ViaBackwards/graphs/contributors Building - After cloning this repository, build the project with Gradle by running `./gradlew build` and take the created jar out of the `build/libs` directory. You need JDK 17 or newer to build ViaBackwards. License - This project is licensed under the [GNU General Public License Version 3](LICENSE). Special Thanks - ![https://www.yourkit.com/](https://www.yourkit.com/images/yklogo.png) [YourKit](https://www.yourkit.com/) supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET applications. YourKit is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/), [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/), and [YourKit YouMonitor](https://www.yourkit.com/youmonitor/). ================================================ FILE: SECURITY.md ================================================ # 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). ================================================ FILE: build-logic/build.gradle.kts ================================================ plugins { `kotlin-dsl` } repositories { gradlePluginPortal() } dependencies { // version must be manually kept in sync with the one in root project settings.gradle.kts implementation("com.gradleup.shadow:shadow-gradle-plugin:9.4.1") // A nice no-conflict comment for patching in downgrading } ================================================ FILE: build-logic/src/main/kotlin/extensions.kt ================================================ import org.gradle.api.Project import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.provider.ListProperty import org.gradle.api.provider.ValueSource import org.gradle.api.provider.ValueSourceParameters import org.gradle.jvm.toolchain.JavaLanguageVersion import org.gradle.process.ExecOperations import java.io.ByteArrayOutputStream import javax.inject.Inject fun Project.latestCommitHash(): String { return runGitCommand(listOf("rev-parse", "--short", "HEAD")) } fun Project.latestCommitMessage(): String { return runGitCommand(listOf("log", "-1", "--pretty=%B")) } fun Project.branchName(): String { return runGitCommand(listOf("rev-parse", "--abbrev-ref", "HEAD")) } fun Project.runGitCommand(args: List): String { return providers.of(GitCommand::class.java) { parameters.args.set(args) }.getOrNull() ?: "unknown" } abstract class GitCommand : ValueSource { @get:Inject abstract val execOperations: ExecOperations interface GitCommandParameters : ValueSourceParameters { val args: ListProperty } override fun obtain(): String? { try { val command = listOf("git") + parameters.args.get() val output = ByteArrayOutputStream() execOperations.exec { commandLine = command standardOutput = output isIgnoreExitValue = true } return output.toString(Charsets.UTF_8).trim().takeIf { it.isNotBlank() } } catch (e: Exception) { return null } } } fun Project.parseMinecraftSnapshotVersion(version: String): String? { val separatorIndex = version.indexOf('-') val lastSeparatorIndex = version.lastIndexOf('-') if (separatorIndex == -1 || separatorIndex == lastSeparatorIndex) { return null } return version.substring(separatorIndex + 1, lastSeparatorIndex) } fun JavaPluginExtension.javaTarget(version: Int) { toolchain.languageVersion.set(JavaLanguageVersion.of(version)) } ================================================ FILE: build-logic/src/main/kotlin/vb.base-conventions.gradle.kts ================================================ plugins { `java-library` } tasks { // Variable replacements processResources { val ver = project.version.toString() val desc = project.description filesMatching(listOf("plugin.yml", "META-INF/sponge_plugins.json", "fabric.mod.json")) { expand(mapOf("version" to ver, "description" to desc)) } } javadoc { options.encoding = Charsets.UTF_8.name() (options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet") } compileJava { options.encoding = Charsets.UTF_8.name() options.compilerArgs.addAll(listOf("-nowarn", "-Xlint:-unchecked", "-Xlint:-deprecation")) options.isFork = true } } java { javaTarget(17) withSourcesJar() } ================================================ FILE: build-logic/src/main/kotlin/vb.build-logic.gradle.kts ================================================ ================================================ FILE: build-logic/src/main/kotlin/vb.shadow-conventions.gradle.kts ================================================ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.jvm.tasks.Jar import org.gradle.kotlin.dsl.named plugins { id("vb.base-conventions") id("maven-publish") id("com.gradleup.shadow") } tasks { named("jar") { archiveClassifier.set("unshaded") from(project.rootProject.file("LICENSE")) } val shadowJar = named("shadowJar") { duplicatesStrategy = DuplicatesStrategy.EXCLUDE archiveClassifier.set("") configureRelocations() } named("build") { dependsOn(shadowJar) } } publishing { publications.create("mavenJava") { groupId = rootProject.group as String artifactId = project.name version = rootProject.version as String artifact(tasks["shadowJar"]) artifact(tasks["sourcesJar"]) } repositories.maven { name = "Via" url = uri("https://repo.viaversion.com/") credentials(PasswordCredentials::class) authentication { create("basic") } } } fun ShadowJar.configureRelocations() { relocate("com.google.gson", "com.viaversion.viaversion.libs.gson") relocate("it.unimi.dsi.fastutil", "com.viaversion.viaversion.libs.fastutil") } ================================================ FILE: build.gradle.kts ================================================ plugins { base id("vb.build-logic") } allprojects { group = "com.viaversion" version = property("projectVersion") as String // from gradle.properties description = "Allows the connection of older clients to newer server versions for Minecraft servers." } val main = setOf( projects.viabackwards, projects.viabackwardsCommon, projects.viabackwardsBukkit, projects.viabackwardsVelocity ).map { it.path } subprojects { when (path) { in main -> plugins.apply("vb.shadow-conventions") else -> plugins.apply("vb.base-conventions") } } ================================================ FILE: bukkit/build.gradle.kts ================================================ dependencies { compileOnlyApi(projects.viabackwardsCommon) compileOnly(libs.paper) { exclude("com.google.code.gson", "gson") exclude("javax.persistence", "persistence-api") } } ================================================ FILE: bukkit/src/main/java/com/viaversion/viabackwards/BukkitPlugin.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards; import com.viaversion.viabackwards.api.ViaBackwardsPlatform; import com.viaversion.viabackwards.listener.DurabilitySync1_11; import com.viaversion.viabackwards.listener.PlayerHurtSound1_12; import com.viaversion.viabackwards.listener.FireExtinguish1_16; import com.viaversion.viabackwards.listener.LecternInteract1_14; import com.viaversion.viabackwards.listener.ItemDropSync1_17; import com.viaversion.viabackwards.listener.SpearAttack1_21_11; import com.viaversion.viabackwards.protocol.v1_20_2to1_20.provider.AdvancementCriteriaProvider; import com.viaversion.viabackwards.provider.BukkitAdvancementCriteriaProvider; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.platform.providers.ViaProviders; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import java.io.File; import org.bukkit.plugin.java.JavaPlugin; public class BukkitPlugin extends JavaPlugin implements ViaBackwardsPlatform { public BukkitPlugin() { Via.getManager().addEnableListener(() -> init(new File(getDataFolder(), "config.yml"))); } @Override public void onEnable() { if (Via.getManager().getInjector().lateProtocolVersionSetting()) { // Enable in the next tick Via.getPlatform().runSync(this::enable, 1); } else { enable(); } } @Override public void enable() { ViaBackwardsPlatform.super.enable(); final ProtocolVersion protocolVersion = Via.getAPI().getServerVersion().highestSupportedProtocolVersion(); if (protocolVersion.newerThanOrEqualTo(ProtocolVersion.v1_21_11)) { new SpearAttack1_21_11(this).register(); } if (protocolVersion.newerThanOrEqualTo(ProtocolVersion.v1_17)) { new ItemDropSync1_17(this).register(); } if (protocolVersion.newerThanOrEqualTo(ProtocolVersion.v1_16)) { new FireExtinguish1_16(this).register(); } if (protocolVersion.newerThanOrEqualTo(ProtocolVersion.v1_14)) { new LecternInteract1_14(this).register(); } if (protocolVersion.newerThanOrEqualTo(ProtocolVersion.v1_12)) { new PlayerHurtSound1_12(this).register(); } if (protocolVersion.newerThanOrEqualTo(ProtocolVersion.v1_11)) { new DurabilitySync1_11(this).register(); } final ViaProviders providers = Via.getManager().getProviders(); if (protocolVersion.newerThanOrEqualTo(ProtocolVersion.v1_20_2)) { providers.use(AdvancementCriteriaProvider.class, new BukkitAdvancementCriteriaProvider()); } } @Override public void disable() { getPluginLoader().disablePlugin(this); } } ================================================ FILE: bukkit/src/main/java/com/viaversion/viabackwards/listener/DurabilitySync1_11.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.listener; import com.viaversion.viabackwards.BukkitPlugin; import com.viaversion.viabackwards.protocol.v1_11to1_10.Protocol1_11To1_10; import com.viaversion.viaversion.bukkit.listeners.ViaBukkitListener; import org.bukkit.GameMode; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.inventory.ItemStack; public class DurabilitySync1_11 extends ViaBukkitListener { public DurabilitySync1_11(final BukkitPlugin plugin) { super(plugin, Protocol1_11To1_10.class); } @EventHandler(priority = EventPriority.MONITOR) public void onBlockBreak(final BlockBreakEvent event) { if (!event.isCancelled()) { return; } final Player player = event.getPlayer(); if (player.getGameMode() == GameMode.CREATIVE || !isOnPipe(player)) { return; } // Resend the item in the hand to sync durability final int slot = player.getInventory().getHeldItemSlot(); final ItemStack item = player.getInventory().getItem(slot); if (item != null && item.getType().getMaxDurability() > 0) { player.getInventory().setItem(slot, item); } } } ================================================ FILE: bukkit/src/main/java/com/viaversion/viabackwards/listener/FireExtinguish1_16.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.listener; import com.viaversion.viabackwards.BukkitPlugin; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.Protocol1_16To1_15_2; import com.viaversion.viaversion.bukkit.listeners.ViaBukkitListener; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; public class FireExtinguish1_16 extends ViaBukkitListener { public FireExtinguish1_16(BukkitPlugin plugin) { super(plugin, Protocol1_16To1_15_2.class); } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onFireExtinguish(PlayerInteractEvent event) { if (event.getAction() != Action.LEFT_CLICK_BLOCK) return; Block block = event.getClickedBlock(); if (block == null) return; Player player = event.getPlayer(); if (!isOnPipe(player)) return; Block relative = block.getRelative(event.getBlockFace()); if (relative.getType() == Material.FIRE) { event.setCancelled(true); relative.setType(Material.AIR); } } } ================================================ FILE: bukkit/src/main/java/com/viaversion/viabackwards/listener/ItemDropSync1_17.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.listener; import com.viaversion.viabackwards.BukkitPlugin; import com.viaversion.viabackwards.protocol.v1_13_1to1_13.Protocol1_13_1To1_13; import com.viaversion.viaversion.bukkit.listeners.ViaBukkitListener; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.inventory.ItemStack; public class ItemDropSync1_17 extends ViaBukkitListener { public ItemDropSync1_17(final BukkitPlugin plugin) { super(plugin, Protocol1_13_1To1_13.class); // Starts with 1.13 clients on 1.17 servers } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onItemDrop(final PlayerDropItemEvent event) { final Player player = event.getPlayer(); if (!isOnPipe(player)) { return; } // Resend the item in the hand final int slot = player.getInventory().getHeldItemSlot(); final ItemStack item = player.getInventory().getItem(slot); player.getInventory().setItem(slot, item); } } ================================================ FILE: bukkit/src/main/java/com/viaversion/viabackwards/listener/LecternInteract1_14.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.listener; import com.viaversion.viabackwards.BukkitPlugin; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.Protocol1_14To1_13_2; import com.viaversion.viaversion.bukkit.listeners.ViaBukkitListener; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.Lectern; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BookMeta; public class LecternInteract1_14 extends ViaBukkitListener { public LecternInteract1_14(BukkitPlugin plugin) { super(plugin, Protocol1_14To1_13_2.class); } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onLecternInteract(PlayerInteractEvent event) { Block block = event.getClickedBlock(); if (block == null || block.getType() != Material.LECTERN) return; Player player = event.getPlayer(); if (!isOnPipe(player)) return; Lectern lectern = (Lectern) block.getState(); ItemStack book = lectern.getInventory().getItem(0); if (book == null) return; BookMeta meta = (BookMeta) book.getItemMeta(); // Open a book with the text of the lectern's writable book ItemStack newBook = new ItemStack(Material.WRITTEN_BOOK); BookMeta newBookMeta = (BookMeta) newBook.getItemMeta(); //noinspection deprecation newBookMeta.setPages(meta.getPages()); newBookMeta.setAuthor("an upsidedown person"); newBookMeta.setTitle("buk"); newBook.setItemMeta(newBookMeta); player.openBook(newBook); event.setCancelled(true); } } ================================================ FILE: bukkit/src/main/java/com/viaversion/viabackwards/listener/PlayerHurtSound1_12.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.listener; import com.viaversion.viabackwards.BukkitPlugin; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.Protocol1_12To1_11_1; import com.viaversion.viaversion.bukkit.listeners.ViaBukkitListener; import org.bukkit.Sound; import org.bukkit.SoundCategory; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.entity.EntityDamageEvent; public class PlayerHurtSound1_12 extends ViaBukkitListener { public PlayerHurtSound1_12(BukkitPlugin plugin) { super(plugin, Protocol1_12To1_11_1.class); } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onFireDamage(EntityDamageEvent event) { if (event.getEntityType() != EntityType.PLAYER) return; EntityDamageEvent.DamageCause cause = event.getCause(); if (cause != EntityDamageEvent.DamageCause.FIRE && cause != EntityDamageEvent.DamageCause.FIRE_TICK && cause != EntityDamageEvent.DamageCause.LAVA && cause != EntityDamageEvent.DamageCause.DROWNING) { return; } Player player = (Player) event.getEntity(); if (isOnPipe(player)) { player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_HURT, SoundCategory.PLAYERS, 1, 1); } } } ================================================ FILE: bukkit/src/main/java/com/viaversion/viabackwards/listener/SpearAttack1_21_11.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.listener; import com.viaversion.viabackwards.BukkitPlugin; import com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.Protocol1_21_11To1_21_9; import com.viaversion.viaversion.bukkit.listeners.ViaBukkitListener; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.inventory.ItemStack; public class SpearAttack1_21_11 extends ViaBukkitListener { public SpearAttack1_21_11(final BukkitPlugin plugin) { super(plugin, Protocol1_21_11To1_21_9.class); } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onBlockBreak(final BlockBreakEvent event) { final Player player = event.getPlayer(); if (!isOnPipe(player)) { return; } final ItemStack item = player.getInventory().getItemInMainHand(); if (item.getType().name().endsWith("_SPEAR")) { // Prevent spears from breaking blocks event.setCancelled(true); } } } ================================================ FILE: bukkit/src/main/java/com/viaversion/viabackwards/provider/BukkitAdvancementCriteriaProvider.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.provider; import com.viaversion.viabackwards.protocol.v1_20_2to1_20.provider.AdvancementCriteriaProvider; import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; import org.bukkit.advancement.Advancement; public final class BukkitAdvancementCriteriaProvider extends AdvancementCriteriaProvider { private static final String[] EMPTY_CRITERIA = new String[0]; @Override public String[] getCriteria(final String advancementKey) { final Advancement advancement = Bukkit.getAdvancement(NamespacedKey.fromString(advancementKey)); return advancement == null ? EMPTY_CRITERIA : advancement.getCriteria().toArray(EMPTY_CRITERIA); } } ================================================ FILE: bukkit/src/main/resources/plugin.yml ================================================ name: ViaBackwards version: ${version} description: ${description} main: com.viaversion.viabackwards.BukkitPlugin api-version: 1.13 folia-supported: true authors: [Matsv, kennytv, Gerrygames, creeper123123321, ForceUpdate1, EnZaXD] website: "https://viaversion.com/backwards" depend: [ViaVersion] ================================================ FILE: common/build.gradle.kts ================================================ plugins { id("net.kyori.blossom") id("org.jetbrains.gradle.plugin.idea-ext") } sourceSets { main { blossom { javaSources { property("version", project.version.toString()) property("impl_version", "git-ViaBackwards-${project.version}:${rootProject.latestCommitHash()}") } } } } dependencies { compileOnlyApi(libs.viaver) compileOnlyApi(libs.netty) compileOnlyApi(libs.guava) compileOnlyApi(libs.checkerQual) } java { withJavadocJar() } // Task to quickly test/debug code changes using https://github.com/ViaVersion/ViaProxy // For further instructions see the ViaProxy repository README val prepareViaProxyFiles by tasks.registering(Copy::class) { dependsOn(project.tasks.shadowJar) from(project.tasks.shadowJar.map { it.archiveFile.get().asFile }) into(layout.projectDirectory.dir("run/jars")) val projectName = project.name rename { "${projectName}.jar" } } val cleanupViaProxyFiles by tasks.registering(Delete::class) { delete( layout.projectDirectory.file("run/jars/${project.name}.jar"), layout.projectDirectory.dir("run/logs") ) } val viaProxyConfiguration: Configuration by configurations.creating { dependencies.add(rootProject.libs.viaProxy.get().copy().setTransitive(false)) } tasks.register("runViaProxy") { dependsOn(prepareViaProxyFiles) finalizedBy(cleanupViaProxyFiles) mainClass.set("net.raphimc.viaproxy.ViaProxy") classpath = viaProxyConfiguration workingDir = layout.projectDirectory.dir("run").asFile jvmArgs = listOf("-DskipUpdateCheck") if (System.getProperty("viaproxy.gui.autoStart") != null) { jvmArgs("-Dviaproxy.gui.autoStart") } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/ViaBackwards.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards; import com.google.common.base.Preconditions; import com.viaversion.viabackwards.api.ViaBackwardsConfig; import com.viaversion.viabackwards.api.ViaBackwardsPlatform; public final class ViaBackwards { private static ViaBackwardsPlatform platform; private static ViaBackwardsConfig config; public static void init(ViaBackwardsPlatform platform, ViaBackwardsConfig config) { Preconditions.checkArgument(ViaBackwards.platform == null, "ViaBackwards is already initialized"); ViaBackwards.platform = platform; ViaBackwards.config = config; } public static ViaBackwardsPlatform getPlatform() { return platform; } public static ViaBackwardsConfig getConfig() { return config; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/ViaBackwardsConfig.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards; import com.viaversion.viabackwards.api.DialogStyleConfig; import com.viaversion.viaversion.util.ChatColorUtil; import com.viaversion.viaversion.util.Config; import com.viaversion.viaversion.util.ConfigSection; import java.io.File; import java.io.InputStream; import java.net.URL; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.logging.Logger; public class ViaBackwardsConfig extends Config implements com.viaversion.viabackwards.api.ViaBackwardsConfig { private boolean addCustomEnchantsToLore; private boolean addTeamColorToPrefix; private boolean fix1_13FacePlayer; private boolean alwaysShowOriginalMobName; private boolean fix1_13FormattedInventoryTitles; private boolean handlePingsAsInvAcknowledgements; private boolean bedrockAtY0; private boolean sculkShriekersToCryingObsidian; private boolean scaffoldingToWater; private boolean mapDarknessEffect; private boolean mapCustomModelData; private boolean mapDisplayEntities; private boolean suppressEmulationWarnings; private boolean dialogsViaChests; private DialogStyleConfig dialogStyleConfig; private boolean codeOfConductAsDialog; private boolean passOriginalItemNameToResourcePacks; public ViaBackwardsConfig(File configFile, Logger logger) { super(configFile, logger); } @Override public void reload() { super.reload(); loadFields(); } private void loadFields() { addCustomEnchantsToLore = getBoolean("add-custom-enchants-into-lore", true); addTeamColorToPrefix = getBoolean("add-teamcolor-to-prefix", true); fix1_13FacePlayer = getBoolean("fix-1_13-face-player", false); fix1_13FormattedInventoryTitles = getBoolean("fix-formatted-inventory-titles", true); alwaysShowOriginalMobName = getBoolean("always-show-original-mob-name", true); handlePingsAsInvAcknowledgements = getBoolean("handle-pings-as-inv-acknowledgements", false); bedrockAtY0 = getBoolean("bedrock-at-y-0", false); sculkShriekersToCryingObsidian = getBoolean("sculk-shriekers-to-crying-obsidian", false); scaffoldingToWater = getBoolean("scaffolding-to-water", false); mapDarknessEffect = getBoolean("map-darkness-effect", true); mapCustomModelData = getBoolean("map-custom-model-data", true); mapDisplayEntities = getBoolean("map-display-entities", true); suppressEmulationWarnings = getBoolean("suppress-emulation-warnings", false); dialogsViaChests = getBoolean("dialogs-via-chests", true); dialogStyleConfig = loadDialogStyleConfig(getSection("dialog-style")); codeOfConductAsDialog = getBoolean("code-of-conduct-as-dialog", true); passOriginalItemNameToResourcePacks = getBoolean("pass-original-item-name-to-resource-packs", true); } private DialogStyleConfig loadDialogStyleConfig(final ConfigSection section) { return new DialogStyleConfig( getString(section, "page-navigation-title", "&9&lPage navigation"), getString(section, "page-navigation-next", "&9Left click: &6Go to next page"), getString(section, "page-navigation-previous", "&9Right click: &6Go to previous page"), getString(section, "increase-value", "&9Left click: &6Increase value by %s"), getString(section, "decrease-value", "&9Right click: &6Decrease value by %s"), getString(section, "value-range", "&7(Value between &a%s &7and &a%s&7)"), getString(section, "next-option", "&9Left click: &6Go to next option"), getString(section, "previous-option", "&9Right click: &6Go to previous option"), getString(section, "current-value", "&7Current value: &a%s"), getString(section, "edit-value", "&9Left click: &6Edit text"), getString(section, "set-text", "&9Left click/close: &6Set text"), getString(section, "close", "&9Left click: &6Close"), getString(section, "toggle-value", "&9Left click: &6Toggle value") ); } protected String getString(final ConfigSection section, final String key, final String def) { return ChatColorUtil.translateAlternateColorCodes(section.getString(key, def)); } @Override public boolean addCustomEnchantsToLore() { return addCustomEnchantsToLore; } @Override public boolean addTeamColorTo1_13Prefix() { return addTeamColorToPrefix; } @Override public boolean isFix1_13FacePlayer() { return fix1_13FacePlayer; } @Override public boolean fix1_13FormattedInventoryTitle() { return fix1_13FormattedInventoryTitles; } @Override public boolean alwaysShowOriginalMobName() { return alwaysShowOriginalMobName; } @Override public boolean handlePingsAsInvAcknowledgements() { return handlePingsAsInvAcknowledgements || Boolean.getBoolean("com.viaversion.handlePingsAsInvAcknowledgements"); } @Override public boolean bedrockAtY0() { return bedrockAtY0; } @Override public boolean sculkShriekerToCryingObsidian() { return sculkShriekersToCryingObsidian; } @Override public boolean scaffoldingToWater() { return scaffoldingToWater; } @Override public boolean mapDarknessEffect() { return mapDarknessEffect; } @Override public boolean mapCustomModelData() { return mapCustomModelData; } @Override public boolean mapDisplayEntities() { return mapDisplayEntities; } @Override public boolean suppressEmulationWarnings() { return suppressEmulationWarnings; } @Override public boolean dialogsViaChests() { return dialogsViaChests; } @Override public DialogStyleConfig dialogStyleConfig() { return dialogStyleConfig; } @Override public boolean codeOfConductAsDialog() { return codeOfConductAsDialog; } @Override public boolean passOriginalItemNameToResourcePacks() { return passOriginalItemNameToResourcePacks; } @Override public URL getDefaultConfigURL() { return getClass().getClassLoader().getResource("assets/viabackwards/config.yml"); } @Override public InputStream getDefaultConfigInputStream() { return getClass().getClassLoader().getResourceAsStream("assets/viabackwards/config.yml"); } @Override protected void handleConfig(Map map) { } @Override public List getUnsupportedOptions() { return Collections.emptyList(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/ViaBackwardsPlatformImpl.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards; import com.viaversion.viabackwards.api.ViaBackwardsPlatform; import com.viaversion.viaversion.api.Via; import java.io.File; import java.util.logging.Logger; public class ViaBackwardsPlatformImpl implements ViaBackwardsPlatform { private final Logger logger; public ViaBackwardsPlatformImpl() { logger = Via.getPlatform().createLogger("ViaBackwards"); init(new File(getDataFolder(), "viabackwards.yml")); enable(); } @Override public Logger getLogger() { return logger; } @Override public void disable() { } @Override public File getDataFolder() { return Via.getPlatform().getDataFolder(); } @Override public boolean isOutdated() { return false; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/BackwardsProtocol.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter; import com.viaversion.viabackwards.protocol.registration.BackwardsRegistrations; import com.viaversion.viabackwards.utils.BackwardsProtocolLogger; import com.viaversion.viaversion.api.protocol.AbstractProtocol; import com.viaversion.viaversion.api.protocol.Protocol; import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; import com.viaversion.viaversion.api.protocol.packet.ServerboundPacketType; import com.viaversion.viaversion.util.ProtocolLogger; import org.checkerframework.checker.nullness.qual.Nullable; public abstract class BackwardsProtocol extends AbstractProtocol { @Deprecated protected BackwardsProtocol() { } protected BackwardsProtocol(@Nullable Class oldClientboundPacketEnum, @Nullable Class clientboundPacketEnum, @Nullable Class oldServerboundPacketEnum, @Nullable Class serverboundPacketEnum) { super(oldClientboundPacketEnum, clientboundPacketEnum, oldServerboundPacketEnum, serverboundPacketEnum); } @Override protected void applySharedRegistrations() { super.applySharedRegistrations(); BackwardsRegistrations.registrations().applyMatching(this); } @Override protected ProtocolLogger createLogger() { return new BackwardsProtocolLogger(getClass()); } @Override public @Nullable Class> dependsOn() { return getMappingData() != null ? getMappingData().getViaVersionProtocolClass() : null; } @Override public @Nullable BackwardsMappingData getMappingData() { return null; } @Override public @Nullable BackwardsRegistryRewriter getRegistryDataRewriter() { return null; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/DialogStyleConfig.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api; public record DialogStyleConfig(String pageNavigationTitle, String pageNavigationNext, String pageNavigationPrevious, String increaseValue, String decreaseValue, String valueRange, String nextOption, String previousOption, String currentValue, String editValue, String setText, String close, String toggleValue) { } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/ViaBackwardsConfig.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api; import com.viaversion.viaversion.api.configuration.Config; public interface ViaBackwardsConfig extends Config { /** * Mimics name and level of a custom enchant through the item's lore. * * @return true if enabled */ boolean addCustomEnchantsToLore(); /** * Writes the color of the scoreboard team after the prefix. * * @return true if enabled */ boolean addTeamColorTo1_13Prefix(); /** * Converts the new 1.13 face player packets to look packets. * * @return true if enabled */ boolean isFix1_13FacePlayer(); /** * Converts the 1.13 face look-at packet for 1.12- players. Requires a bit of extra caching. * * @return true if enabled */ boolean fix1_13FormattedInventoryTitle(); /** * Always shows the original mob's name instead of only when hovering over them with the cursor. * * @return true if enabled */ boolean alwaysShowOriginalMobName(); /** * Sends inventory acknowledgement packets to act as a replacement for ping packets for sub 1.17 clients. * This only takes effect for ids in the short range. Useful for anticheat compatibility. * * @return true if enabled */ boolean handlePingsAsInvAcknowledgements(); /** * Adds bedrock at y=0 for sub 1.17 clients. * * @return true if enabled */ boolean bedrockAtY0(); /** * Shows sculk shriekers as crying obsidian for 1.18.2 clients on 1.19+ servers. This fixes collision and block breaking issues. * * @return true if enabled */ boolean sculkShriekerToCryingObsidian(); /** * Shows scaffolding as water for 1.13.2 clients on 1.14+ servers. This fixes collision issues. * * @return true if enabled */ boolean scaffoldingToWater(); /** * Maps the darkness effect to blindness for 1.18.2 clients on 1.19+ servers. * * @return true if enabled */ boolean mapDarknessEffect(); /** * If enabled, 1.21.3 clients will receive the first float of 1.21.4+ custom model data as int. Disable if you handle this change yourself. * * @return true if enabled */ boolean mapCustomModelData(); /** * If enabled, 1.19.3 clients will receive display entities as armor stands with custom entity data on 1.19.4+ servers. * * @return true if enabled */ boolean mapDisplayEntities(); /** * Suppresses warnings of missing emulations for certain features that are not supported (e.g. world height in 1.17+). * * @return true if enabled */ boolean suppressEmulationWarnings(); /** * If enabled, dialogs will be shown via chest inventories for 1.21.5 clients on 1.21.6+ servers. * * @return true if enabled */ boolean dialogsViaChests(); /** * Returns the dialog style configuration. * * @return the dialog style configuration */ DialogStyleConfig dialogStyleConfig(); /** * If true, the code of conduct will be displayed as a dialog for 1.21.7 clients on 1.21.9+ servers. * * @return true if enabled */ boolean codeOfConductAsDialog(); /** * Injects the original vanilla 1.21.4+ item name into custom_model_data strings for resource packs. * Disable if your server creates custom items using modern items as their base. * Tip: For server custom items, base them on items in the game before 1.21.4 (e.g. saddle) to ensure compatibility. * * @return true if enabled */ boolean passOriginalItemNameToResourcePacks(); } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/ViaBackwardsPlatform.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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.registration.BackwardsRegistrations; 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_11to1_10.Protocol1_11To1_10; import com.viaversion.viabackwards.protocol.v1_12_1to1_12.Protocol1_12_1To1_12; 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_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_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_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_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_20to1_19_4.Protocol1_20To1_19_4; import com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.Protocol1_21_11To1_21_9; 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_21_6to1_21_5.Protocol1_21_6To1_21_5; 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_21_9to1_21_7.Protocol1_21_9To1_21_7; 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.protocol.v26_1to1_21_11.Protocol26_1To1_21_11; import com.viaversion.viabackwards.utils.VersionInfo; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.protocol.ProtocolManager; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.update.Version; import java.io.File; import java.util.Arrays; import java.util.logging.Logger; public interface ViaBackwardsPlatform { String MINIMUM_VV_VERSION = "5.8.1"; default void init(final File configFile) { init(new ViaBackwardsConfig(configFile, getLogger())); } /** * Initialize ViaBackwards. */ default void init(final com.viaversion.viabackwards.api.ViaBackwardsConfig config) { config.reload(); Via.getManager().getConfigurationProvider().register(config); ViaBackwards.init(this, config); if (isOutdated()) { disable(); return; } Via.getManager().getSubPlatforms().add(VersionInfo.getImplementationVersion()); getLogger().info("Loading translations..."); TranslatableMappings.loadTranslatables(); getLogger().info("Registering protocols..."); BackwardsRegistrations.apply(); final ProtocolManager protocolManager = Via.getManager().getProtocolManager(); protocolManager.registerProtocol(new Protocol1_9_1To1_9(), ProtocolVersion.v1_9, ProtocolVersion.v1_9_1); protocolManager.registerProtocol(new Protocol1_9_3To1_9_1(), Arrays.asList(ProtocolVersion.v1_9_1, ProtocolVersion.v1_9_2), ProtocolVersion.v1_9_3); protocolManager.registerProtocol(new Protocol1_10To1_9_3(), ProtocolVersion.v1_9_3, ProtocolVersion.v1_10); protocolManager.registerProtocol(new Protocol1_11To1_10(), ProtocolVersion.v1_10, ProtocolVersion.v1_11); protocolManager.registerProtocol(new Protocol1_11_1To1_11(), ProtocolVersion.v1_11, ProtocolVersion.v1_11_1); protocolManager.registerProtocol(new Protocol1_12To1_11_1(), ProtocolVersion.v1_11_1, ProtocolVersion.v1_12); protocolManager.registerProtocol(new Protocol1_12_1To1_12(), ProtocolVersion.v1_12, ProtocolVersion.v1_12_1); protocolManager.registerProtocol(new Protocol1_12_2To1_12_1(), ProtocolVersion.v1_12_1, ProtocolVersion.v1_12_2); protocolManager.registerProtocol(new Protocol1_13To1_12_2(), ProtocolVersion.v1_12_2, ProtocolVersion.v1_13); protocolManager.registerProtocol(new Protocol1_13_1To1_13(), ProtocolVersion.v1_13, ProtocolVersion.v1_13_1); protocolManager.registerProtocol(new Protocol1_13_2To1_13_1(), ProtocolVersion.v1_13_1, ProtocolVersion.v1_13_2); protocolManager.registerProtocol(new Protocol1_14To1_13_2(), ProtocolVersion.v1_13_2, ProtocolVersion.v1_14); protocolManager.registerProtocol(new Protocol1_14_1To1_14(), ProtocolVersion.v1_14, ProtocolVersion.v1_14_1); protocolManager.registerProtocol(new Protocol1_14_2To1_14_1(), ProtocolVersion.v1_14_1, ProtocolVersion.v1_14_2); protocolManager.registerProtocol(new Protocol1_14_3To1_14_2(), ProtocolVersion.v1_14_2, ProtocolVersion.v1_14_3); protocolManager.registerProtocol(new Protocol1_14_4To1_14_3(), ProtocolVersion.v1_14_3, ProtocolVersion.v1_14_4); protocolManager.registerProtocol(new Protocol1_15To1_14_4(), ProtocolVersion.v1_14_4, ProtocolVersion.v1_15); protocolManager.registerProtocol(new Protocol1_15_1To1_15(), ProtocolVersion.v1_15, ProtocolVersion.v1_15_1); protocolManager.registerProtocol(new Protocol1_15_2To1_15_1(), ProtocolVersion.v1_15_1, ProtocolVersion.v1_15_2); protocolManager.registerProtocol(new Protocol1_16To1_15_2(), ProtocolVersion.v1_15_2, ProtocolVersion.v1_16); protocolManager.registerProtocol(new Protocol1_16_1To1_16(), ProtocolVersion.v1_16, ProtocolVersion.v1_16_1); protocolManager.registerProtocol(new Protocol1_16_2To1_16_1(), ProtocolVersion.v1_16_1, ProtocolVersion.v1_16_2); protocolManager.registerProtocol(new Protocol1_16_3To1_16_2(), ProtocolVersion.v1_16_2, ProtocolVersion.v1_16_3); protocolManager.registerProtocol(new Protocol1_16_4To1_16_3(), ProtocolVersion.v1_16_3, ProtocolVersion.v1_16_4); protocolManager.registerProtocol(new Protocol1_17To1_16_4(), ProtocolVersion.v1_16_4, ProtocolVersion.v1_17); protocolManager.registerProtocol(new Protocol1_17_1To1_17(), ProtocolVersion.v1_17, ProtocolVersion.v1_17_1); protocolManager.registerProtocol(new Protocol1_18To1_17_1(), ProtocolVersion.v1_17_1, ProtocolVersion.v1_18); protocolManager.registerProtocol(new Protocol1_18_2To1_18(), ProtocolVersion.v1_18, ProtocolVersion.v1_18_2); protocolManager.registerProtocol(new Protocol1_19To1_18_2(), ProtocolVersion.v1_18_2, ProtocolVersion.v1_19); protocolManager.registerProtocol(new Protocol1_19_1To1_19(), ProtocolVersion.v1_19, ProtocolVersion.v1_19_1); protocolManager.registerProtocol(new Protocol1_19_3To1_19_1(), ProtocolVersion.v1_19_1, ProtocolVersion.v1_19_3); protocolManager.registerProtocol(new Protocol1_19_4To1_19_3(), ProtocolVersion.v1_19_3, ProtocolVersion.v1_19_4); protocolManager.registerProtocol(new Protocol1_20To1_19_4(), ProtocolVersion.v1_19_4, ProtocolVersion.v1_20); protocolManager.registerProtocol(new Protocol1_20_2To1_20(), ProtocolVersion.v1_20, ProtocolVersion.v1_20_2); protocolManager.registerProtocol(new Protocol1_20_3To1_20_2(), ProtocolVersion.v1_20_2, ProtocolVersion.v1_20_3); protocolManager.registerProtocol(new Protocol1_20_5To1_20_3(), ProtocolVersion.v1_20_3, ProtocolVersion.v1_20_5); protocolManager.registerProtocol(new Protocol1_21To1_20_5(), ProtocolVersion.v1_20_5, ProtocolVersion.v1_21); protocolManager.registerProtocol(new Protocol1_21_2To1_21(), ProtocolVersion.v1_21, ProtocolVersion.v1_21_2); protocolManager.registerProtocol(new Protocol1_21_4To1_21_2(), ProtocolVersion.v1_21_2, ProtocolVersion.v1_21_4); 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); protocolManager.registerProtocol(new Protocol1_21_9To1_21_7(), ProtocolVersion.v1_21_7, ProtocolVersion.v1_21_9); protocolManager.registerProtocol(new Protocol1_21_11To1_21_9(), ProtocolVersion.v1_21_9, ProtocolVersion.v1_21_11); protocolManager.registerProtocol(new Protocol26_1To1_21_11(), ProtocolVersion.v1_21_11, ProtocolVersion.v26_1); } default void enable() { final ProtocolVersion protocolVersion = Via.getAPI().getServerVersion().highestSupportedProtocolVersion(); 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); } } /** * Logger provided by the platform. * * @return logger instance */ Logger getLogger(); default boolean isOutdated() { String vvVersion = Via.getPlatform().getPluginVersion(); if (vvVersion != null && new Version(vvVersion).compareTo(new Version(MINIMUM_VV_VERSION + "--")) < 0) { getLogger().severe("================================"); getLogger().severe("YOUR VIAVERSION IS OUTDATED (you are running " + vvVersion + ")"); getLogger().severe("PLEASE USE VIAVERSION " + MINIMUM_VV_VERSION + " OR NEWER"); getLogger().severe("LINK: https://ci.viaversion.com/"); getLogger().severe("VIABACKWARDS WILL NOW DISABLE"); getLogger().severe("================================"); return true; } return false; } /** * Disable the plugin. */ void disable(); /** * Returns ViaBackwards's data folder. * * @return data folder */ File getDataFolder(); } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/data/BackwardsMappingData.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.data; import com.google.common.base.Preconditions; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.data.BiMappings; import com.viaversion.viaversion.api.data.IdentityMappings; import com.viaversion.viaversion.api.data.MappingData; import com.viaversion.viaversion.api.data.MappingDataBase; import com.viaversion.viaversion.api.data.Mappings; import com.viaversion.viaversion.api.protocol.Protocol; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectArrayMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectOpenHashMap; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; public class BackwardsMappingData extends MappingDataBase { private final Class> vvProtocolClass; protected Int2ObjectMap backwardsItemMappings; private Map entityNames; private Int2ObjectMap enchantmentNames; public BackwardsMappingData(final String unmappedVersion, final String mappedVersion) { this(unmappedVersion, mappedVersion, null); } public BackwardsMappingData(final String unmappedVersion, final String mappedVersion, @Nullable final Class> vvProtocolClass) { super(unmappedVersion, mappedVersion); Preconditions.checkArgument(vvProtocolClass == null || !BackwardsProtocol.class.isAssignableFrom(vvProtocolClass)); this.vvProtocolClass = vvProtocolClass; } @Override protected void loadExtras(final CompoundTag data) { final CompoundTag itemNames = data.getCompoundTag("itemnames"); if (itemNames != null) { Preconditions.checkNotNull(itemMappings); backwardsItemMappings = new Int2ObjectOpenHashMap<>(itemNames.size()); final CompoundTag extraItemData = data.getCompoundTag("itemdata"); for (final Map.Entry entry : itemNames.entrySet()) { final StringTag name = (StringTag) entry.getValue(); final int id = Integer.parseInt(entry.getKey()); Integer customModelData = null; if (extraItemData != null && extraItemData.contains(entry.getKey())) { final CompoundTag entryTag = extraItemData.getCompoundTag(entry.getKey()); final NumberTag customModelDataTag = entryTag.getNumberTag("custom_model_data"); customModelData = customModelDataTag != null ? customModelDataTag.asInt() : null; } backwardsItemMappings.put(id, new MappedItem(getNewItemId(id), name.getValue(), customModelData)); } } this.entityNames = loadNameByStringMappings(data, "entitynames"); this.enchantmentNames = loadNameByIdMappings(data, "enchantmentnames"); } private @Nullable Map loadNameByStringMappings(final CompoundTag data, final String key) { final CompoundTag nameMappings = data.getCompoundTag(key); if (nameMappings == null) { return null; } final Map map = new HashMap<>(nameMappings.size()); for (final Map.Entry entry : nameMappings.entrySet()) { final StringTag mappedTag = (StringTag) entry.getValue(); map.put(entry.getKey(), mappedTag.getValue()); } return map; } private @Nullable Int2ObjectMap loadNameByIdMappings(final CompoundTag data, final String key) { final CompoundTag nameMappings = data.getCompoundTag(key); if (nameMappings == null) { return null; } final Int2ObjectMap map = new Int2ObjectArrayMap<>(nameMappings.size()); for (final Map.Entry entry : nameMappings.entrySet()) { final StringTag mappedTag = (StringTag) entry.getValue(); map.put(Integer.parseInt(entry.getKey()), mappedTag.getValue()); } return map; } @Override protected @Nullable BiMappings loadBiMappings(final CompoundTag data, final String key) { if (key.equals("items") && vvProtocolClass != null) { Mappings mappings = super.loadMappings(data, key); final MappingData mappingData = Via.getManager().getProtocolManager().getProtocol(vvProtocolClass).getMappingData(); if (mappingData != null && mappingData.getItemMappings() != null) { final BiMappings vvItemMappings = mappingData.getItemMappings(); if (mappings == null) { mappings = new IdentityMappings(vvItemMappings.mappedSize(), vvItemMappings.size()); } return ItemMappings.of(mappings, vvItemMappings); } } return super.loadBiMappings(data, key); } /** * @see #getMappedItem(int) for custom backwards mappings */ @Override public int getNewItemId(final int id) { // Don't warn on missing here return this.itemMappings.getNewId(id); } @Override public int getNewBlockId(final int id) { // Don't warn on missing here return this.blockMappings.getNewId(id); } @Override public int getOldItemId(final int id) { // Warn on missing return checkValidity(id, this.itemMappings.inverse().getNewId(id), "item"); } @Override public int getNewAttributeId(final int id) { return this.attributeMappings.getNewId(id); } public @Nullable MappedItem getMappedItem(final int id) { return backwardsItemMappings != null ? backwardsItemMappings.get(id) : null; } public @Nullable String getMappedNamedSound(final String id) { return getFullSoundMappings().mappedIdentifier(id); } public @Nullable String mappedEntityName(final String entityName) { if (entityNames == null) { getLogger().log(Level.SEVERE, "No entity mappings found when requesting them for " + entityName, new RuntimeException()); return null; } return entityNames.get(entityName); } public @Nullable String mappedEnchantmentName(final int enchantmentId) { if (enchantmentNames == null) { ViaBackwards.getPlatform().getLogger().log(Level.SEVERE, "No enchantment name mappings found when requesting " + enchantmentId, new RuntimeException()); return null; } return enchantmentNames.get(enchantmentId); } public @Nullable Int2ObjectMap getBackwardsItemMappings() { return backwardsItemMappings; } public @Nullable Class> getViaVersionProtocolClass() { return vvProtocolClass; } @Override protected Logger getLogger() { return ViaBackwards.getPlatform().getLogger(); } @Override protected @Nullable CompoundTag readMappingsFile(final String name) { return BackwardsMappingDataLoader.INSTANCE.loadNBTFromDir(name); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/data/BackwardsMappingDataLoader.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.data; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viaversion.api.data.MappingDataLoader; import java.io.File; import java.io.IOException; import java.util.Map; import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; public class BackwardsMappingDataLoader extends MappingDataLoader { public static final BackwardsMappingDataLoader INSTANCE = new BackwardsMappingDataLoader(BackwardsMappingDataLoader.class, "assets/viabackwards/data/"); public BackwardsMappingDataLoader(final Class dataLoaderClass, final String dataPath) { super(dataLoaderClass, dataPath); } /** * Returns nbt data from the plugin folder or packed assets. * If a file with the same name exists in the plugin folder, the data of the original packed tag will be merged with the file's tag. * * @param name name of the file * @return nbt data from the plugin folder or packed assets */ public @Nullable CompoundTag loadNBTFromDir(final String name) { final CompoundTag packedData = loadNBT(name); final File file = new File(getDataFolder(), name); if (!file.exists()) { return packedData; } getLogger().info("Loading " + name + " from plugin folder"); try { final CompoundTag fileData = MAPPINGS_READER.read(file.toPath(), false); return mergeTags(packedData, fileData); } catch (final IOException e) { throw new RuntimeException(e); } } private CompoundTag mergeTags(final CompoundTag original, final CompoundTag extra) { for (final Map.Entry entry : extra.entrySet()) { if (entry.getValue() instanceof CompoundTag) { // For compound tags, don't replace the entire tag final CompoundTag originalEntry = original.getCompoundTag(entry.getKey()); if (originalEntry != null) { mergeTags(originalEntry, (CompoundTag) entry.getValue()); continue; } } original.put(entry.getKey(), entry.getValue()); } return original; } @Override public Logger getLogger() { return ViaBackwards.getPlatform().getLogger(); } @Override public File getDataFolder() { return ViaBackwards.getPlatform().getDataFolder(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/data/ItemMappings.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.data; import com.viaversion.viaversion.api.data.BiMappingsBase; import com.viaversion.viaversion.api.data.Mappings; public final class ItemMappings extends BiMappingsBase { private ItemMappings(final Mappings mappings, final Mappings inverse) { super(mappings, inverse); } public static ItemMappings of(final Mappings mappings, final Mappings inverse) { return new ItemMappings(mappings, inverse); } @Override public void setNewId(final int id, final int mappedId) { // Only set one-way mappings.setNewId(id, mappedId); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/data/MappedItem.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.data; import com.viaversion.nbt.tag.Tag; import com.viaversion.viaversion.util.ComponentUtil; import org.checkerframework.checker.nullness.qual.Nullable; public class MappedItem { private final int id; private final String jsonName; private final Tag tagName; private final Integer customModelData; public MappedItem(final int id, final String name) { this(id, name, null); } public MappedItem(final int id, final String name, @Nullable final Integer customModelData) { this.id = id; this.jsonName = ComponentUtil.legacyToJsonString("§f" + name, true); this.tagName = ComponentUtil.jsonStringToTag(jsonName); this.customModelData = customModelData; } public int id() { return id; } public String jsonName() { return jsonName; } public Tag tagName() { return tagName; } public @Nullable Integer customModelData() { return customModelData; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/data/MappedLegacyBlockItem.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.data; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viaversion.util.IdAndData; import org.checkerframework.checker.nullness.qual.Nullable; public class MappedLegacyBlockItem { private final int id; private final short data; private final String name; private final IdAndData block; private BlockEntityHandler blockEntityHandler; public MappedLegacyBlockItem(int id) { this(id, (short) -1, null, Type.ITEM); } public MappedLegacyBlockItem(int id, short data, @Nullable String name, Type type) { this.id = id; this.data = data; this.name = name != null ? "§f" + name : null; this.block = type != Type.ITEM ? data != -1 ? new IdAndData(id, data) : new IdAndData(id) : null; } public int getId() { return id; } public short getData() { return data; } public String getName() { return name; } public IdAndData getBlock() { return block; } public boolean hasBlockEntityHandler() { return blockEntityHandler != null; } public @Nullable BlockEntityHandler getBlockEntityHandler() { return blockEntityHandler; } public void setBlockEntityHandler(@Nullable BlockEntityHandler blockEntityHandler) { this.blockEntityHandler = blockEntityHandler; } @FunctionalInterface public interface BlockEntityHandler { void handleCompoundTag(int block, CompoundTag tag); } public enum Type { ITEM("items"), BLOCK_ITEM("block-items"), BLOCK("blocks"); final String name; Type(final String name) { this.name = name; } public String getName() { return name; } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/data/TranslatableMappings.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.data; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viaversion.api.protocol.Protocol; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.libs.gson.JsonObject; import java.util.HashMap; import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; public final class TranslatableMappings { private static final Map> TRANSLATABLES = new HashMap<>(); public static void loadTranslatables() { if (!TRANSLATABLES.isEmpty()) { throw new IllegalStateException("Translatables already loaded!"); } fillTranslatables(BackwardsMappingDataLoader.INSTANCE.loadFromDataDir("translation-mappings.json"), TRANSLATABLES); } public static void fillTranslatables(final JsonObject jsonObject, final Map> translatables) { for (final Map.Entry entry : jsonObject.entrySet()) { final Map versionMappings = new HashMap<>(); translatables.put(entry.getKey(), versionMappings); for (final Map.Entry translationEntry : entry.getValue().getAsJsonObject().entrySet()) { versionMappings.put(translationEntry.getKey(), translationEntry.getValue().getAsString()); } } } public static Map translatablesFor(final Protocol protocol) { final String version = protocol.getClass().getSimpleName() .replace("Protocol", "") .split("To")[0] .replace("_", "."); return translatablesFor(version); } public static Map translatablesFor(final String version) { final Map translatableMappings = getTranslatableMappings(version); if (translatableMappings == null) { ViaBackwards.getPlatform().getLogger().warning("Missing " + version + " translatables!"); return new HashMap<>(); } return translatableMappings; } public static @Nullable Map getTranslatableMappings(final String sectionIdentifier) { return TRANSLATABLES.get(sectionIdentifier); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/entities/storage/EntityObjectData.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.entities.storage; import com.viaversion.viabackwards.api.BackwardsProtocol; public class EntityObjectData extends EntityReplacement { private final int objectData; public EntityObjectData(BackwardsProtocol protocol, String key, int id, int replacementId, int objectData) { super(protocol, key, id, replacementId); this.objectData = objectData; } @Override public boolean isObjectType() { return true; } @Override public int objectData() { return objectData; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/entities/storage/EntityPositionHandler.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.entities.storage; import com.viaversion.viabackwards.api.rewriters.EntityRewriterBase; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.entity.StoredEntityData; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.util.ProtocolLogger; import java.util.function.Supplier; public class EntityPositionHandler { public static final double RELATIVE_MOVE_FACTOR = 32 * 128; private final EntityRewriterBase entityRewriter; private final Class storageClass; private final Supplier storageSupplier; private boolean warnedForMissingEntity; public EntityPositionHandler(EntityRewriterBase entityRewriter, Class storageClass, Supplier storageSupplier) { this.entityRewriter = entityRewriter; this.storageClass = storageClass; this.storageSupplier = storageSupplier; } public void cacheEntityPosition(PacketWrapper wrapper, boolean create, boolean relative) { cacheEntityPosition(wrapper, wrapper.get(Types.DOUBLE, 0), wrapper.get(Types.DOUBLE, 1), wrapper.get(Types.DOUBLE, 2), create, relative); } public void cacheEntityPosition(PacketWrapper wrapper, double x, double y, double z, boolean create, boolean relative) { cacheEntityPosition(wrapper, wrapper.get(Types.VAR_INT, 0), x, y, z, create, relative); } public void cacheEntityPosition(PacketWrapper wrapper, int entityId, double x, double y, double z, boolean create, boolean relative) { StoredEntityData storedEntity = entityRewriter.tracker(wrapper.user()).entityData(entityId); if (storedEntity == null) { if (Via.getManager().isDebug()) { // There is too many plugins violating this out there, and reading seems to be hard! :> ProtocolLogger logger = entityRewriter.protocol().getLogger(); logger.warning("Stored entity with id " + entityId + " missing at position: " + x + " - " + y + " - " + z + " in " + storageClass.getSimpleName()); if (entityId == -1 && x == 0 && y == 0 && z == 0) { logger.warning("DO NOT REPORT THIS TO VIA, THIS IS A PLUGIN ISSUE"); } else if (!warnedForMissingEntity) { warnedForMissingEntity = true; logger.warning("This is very likely caused by a plugin sending a teleport packet for an entity outside of the player's range."); } } return; } EntityPositionStorage positionStorage; if (create) { positionStorage = storageSupplier.get(); storedEntity.put(positionStorage); } else { positionStorage = storedEntity.get(storageClass); if (positionStorage == null) { entityRewriter.protocol().getLogger().warning("Stored entity with id " + entityId + " missing " + storageClass.getSimpleName()); return; } } if (relative) { positionStorage.addRelativePosition(x, y, z); } else { positionStorage.setPosition(x, y, z); } } public EntityPositionStorage getStorage(UserConnection user, int entityId) { StoredEntityData storedEntity = entityRewriter.tracker(user).entityData(entityId); EntityPositionStorage entityStorage; if (storedEntity == null || (entityStorage = storedEntity.get(EntityPositionStorage.class)) == null) { entityRewriter.protocol().getLogger().warning("Untracked entity with id " + entityId + " in " + storageClass.getSimpleName()); return null; } return entityStorage; } public static void writeFacingAngles(PacketWrapper wrapper, double x, double y, double z, double targetX, double targetY, double targetZ) { double dX = targetX - x; double dY = targetY - y; double dZ = targetZ - z; double r = Math.sqrt(dX * dX + dY * dY + dZ * dZ); double yaw = -Math.atan2(dX, dZ) / Math.PI * 180; if (yaw < 0) { yaw = 360 + yaw; } double pitch = -Math.asin(dY / r) / Math.PI * 180; wrapper.write(Types.BYTE, (byte) (yaw * 256f / 360f)); wrapper.write(Types.BYTE, (byte) (pitch * 256f / 360f)); } public static void writeFacingDegrees(PacketWrapper wrapper, double x, double y, double z, double targetX, double targetY, double targetZ) { double dX = targetX - x; double dY = targetY - y; double dZ = targetZ - z; double r = Math.sqrt(dX * dX + dY * dY + dZ * dZ); double yaw = -Math.atan2(dX, dZ) / Math.PI * 180; if (yaw < 0) { yaw = 360 + yaw; } double pitch = -Math.asin(dY / r) / Math.PI * 180; wrapper.write(Types.FLOAT, (float) yaw); wrapper.write(Types.FLOAT, (float) pitch); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/entities/storage/EntityPositionStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.entities.storage; public abstract class EntityPositionStorage { private double x; private double y; private double z; public double x() { return x; } public double y() { return y; } public double z() { return z; } public void setPosition(double x, double y, double z) { this.x = x; this.y = y; this.z = z; } public void addRelativePosition(double relX, double relY, double relZ) { this.x += relX; this.y += relY; this.z += relZ; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/entities/storage/EntityReplacement.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.entities.storage; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.util.ComponentUtil; import java.util.Locale; import org.checkerframework.checker.nullness.qual.Nullable; public class EntityReplacement { private final BackwardsProtocol protocol; private final int id; private final int replacementId; private final String key; private ComponentType componentType = ComponentType.NONE; private EntityDataCreator defaultData; public EntityReplacement(BackwardsProtocol protocol, EntityType type, int replacementId) { this(protocol, type.name(), type.getId(), replacementId); } public EntityReplacement(BackwardsProtocol protocol, String key, int id, int replacementId) { this.protocol = protocol; this.id = id; this.replacementId = replacementId; this.key = key.toLowerCase(Locale.ROOT); } public EntityReplacement jsonName() { this.componentType = ComponentType.JSON; return this; } public EntityReplacement tagName() { this.componentType = ComponentType.TAG; return this; } public EntityReplacement plainName() { this.componentType = ComponentType.PLAIN; return this; } public EntityReplacement spawnEntityData(EntityDataCreator handler) { this.defaultData = handler; return this; } public boolean hasBaseData() { return this.defaultData != null; } public int typeId() { return id; } /** * @return custom mobname, can be either a String or a JsonElement */ public @Nullable Object entityName() { if (componentType == ComponentType.NONE) { return null; } final String name = protocol.getMappingData().mappedEntityName(key); if (name == null) { return null; } if (componentType == ComponentType.JSON) { return ComponentUtil.legacyToJson(name); } else if (componentType == ComponentType.TAG) { return new StringTag(name); } return name; } public int replacementId() { return replacementId; } public @Nullable EntityDataCreator defaultData() { return defaultData; } public boolean isObjectType() { return false; } public int objectData() { return -1; } @Override public String toString() { return "EntityReplacement{" + "protocol=" + protocol + ", id=" + id + ", replacementId=" + replacementId + ", key='" + key + '\'' + ", componentType=" + componentType + ", defaultData=" + defaultData + '}'; } @FunctionalInterface public interface EntityDataCreator { void createData(WrappedEntityData storage); } private enum ComponentType { PLAIN, JSON, TAG, NONE } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/entities/storage/PlayerPositionStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.entities.storage; import com.viaversion.viaversion.api.connection.StorableObject; public abstract class PlayerPositionStorage implements StorableObject { private double x; private double y; private double z; protected PlayerPositionStorage() { } public double x() { return x; } public double y() { return y; } public double z() { return z; } public void setX(final double x) { this.x = x; } public void setY(final double y) { this.y = y; } public void setZ(final double z) { this.z = z; } public void setPosition(double x, double y, double z) { this.x = x; this.y = y; this.z = z; } public void addRelativePosition(double relX, double relY, double relZ) { this.x += relX; this.y += relY; this.z += relZ; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/entities/storage/WrappedEntityData.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.entities.storage; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; public record WrappedEntityData(List entityDataList) { public boolean has(EntityData data) { return this.entityDataList.contains(data); } public void remove(EntityData data) { this.entityDataList.remove(data); } public void remove(int index) { entityDataList.removeIf(data -> data.id() == index); } public void add(EntityData data) { this.entityDataList.add(data); } public @Nullable EntityData get(int index) { for (EntityData data : this.entityDataList) { if (index == data.id()) { return data; } } return null; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsItemRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; import com.viaversion.nbt.tag.ByteTag; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.MappedItem; import com.viaversion.viabackwards.item.DataItemWithExtras; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; import com.viaversion.viaversion.api.protocol.packet.ServerboundPacketType; import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.libs.gson.JsonElement; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; public class BackwardsItemRewriter> extends BackwardsItemRewriterBase { public BackwardsItemRewriter(T protocol, Type itemType, Type itemArrayType) { super(protocol, itemType, itemArrayType, true); } public BackwardsItemRewriter(T protocol, Type itemType, Type itemArrayType, Type mappedItemType, Type mappedItemArrayType) { super(protocol, itemType, itemArrayType, mappedItemType, mappedItemArrayType, true); } @Override public @Nullable Item handleItemToClient(UserConnection connection, @Nullable Item item) { if (item == null) { return null; } CompoundTag display = item.tag() != null ? item.tag().getCompoundTag("display") : null; if (protocol.getComponentRewriter() != null && display != null) { final DataItemWithExtras fullItem; if (item instanceof DataItemWithExtras) { fullItem = (DataItemWithExtras) item; } else { item = fullItem = new DataItemWithExtras(item); } // Handle name and lore components final JsonElement name = fullItem.name(); if (name != null) { final JsonElement updatedName = name.deepCopy(); protocol.getComponentRewriter().processText(connection, updatedName); if (!updatedName.equals(name)) { final StringTag rawName = fullItem.rawName(); saveStringTag(display, rawName, "Name"); rawName.setValue(updatedName.toString()); } } final List lore = fullItem.lore(); if (lore != null) { boolean changed = false; final ListTag rawLore = fullItem.rawLore(); for (int i = 0; i < lore.size(); i++) { final JsonElement loreEntry = lore.get(i); final JsonElement updatedLoreEntry = loreEntry.deepCopy(); protocol.getComponentRewriter().processText(connection, updatedLoreEntry); if (updatedLoreEntry.equals(loreEntry)) { continue; } if (!changed) { // Backup original lore before doing any modifications changed = true; saveListTag(display, rawLore, "Lore"); } final StringTag rawLoreEntry = rawLore.get(i); rawLoreEntry.setValue(updatedLoreEntry.toString()); } } } MappedItem data = protocol.getMappingData() != null ? protocol.getMappingData().getMappedItem(item.identifier()) : null; if (data == null) { // Just rewrite the id return super.handleItemToClient(connection, item); } if (item.tag() == null) { item.setTag(new CompoundTag()); } // Save original id, set remapped id item.tag().putInt(nbtTagName("id"), item.identifier()); item.setIdentifier(data.id()); // Add custom model data if (data.customModelData() != null && !item.tag().contains("CustomModelData")) { item.tag().putInt("CustomModelData", data.customModelData()); } // Set custom name - only done if there is no original one if (display == null) { item.tag().put("display", display = new CompoundTag()); } if (!display.contains("Name")) { display.put("Name", new StringTag(data.jsonName())); display.put(nbtTagName("customName"), new ByteTag(false)); } return item; } @Override public @Nullable Item handleItemToServer(UserConnection connection, @Nullable Item item) { if (item == null) return null; item = super.handleItemToServer(connection, item); if (item.tag() != null) { Tag originalId = item.tag().remove(nbtTagName("id")); if (originalId instanceof IntTag) { item.setIdentifier(((NumberTag) originalId).asInt()); } } return item; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsItemRewriterBase.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; 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.api.BackwardsProtocol; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; import com.viaversion.viaversion.api.protocol.packet.ServerboundPacketType; import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.rewriter.ItemRewriter; import org.checkerframework.checker.nullness.qual.Nullable; public abstract class BackwardsItemRewriterBase> extends ItemRewriter { protected final boolean jsonNameFormat; protected BackwardsItemRewriterBase(T protocol, Type itemType, Type itemArrayType, Type mappedItemType, Type mappedItemArrayType, boolean jsonFormat) { super(protocol, itemType, itemArrayType, mappedItemType, mappedItemArrayType); this.jsonNameFormat = jsonFormat; } protected BackwardsItemRewriterBase(T protocol, Type itemType, Type itemArrayType, boolean jsonNameFormat) { this(protocol, itemType, itemArrayType, itemType, itemArrayType, jsonNameFormat); } @Override public @Nullable Item handleItemToServer(UserConnection connection, @Nullable Item item) { if (item == null) return null; item = super.handleItemToServer(connection, item); restoreDisplayTag(item); return item; } protected boolean hasBackupTag(CompoundTag tag, String tagName) { return tag.contains(nbtTagName(tagName)); } protected void saveStringTag(CompoundTag tag, StringTag original, String name) { // Multiple places might try to backup data String backupName = nbtTagName(name); if (!tag.contains(backupName)) { tag.putString(backupName, original.getValue()); } } protected void saveListTag(CompoundTag tag, ListTag original, String name) { // Multiple places might try to backup data String backupName = nbtTagName(name); if (!tag.contains(backupName)) { tag.put(backupName, original.copy()); } } protected void restoreDisplayTag(Item item) { if (item.tag() == null) return; CompoundTag display = item.tag().getCompoundTag("display"); if (display != null) { // Remove custom name / restore original name if (display.remove(nbtTagName("customName")) != null) { display.remove("Name"); } else { restoreStringTag(display, "Name"); } // Restore lore restoreListTag(display, "Lore"); } } protected void restoreStringTag(CompoundTag tag, String tagName) { Tag original = tag.remove(nbtTagName(tagName)); if (original instanceof StringTag) { tag.putString(tagName, ((StringTag) original).getValue()); } } protected void restoreListTag(CompoundTag tag, String tagName) { Tag original = tag.remove(nbtTagName(tagName)); if (original instanceof ListTag) { tag.put(tagName, ((ListTag) original).copy()); } } @Override public String nbtTagName() { return "VB|" + protocol.getClass().getSimpleName(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsRegistryRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.RegistryEntry; import com.viaversion.viaversion.rewriter.RegistryDataRewriter; import com.viaversion.viaversion.util.Key; public class BackwardsRegistryRewriter extends RegistryDataRewriter { private final BackwardsProtocol protocol; public BackwardsRegistryRewriter(final BackwardsProtocol protocol) { super(protocol); this.protocol = protocol; } @Override public RegistryEntry[] handle(final UserConnection connection, final String key, final RegistryEntry[] entries) { if (Key.stripMinecraftNamespace(key).equals("worldgen/biome")) { for (final RegistryEntry entry : entries) { final CompoundTag biome = (CompoundTag) entry.tag(); if (biome == null) { continue; } final CompoundTag effects = biome.getCompoundTag("effects"); updateBiomeEffects(effects); } } return super.handle(connection, key, entries); } @Override public void updateJukeboxSongs(final RegistryEntry[] entries) { for (final RegistryEntry entry : entries) { if (entry.tag() == null) { continue; } updateSound((CompoundTag) entry.tag(), "sound_event"); } } private void updateBiomeEffects(final CompoundTag effects) { updateSound(effects.getCompoundTag("mood_sound"), "sound"); updateSound(effects.getCompoundTag("additions_sound"), "sound"); updateSound(effects.getCompoundTag("music"), "sound"); updateSound(effects, "ambient_sound"); } private void updateSound(final CompoundTag tag, final String name) { if (tag == null) { return; } final String sound = tag.getString(name); if (sound == null) { return; } final String mappedSound = protocol.getMappingData().getMappedNamedSound(sound); if (mappedSound == null) { return; } if (mappedSound.isEmpty()) { tag.putString(name, "minecraft:intentionally_empty"); } else { tag.putString(name, mappedSound); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsStructuredItemRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.FloatTag; import com.viaversion.nbt.tag.IntArrayTag; import com.viaversion.nbt.tag.IntTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.data.MappedItem; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.EitherHolder; import com.viaversion.viaversion.api.minecraft.Holder; import com.viaversion.viaversion.api.minecraft.HolderSet; import com.viaversion.viaversion.api.minecraft.SoundEvent; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.item.data.CustomModelData1_21_4; import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; import com.viaversion.viaversion.api.protocol.packet.ServerboundPacketType; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.rewriter.StructuredItemRewriter; import com.viaversion.viaversion.util.ArrayUtil; import com.viaversion.viaversion.util.Key; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Function; import org.checkerframework.checker.nullness.qual.Nullable; public class BackwardsStructuredItemRewriter> extends StructuredItemRewriter { private static final int[] EMPTY_INT_ARRAY = new int[0]; private static final String GLOBAL_MODEL_DATA_MARKER = "VB|injected_cmd"; private final String nbtTagName; public BackwardsStructuredItemRewriter(T protocol) { super(protocol); this.nbtTagName = "VB|" + protocol.getClass().getSimpleName(); } @Override protected void backupInconvertibleData(final UserConnection connection, final Item item, final StructuredDataContainer dataContainer, final CompoundTag backupTag) { super.backupInconvertibleData(connection, item, dataContainer, backupTag); final BackwardsMappingData mappingData = protocol.getMappingData(); final MappedItem mappedItem = mappingData != null ? mappingData.getMappedItem(item.identifier()) : null; if (mappedItem == null) { return; } final CompoundTag customTag = createCustomTag(item); customTag.putInt(nbtTagName("id"), item.identifier()); // Save original id // Add custom model data final boolean addOriginalIdentifier = ViaBackwards.getConfig().passOriginalItemNameToResourcePacks(); if (mappedItem.customModelData() != null || addOriginalIdentifier) { if (connection.getProtocolInfo().protocolVersion().newerThanOrEqualTo(ProtocolVersion.v1_21_4)) { addCustomModelData(item, addOriginalIdentifier, mappedItem, customTag); } else if (mappedItem.customModelData() != null && !dataContainer.has(StructuredDataKey.CUSTOM_MODEL_DATA1_20_5)) { dataContainer.set(StructuredDataKey.CUSTOM_MODEL_DATA1_20_5, mappedItem.customModelData()); } } // Set custom name - only done if there is no original one if (!dataContainer.has(StructuredDataKey.CUSTOM_NAME)) { dataContainer.set(StructuredDataKey.CUSTOM_NAME, mappedItem.tagName()); customTag.putBoolean(nbtTagName("added_custom_name"), true); } } private void addCustomModelData(final Item item, final boolean addOriginalIdentifier, final MappedItem mappedItem, final CompoundTag customTag) { final StructuredDataContainer dataContainer = item.dataContainer(); CustomModelData1_21_4 customModelData = dataContainer.get(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4); if (customModelData == null) { final String[] strings = addOriginalIdentifier ? new String[]{protocol.getMappingData().getFullItemMappings().identifier(item.identifier())} : new String[0]; customModelData = new CustomModelData1_21_4( mappedItem.customModelData() != null ? new float[]{mappedItem.customModelData().floatValue()} : new float[0], new boolean[0], strings, EMPTY_INT_ARRAY ); dataContainer.set(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4, customModelData); // Add one global marker and one for this specific version, so it is removed at the correct protocol customTag.putBoolean(GLOBAL_MODEL_DATA_MARKER, true); customTag.putBoolean(nbtTagName("added_custom_model_data"), true); } else if (addOriginalIdentifier && !customTag.contains(GLOBAL_MODEL_DATA_MARKER)) { final String identifier = protocol.getMappingData().getFullItemMappings().identifier(item.identifier()); dataContainer.set(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4, new CustomModelData1_21_4( customModelData.floats(), customModelData.booleans(), ArrayUtil.add(customModelData.strings(), identifier), customModelData.colors() )); customTag.putBoolean(GLOBAL_MODEL_DATA_MARKER, true); customTag.putString(nbtTagName("injected_custom_model_data"), identifier); } } @Override protected void restoreBackupData(final Item item, final StructuredDataContainer container, final CompoundTag customData) { super.restoreBackupData(item, container, customData); if (removeBackupTag(customData, "id") instanceof final IntTag originalTag) { item.setIdentifier(originalTag.asInt()); removeCustomTag(container, customData); } if (removeBackupTag(customData, "injected_custom_model_data") instanceof StringTag injectedCustomModelData) { customData.remove(GLOBAL_MODEL_DATA_MARKER); container.replace(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4, customModelData -> { final String target = injectedCustomModelData.getValue(); final String[] strings = customModelData.strings(); for (int i = 0; i < strings.length; i++) { if (strings[i].equals(target)) { // Remove the injected string final String[] filteredStrings = ArrayUtil.remove(strings, i); return new CustomModelData1_21_4(customModelData.floats(), customModelData.booleans(), filteredStrings, customModelData.colors()); } } return customModelData; }); } else if (removeBackupTag(customData, "added_custom_model_data") != null) { customData.remove(GLOBAL_MODEL_DATA_MARKER); container.remove(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4); } } protected void saveListTag(CompoundTag tag, ListTag original, String name) { // Multiple places might try to backup data String backupName = nbtTagName(name); if (!tag.contains(backupName)) { tag.put(backupName, original.copy()); } } public @Nullable ListTag removeListTag(CompoundTag tag, String tagName, Class tagType) { String backupName = nbtTagName(tagName); ListTag data = tag.getListTag(backupName, tagType); if (data == null) { return null; } tag.remove(backupName); return data; } protected void saveGenericTagList(CompoundTag tag, List original, String name) { // List tags cannot contain tags of different types, so we have to store them a bit more awkwardly as an indexed compound tag String backupName = nbtTagName(name); if (!tag.contains(backupName)) { CompoundTag output = new CompoundTag(); for (int i = 0; i < original.size(); i++) { output.put(Integer.toString(i), original.get(i)); } tag.put(backupName, output); } } protected List removeGenericTagList(CompoundTag tag, String name) { String backupName = nbtTagName(name); CompoundTag data = tag.getCompoundTag(backupName); if (data == null) { return null; } tag.remove(backupName); return new ArrayList<>(data.values()); } protected Tag holderSetToTag(final HolderSet set) { if (set.hasIds()) { return new IntArrayTag(set.ids()); } else { return new StringTag(set.tagKey()); } } protected HolderSet restoreHolderSet(final CompoundTag tag, final String key) { final Tag savedTag = tag.get(key); if (savedTag == null) { return HolderSet.of(EMPTY_INT_ARRAY); } if (savedTag instanceof StringTag tagKey) { return HolderSet.of(tagKey.getValue()); } else if (savedTag instanceof IntArrayTag idsTag) { return HolderSet.of(idsTag.getValue()); } else { return HolderSet.of(EMPTY_INT_ARRAY); } } protected Tag holderToTag(final Holder holder, final BiConsumer valueSaveFunction) { if (holder.hasId()) { return new IntTag(holder.id()); } else { final CompoundTag savedTag = new CompoundTag(); valueSaveFunction.accept(holder.value(), savedTag); return savedTag; } } protected Tag eitherHolderToTag(final EitherHolder holder, final BiConsumer valueSaveFunction) { if (holder.hasKey()) { return new StringTag(holder.key()); } else { return holderToTag(holder.holder(), valueSaveFunction); } } protected void saveEitherHolderData(final StructuredDataKey> key, final StructuredDataContainer data, final CompoundTag backupTag, final BiConsumer valueSaveFunction) { final EitherHolder holder = data.get(key); if (holder != null) { backupTag.put(key.identifier(), eitherHolderToTag(holder, valueSaveFunction)); } } protected void saveHolderData(final StructuredDataKey> key, final StructuredDataContainer data, final CompoundTag backupTag, final BiConsumer valueSaveFunction) { final Holder holder = data.get(key); if (holder != null) { backupTag.put(key.identifier(), holderToTag(holder, valueSaveFunction)); } } protected Holder restoreHolder(final CompoundTag tag, final String key, final Function valueRestoreFunction) { final Tag savedTag = tag.get(key); if (savedTag == null) { return Holder.of(0); } if (savedTag instanceof IntTag idTag) { return Holder.of(idTag.asInt()); } else if (savedTag instanceof CompoundTag compoundTag) { return Holder.of(valueRestoreFunction.apply(compoundTag)); } else { return Holder.of(0); } } protected EitherHolder restoreEitherHolder(final CompoundTag tag, final String key, final Function valueRestoreFunction) { final Tag savedTag = tag.get(key); if (savedTag == null) { return EitherHolder.of(Holder.of(0)); } if (savedTag instanceof StringTag keyTag) { return EitherHolder.of(keyTag.getValue()); } else { return EitherHolder.of(restoreHolder(tag, key, valueRestoreFunction)); } } protected void restoreHolderData(final StructuredDataKey> key, final StructuredDataContainer data, final CompoundTag backupTag, final Function valueRestoreFunction) { if (backupTag.contains(key.identifier())) { data.set(key, restoreHolder(backupTag, key.identifier(), valueRestoreFunction)); } } protected void saveStringData(final StructuredDataKey key, final StructuredDataContainer data, final CompoundTag backupTag) { final String value = data.get(key); if (value != null) { backupTag.putString(key.identifier(), value); } } protected void restoreStringData(final StructuredDataKey key, final StructuredDataContainer data, final CompoundTag backupTag) { final String value = backupTag.getString(key.identifier()); if (value != null) { data.set(key, value); } } protected void saveKeyData(final StructuredDataKey key, final StructuredDataContainer data, final CompoundTag backupTag) { final Key value = data.get(key); if (value != null) { backupTag.putString(key.identifier(), value.original()); } } protected void restoreKeyData(final StructuredDataKey key, final StructuredDataContainer data, final CompoundTag backupTag) { final String value = backupTag.getString(key.identifier()); if (value != null) { data.set(key, Key.of(value)); } } protected void saveIntData(final StructuredDataKey key, final StructuredDataContainer data, final CompoundTag backupTag) { final Integer variant = data.get(key); if (variant != null) { backupTag.putInt(key.identifier(), variant); } } protected void restoreIntData(final StructuredDataKey key, final StructuredDataContainer data, final CompoundTag backupTag) { final IntTag variant = backupTag.getIntTag(key.identifier()); if (variant != null) { data.set(key, variant.asInt()); } } protected void saveFloatData(final StructuredDataKey key, final StructuredDataContainer data, final CompoundTag backupTag) { final Float variant = data.get(key); if (variant != null) { backupTag.putFloat(key.identifier(), variant); } } protected void restoreFloatData(final StructuredDataKey key, final StructuredDataContainer data, final CompoundTag backupTag) { final FloatTag variant = backupTag.getFloatTag(key.identifier()); if (variant != null) { data.set(key, variant.asFloat()); } } protected void saveSoundEventHolder(final CompoundTag tag, final Holder holder) { tag.put("sound_event", holderToTag(holder, this::saveSoundEvent)); } protected void saveSoundEvent(final SoundEvent soundEvent, final CompoundTag tag) { tag.putString("identifier", soundEvent.identifier()); if (soundEvent.fixedRange() != null) { tag.putFloat("fixed_range", soundEvent.fixedRange()); } } protected Holder restoreSoundEventHolder(final CompoundTag tag) { return restoreSoundEventHolder(tag, "sound_event"); } protected Holder restoreSoundEventHolder(final CompoundTag tag, final String key) { return restoreHolder(tag, key, soundEventTag -> { final String identifier = soundEventTag.getString("identifier"); final FloatTag fixedRange = soundEventTag.getFloatTag("fixed_range"); return new SoundEvent(identifier, fixedRange != null ? fixedRange.asFloat() : null); }); } @Override public String nbtTagName() { return this.nbtTagName; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/EnchantmentRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.utils.ChatUtil; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.util.Key; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * Rewriter to handle the addition of new enchantments. */ public class EnchantmentRewriter { public static final String ENCHANTMENT_LEVEL_TRANSLATION = "enchantment.level.%s"; protected final Map enchantmentMappings = new HashMap<>(); protected final BackwardsItemRewriter itemRewriter; private final boolean jsonFormat; public EnchantmentRewriter(BackwardsItemRewriter itemRewriter, boolean jsonFormat) { this.itemRewriter = itemRewriter; this.jsonFormat = jsonFormat; } public EnchantmentRewriter(BackwardsItemRewriter itemRewriter) { this(itemRewriter, true); } public void registerEnchantment(String key, String replacementLore) { enchantmentMappings.put(Key.stripMinecraftNamespace(key), replacementLore); } public void handleToClient(Item item) { CompoundTag tag = item.tag(); if (tag == null) return; if (tag.getListTag("Enchantments") != null) { rewriteEnchantmentsToClient(tag, false); } if (tag.getListTag("StoredEnchantments") != null) { rewriteEnchantmentsToClient(tag, true); } } public void handleToServer(Item item) { CompoundTag tag = item.tag(); if (tag == null) return; if (tag.contains(itemRewriter.nbtTagName("Enchantments"))) { rewriteEnchantmentsToServer(tag, false); } if (tag.contains(itemRewriter.nbtTagName("StoredEnchantments"))) { rewriteEnchantmentsToServer(tag, true); } } public void rewriteEnchantmentsToClient(CompoundTag tag, boolean storedEnchant) { String key = storedEnchant ? "StoredEnchantments" : "Enchantments"; ListTag enchantments = tag.getListTag(key, CompoundTag.class); List loreToAdd = new ArrayList<>(); boolean changed = false; Iterator iterator = enchantments.iterator(); while (iterator.hasNext()) { CompoundTag enchantmentEntry = iterator.next(); StringTag idTag = enchantmentEntry.getStringTag("id"); if (idTag == null) { continue; } String enchantmentId = Key.stripMinecraftNamespace(idTag.getValue()); String remappedName = enchantmentMappings.get(enchantmentId); if (remappedName != null) { if (!changed) { // Backup original before doing modifications itemRewriter.saveListTag(tag, enchantments, key); changed = true; } iterator.remove(); NumberTag levelTag = enchantmentEntry.getNumberTag("lvl"); int level = levelTag != null ? levelTag.asInt() : 1; String loreValue; if (jsonFormat) { loreValue = ChatUtil.legacyToJsonString(remappedName, ENCHANTMENT_LEVEL_TRANSLATION.formatted(level), true); } else { loreValue = remappedName + " " + getRomanNumber(level); } loreToAdd.add(new StringTag(loreValue)); } } if (!loreToAdd.isEmpty()) { // Add dummy enchant for the glow effect if there are no actual enchantments left if (!storedEnchant && enchantments.isEmpty()) { CompoundTag dummyEnchantment = new CompoundTag(); dummyEnchantment.putString("id", ""); dummyEnchantment.putShort("lvl", (short) 0); enchantments.add(dummyEnchantment); } CompoundTag display = tag.getCompoundTag("display"); if (display == null) { tag.put("display", display = new CompoundTag()); } ListTag loreTag = display.getListTag("Lore", StringTag.class); if (loreTag == null) { display.put("Lore", loreTag = new ListTag<>(StringTag.class)); } else { // Save original lore itemRewriter.saveListTag(display, loreTag, "Lore"); } loreToAdd.addAll(loreTag.getValue()); loreTag.setValue(loreToAdd); } } public void rewriteEnchantmentsToServer(CompoundTag tag, boolean storedEnchant) { // Just restore the original tag ig present (lore is always restored in the item rewriter) String key = storedEnchant ? "StoredEnchantments" : "Enchantments"; itemRewriter.restoreListTag(tag, key); } public static String getRomanNumber(int number) { return switch (number) { case 1 -> "I"; case 2 -> "II"; case 3 -> "III"; case 4 -> "IV"; case 5 -> "V"; case 6 -> "VI"; case 7 -> "VII"; case 8 -> "VIII"; case 9 -> "IX"; case 10 -> "X"; default -> ENCHANTMENT_LEVEL_TRANSLATION.formatted(number); // Fallback to translation to match vanilla style }; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/EntityRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entitydata.EntityDataType; import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_14; public abstract class EntityRewriter> extends EntityRewriterBase { protected EntityRewriter(T protocol) { this(protocol, Types1_14.ENTITY_DATA_TYPES.optionalComponentType, Types1_14.ENTITY_DATA_TYPES.booleanType); } protected EntityRewriter(T protocol, EntityDataType displayType, EntityDataType displayVisibilityType) { super(protocol, displayType, 2, displayVisibilityType, 3); } @Override public void registerTrackerWithData(C packetType) { protocol.registerClientbound(packetType, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Entity ID wrapper.passthrough(Types.UUID); // Entity UUID wrapper.passthrough(Types.VAR_INT); // Entity Type wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z wrapper.passthrough(Types.BYTE); // Pitch wrapper.passthrough(Types.BYTE); // Yaw wrapper.passthrough(Types.INT); // Data getSpawnTrackerWithDataHandler().handle(wrapper); }); } @Override public void registerTrackerWithData1_19(C packetType) { protocol.registerClientbound(packetType, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Entity id wrapper.passthrough(Types.UUID); // Entity UUID wrapper.passthrough(Types.VAR_INT); // Entity type wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z wrapper.passthrough(Types.BYTE); // Pitch wrapper.passthrough(Types.BYTE); // Yaw wrapper.passthrough(Types.BYTE); // Head yaw wrapper.passthrough(Types.VAR_INT); // Data getSpawnTrackerWithDataHandler1_19().handle(wrapper); }); } public PacketHandler getSpawnTrackerWithDataHandler() { return wrapper -> { // Check against the UNMAPPED entity type EntityType entityType = trackAndMapEntity(wrapper); if (entityType == typeFromId("falling_block")) { int blockState = wrapper.get(Types.INT, 0); wrapper.set(Types.INT, 0, protocol.getMappingData().getNewBlockStateId(blockState)); } }; } public PacketHandler getSpawnTrackerWithDataHandler1_19() { return wrapper -> { if (protocol.getMappingData() == null) { return; } // Check against the UNMAPPED entity type EntityType entityType = trackAndMapEntity(wrapper); if (entityType == typeFromId("falling_block")) { int blockState = wrapper.get(Types.VAR_INT, 2); wrapper.set(Types.VAR_INT, 2, protocol.getMappingData().getNewBlockStateId(blockState)); } }; } @Override public void registerTracker(C packetType) { protocol.registerClientbound(packetType, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Entity ID wrapper.passthrough(Types.UUID); // Entity UUID wrapper.passthrough(Types.VAR_INT); // Entity Type trackAndMapEntity(wrapper); }); } /** * Returns a handler to track the current world and uncache entity data on world changes. * * @return handler to track the current world */ public PacketHandler worldTrackerHandlerByKey() { return wrapper -> { EntityTracker tracker = tracker(wrapper.user()); String world = wrapper.get(Types.STRING, 1); if (tracker.currentWorld() != null && !tracker.currentWorld().equals(world)) { tracker.clearEntities(); } tracker.setCurrentWorld(world); }; } /** * Sets the mapped entity id and returns the unmapped entity type. * * @param wrapper packet wrapper * @return unmapped (!) entity type */ protected EntityType trackAndMapEntity(PacketWrapper wrapper) { int typeId = wrapper.get(Types.VAR_INT, 1); EntityType entityType = typeFromId(typeId); if (entityType == null) { return null; } tracker(wrapper.user()).addEntity(wrapper.get(Types.VAR_INT, 0), entityType); int mappedTypeId = newEntityId(entityType.getId()); if (typeId != mappedTypeId) { wrapper.set(Types.VAR_INT, 1, mappedTypeId); } return entityType; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/EntityRewriterBase.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; import com.google.common.base.Preconditions; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.entities.storage.EntityReplacement; import com.viaversion.viabackwards.api.entities.storage.WrappedEntityData; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.data.entity.StoredEntityData; import com.viaversion.viaversion.api.data.entity.TrackedEntity; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.entitydata.EntityDataType; import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectOpenHashMap; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.rewriter.EntityRewriter; import com.viaversion.viaversion.rewriter.entitydata.EntityDataHandlerEvent; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; /** * Entity rewriter base class. * * @see com.viaversion.viabackwards.api.rewriters.EntityRewriter * @see LegacyEntityRewriter */ public abstract class EntityRewriterBase> extends EntityRewriter { private final Int2ObjectMap entityDataMappings = new Int2ObjectOpenHashMap<>(); private final EntityDataType displayNameDataType; private final EntityDataType displayVisibilityDataType; private final int displayNameIndex; private final int displayVisibilityIndex; EntityRewriterBase(T protocol, EntityDataType displayNameDataType, int displayNameIndex, EntityDataType displayVisibilityDataType, int displayVisibilityIndex) { super(protocol, false); this.displayNameDataType = displayNameDataType; this.displayNameIndex = displayNameIndex; this.displayVisibilityDataType = displayVisibilityDataType; this.displayVisibilityIndex = displayVisibilityIndex; } @Override public void handleEntityData(int entityId, List entityDataList, UserConnection connection) { final TrackedEntity entity = tracker(connection).entity(entityId); final boolean initialEntityData = !(entity != null && entity.hasSentEntityData()); super.handleEntityData(entityId, entityDataList, connection); if (entity == null) { return; // Don't handle untracked entities - basically always the fault of a plugin sending virtual entities through concurrency-unsafe handling } // Set the mapped entity name if there is no custom name set already final EntityReplacement entityMapping = entityDataForType(entity.entityType()); final Object displayNameObject; if (entityMapping != null && (displayNameObject = entityMapping.entityName()) != null) { final EntityData displayName = getData(displayNameIndex, entityDataList); if (initialEntityData) { if (displayName == null) { // Add it as new entity data entityDataList.add(new EntityData(displayNameIndex, displayNameDataType, displayNameObject)); addDisplayVisibilityData(entityDataList); } else if (displayName.getValue() == null || displayName.getValue().toString().isEmpty()) { // Overwrite the existing null/empty display name displayName.setValue(displayNameObject); addDisplayVisibilityData(entityDataList); } } else if (displayName != null && (displayName.getValue() == null || displayName.getValue().toString().isEmpty())) { // Overwrite null/empty display name displayName.setValue(displayNameObject); addDisplayVisibilityData(entityDataList); } } // Add any other extra data for mapped entities if (entityMapping != null && entityMapping.hasBaseData() && initialEntityData) { entityMapping.defaultData().createData(new WrappedEntityData(entityDataList)); } } private void addDisplayVisibilityData(List entityDataList) { if (alwaysShowOriginalMobName()) { removeData(displayVisibilityIndex, entityDataList); entityDataList.add(new EntityData(displayVisibilityIndex, displayVisibilityDataType, getDisplayVisibilityDataValue())); } } protected Object getDisplayVisibilityDataValue() { return true; } protected boolean alwaysShowOriginalMobName() { return ViaBackwards.getConfig().alwaysShowOriginalMobName(); } protected @Nullable EntityData getData(int dataIndex, List entityDataList) { for (EntityData entityData : entityDataList) { if (entityData.id() == dataIndex) { return entityData; } } return null; } protected void removeData(int dataIndex, List entityDataList) { entityDataList.removeIf(data -> data.id() == dataIndex); } protected boolean hasData(EntityType type) { return entityDataMappings.containsKey(type.getId()); } protected @Nullable EntityReplacement entityDataForType(EntityType type) { return entityDataMappings.get(type.getId()); } protected @Nullable StoredEntityData storedEntityData(EntityDataHandlerEvent event) { return tracker(event.user()).entityData(event.entityId()); } /** * Maps an entity type to another with extra data. * Note that both types should be of the same version. * * @param type entity type * @param mappedType mapped entity type * @return created entity data * @see #mapEntityType(EntityType, EntityType) for id only rewriting */ protected EntityReplacement mapEntityTypeWithData(EntityType type, EntityType mappedType) { Preconditions.checkArgument(type.getClass() == mappedType.getClass(), "Both entity types need to be of the same class"); // Already rewrite the id here int mappedReplacementId = newEntityId(mappedType.getId()); EntityReplacement data = new EntityReplacement(protocol, type, mappedReplacementId); mapEntityType(type.getId(), mappedReplacementId); entityDataMappings.put(type.getId(), data); return data; } public void registerEntityDataTypeHandler( @Nullable EntityDataType itemType, @Nullable EntityDataType blockStateType, @Nullable EntityDataType optionalBlockStateType, @Nullable EntityDataType particleType, @Nullable EntityDataType componentType, @Nullable EntityDataType optionalComponentType ) { filter().handler((event, data) -> { EntityDataType type = data.dataType(); if (type == itemType) { data.setValue(protocol.getItemRewriter().handleItemToClient(event.user(), data.value())); } else if (type == blockStateType) { int value = data.value(); data.setValue(protocol.getMappingData().getNewBlockStateId(value)); } else if (type == optionalBlockStateType) { int value = data.value(); if (value != 0) { data.setValue(protocol.getMappingData().getNewBlockStateId(value)); } } else if (type == particleType) { protocol.getParticleRewriter().rewriteParticle(event.user(), data.value()); } else if (type == optionalComponentType || type == componentType) { JsonElement text = data.value(); protocol.getComponentRewriter().processText(event.user(), text); } }); } public void registerEntityDataTypeHandler1_20_3( @Nullable EntityDataType itemType, @Nullable EntityDataType blockStateType, @Nullable EntityDataType optionalBlockStateType, @Nullable EntityDataType particleType, @Nullable EntityDataType particlesType, @Nullable EntityDataType componentType, @Nullable EntityDataType optionalComponentType ) { filter().handler((event, data) -> { EntityDataType type = data.dataType(); if (type == itemType) { data.setValue(protocol.getItemRewriter().handleItemToClient(event.user(), data.value())); } else if (type == blockStateType) { int value = data.value(); data.setValue(protocol.getMappingData().getNewBlockStateId(value)); } else if (type == optionalBlockStateType) { int value = data.value(); if (value != 0) { data.setValue(protocol.getMappingData().getNewBlockStateId(value)); } } else if (type == particleType) { protocol.getParticleRewriter().rewriteParticle(event.user(), data.value()); } else if (type == particlesType) { Particle[] particles = data.value(); for (final Particle particle : particles) { protocol.getParticleRewriter().rewriteParticle(event.user(), particle); } } else if (type == optionalComponentType || type == componentType) { protocol.getComponentRewriter().processTag(event.user(), data.value()); } }); } // ONLY TRACKS, DOESN'T REWRITE IDS protected PacketHandler getTrackerHandler(Type intType, int typeIndex) { return wrapper -> { Number id = wrapper.get(intType, typeIndex); EntityType entityType = typeFromId(id.intValue()); if (entityType != null) { tracker(wrapper.user()).addEntity(wrapper.get(Types.VAR_INT, 0), entityType); } }; } protected PacketHandler getTrackerHandler() { return getTrackerHandler(Types.VAR_INT, 1); } protected PacketHandler getTrackerHandler(EntityType entityType) { return wrapper -> tracker(wrapper.user()).addEntity(wrapper.get(Types.VAR_INT, 0), entityType); } protected PacketHandler getPlayerTrackerHandler() { return wrapper -> { final int entityId = wrapper.get(Types.INT, 0); final EntityTracker tracker = tracker(wrapper.user()); tracker(wrapper.user()).setClientEntityId(entityId); tracker.addEntity(entityId, tracker.playerType()); }; } protected PacketHandler getDimensionHandler(int index) { return wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(this.protocol.getClass()); int dimensionId = wrapper.get(Types.INT, index); if (clientWorld.setEnvironment(dimensionId)) { tracker(wrapper.user()).clearEntities(); } }; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/LegacyBlockItemRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; import com.viaversion.nbt.tag.ByteTag; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.nbt.tag.ShortTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingDataLoader; import com.viaversion.viabackwards.api.data.MappedLegacyBlockItem; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.data.BlockColors1_11_1; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.BlockChangeRecord; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; import com.viaversion.viaversion.api.minecraft.chunks.DataPalette; import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_12; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; import com.viaversion.viaversion.api.protocol.packet.ServerboundPacketType; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectOpenHashMap; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.libs.gson.JsonObject; import com.viaversion.viaversion.libs.gson.JsonPrimitive; import com.viaversion.viaversion.util.ComponentUtil; import com.viaversion.viaversion.util.IdAndData; import java.util.HashMap; import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; public abstract class LegacyBlockItemRewriter> extends BackwardsItemRewriterBase { protected final Int2ObjectMap itemReplacements = new Int2ObjectOpenHashMap<>(8); // Raw id -> mapped data protected final Int2ObjectMap blockReplacements = new Int2ObjectOpenHashMap<>(8); // Raw id -> mapped data protected LegacyBlockItemRewriter(T protocol, String name, Type itemType, Type itemArrayType, Type mappedItemType, Type mappedItemArrayType) { super(protocol, itemType, itemArrayType, mappedItemType, mappedItemArrayType, false); Int2ObjectMap blockItemReplacements = new Int2ObjectOpenHashMap<>(8); final JsonObject jsonObject = readMappingsFile("item-mappings-" + name + ".json"); addMappings(MappedLegacyBlockItem.Type.ITEM, jsonObject, itemReplacements); addMappings(MappedLegacyBlockItem.Type.BLOCK_ITEM, jsonObject, blockItemReplacements); addMappings(MappedLegacyBlockItem.Type.BLOCK, jsonObject, blockReplacements); blockReplacements.putAll(blockItemReplacements); itemReplacements.putAll(blockItemReplacements); } protected LegacyBlockItemRewriter(T protocol, String name, Type itemType, Type itemArrayType) { this(protocol, name, itemType, itemArrayType, itemType, itemArrayType); } protected LegacyBlockItemRewriter(T protocol, String name) { this(protocol, name, Types.ITEM1_8, Types.ITEM1_8_SHORT_ARRAY); } private void addMappings(MappedLegacyBlockItem.Type type, JsonObject object, Int2ObjectMap mappings) { if (object.has(type.getName())) { final JsonObject mappingsObject = object.getAsJsonObject(type.getName()); for (Map.Entry dataEntry : mappingsObject.entrySet()) { addMapping(dataEntry.getKey(), dataEntry.getValue().getAsJsonObject(), type, mappings); } } } private void addMapping(String key, JsonObject object, MappedLegacyBlockItem.Type type, Int2ObjectMap mappings) { int id = object.getAsJsonPrimitive("id").getAsInt(); JsonPrimitive jsonData = object.getAsJsonPrimitive("data"); short data = jsonData != null ? jsonData.getAsShort() : 0; String name = type != MappedLegacyBlockItem.Type.BLOCK ? object.getAsJsonPrimitive("name").getAsString() : null; if (key.indexOf('-') == -1) { int unmappedId; int dataSeparatorIndex = key.indexOf(':'); if (dataSeparatorIndex != -1) { // Include data short unmappedData = Short.parseShort(key.substring(dataSeparatorIndex + 1)); unmappedId = Integer.parseInt(key.substring(0, dataSeparatorIndex)); unmappedId = compress(unmappedId, unmappedData); } else { unmappedId = compress(Integer.parseInt(key), -1); } mappings.put(unmappedId, new MappedLegacyBlockItem(id, data, name, type)); return; } // Range of ids String[] split = key.split("-", 2); int from = Integer.parseInt(split[0]); int to = Integer.parseInt(split[1]); // Special block color handling if (name != null && name.contains("%color%")) { for (int i = from; i <= to; i++) { mappings.put(compress(i, -1), new MappedLegacyBlockItem(id, data, name.replace("%color%", BlockColors1_11_1.get(i - from)), type)); } } else { MappedLegacyBlockItem mappedBlockItem = new MappedLegacyBlockItem(id, data, name, type); for (int i = from; i <= to; i++) { mappings.put(compress(i, -1), mappedBlockItem); } } } public void registerBlockChange(C packetType) { protocol.registerClientbound(packetType, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_8); // 0 - Block Position map(Types.VAR_INT); // 1 - Block handler(wrapper -> { int idx = wrapper.get(Types.VAR_INT, 0); wrapper.set(Types.VAR_INT, 0, handleBlockId(idx)); }); } }); } public void registerMultiBlockChange(C packetType) { protocol.registerClientbound(packetType, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Chunk X map(Types.INT); // 1 - Chunk Z map(Types.BLOCK_CHANGE_ARRAY); handler(wrapper -> { for (BlockChangeRecord record : wrapper.get(Types.BLOCK_CHANGE_ARRAY, 0)) { record.setBlockId(handleBlockId(record.getBlockId())); } }); } }); } @Override public @Nullable Item handleItemToClient(UserConnection connection, @Nullable Item item) { if (item == null) return null; MappedLegacyBlockItem data = getMappedItem(item.identifier(), item.data()); if (data == null) { // Just rewrite the id return super.handleItemToClient(connection, item); } if (item.tag() == null) { item.setTag(new CompoundTag()); } short originalData = item.data(); item.tag().putInt(nbtTagName("id"), item.identifier()); item.setIdentifier(data.getId()); // Keep original data if mapped data is set to -1 if (data.getData() != -1) { item.setData(data.getData()); item.tag().putShort(nbtTagName("data"), originalData); } // Set display name if (data.getName() != null) { CompoundTag display = item.tag().getCompoundTag("display"); if (display == null) { item.tag().put("display", display = new CompoundTag()); } StringTag nameTag = display.getStringTag("Name"); if (nameTag == null) { nameTag = new StringTag(data.getName()); display.put("Name", nameTag); display.put(nbtTagName("customName"), new ByteTag(false)); } // Handle colors String value = nameTag.getValue(); if (value.contains("%vb_color%")) { display.putString("Name", value.replace("%vb_color%", BlockColors1_11_1.get(originalData))); } } return item; } @Override public @Nullable Item handleItemToServer(UserConnection connection, @Nullable Item item) { if (item == null) return null; item = super.handleItemToServer(connection, item); if (item.tag() != null) { Tag originalId = item.tag().remove(nbtTagName("id")); if (originalId instanceof IntTag) { item.setIdentifier(((NumberTag) originalId).asInt()); } Tag originalData = item.tag().remove(nbtTagName("data")); if (originalData instanceof ShortTag) { item.setData(((NumberTag) originalData).asShort()); } } return item; } public PacketHandler getFallingBlockHandler() { return wrapper -> { final int objectData = wrapper.get(Types.INT, 0); final EntityTypes1_12.ObjectType type = EntityTypes1_12.ObjectType.findById(wrapper.get(Types.BYTE, 0), objectData); if (type == EntityTypes1_12.ObjectType.FALLING_BLOCK) { final IdAndData block = handleBlock(objectData & 4095, objectData >> 12 & 15); if (block == null) return; wrapper.set(Types.INT, 0, block.getId() | block.getData() << 12); } }; } public @Nullable IdAndData handleBlock(int blockId, int data) { MappedLegacyBlockItem settings = getMappedBlock(blockId, data); if (settings == null) { return null; } IdAndData block = settings.getBlock(); // For some blocks, the data can still be useful (: if (block.getData() == -1) { return block.withData(data); } return block; } public int handleBlockId(final int rawId) { final int id = IdAndData.getId(rawId); final int data = IdAndData.getData(rawId); final IdAndData mappedBlock = handleBlock(id, data); if (mappedBlock == null) return rawId; return IdAndData.toRawData(mappedBlock.getId(), mappedBlock.getData()); } public void handleChunk(Chunk chunk) { // Map Block Entities Map tags = new HashMap<>(); for (CompoundTag tag : chunk.getBlockEntities()) { NumberTag xTag; NumberTag yTag; NumberTag zTag; if ((xTag = tag.getNumberTag("x")) == null || (yTag = tag.getNumberTag("y")) == null || (zTag = tag.getNumberTag("z")) == null) { continue; } Pos pos = new Pos(xTag.asInt() & 0xF, yTag.asInt(), zTag.asInt() & 0xF); tags.put(pos, tag); // Handle given Block Entities if (pos.y() < 0 || pos.y() > 255) continue; // 1.17 ChunkSection section = chunk.getSections()[pos.y() >> 4]; if (section == null) continue; int block = section.palette(PaletteType.BLOCKS).idAt(pos.x(), pos.y() & 0xF, pos.z()); MappedLegacyBlockItem settings = getMappedBlock(block); if (settings != null && settings.hasBlockEntityHandler()) { settings.getBlockEntityHandler().handleCompoundTag(block, tag); } } for (int i = 0; i < chunk.getSections().length; i++) { ChunkSection section = chunk.getSections()[i]; if (section == null) { continue; } boolean hasBlockEntityHandler = false; // Map blocks DataPalette palette = section.palette(PaletteType.BLOCKS); for (int j = 0; j < palette.size(); j++) { int block = palette.idByIndex(j); int btype = block >> 4; int meta = block & 0xF; IdAndData b = handleBlock(btype, meta); if (b != null) { palette.setIdByIndex(j, IdAndData.toRawData(b.getId(), b.getData())); } // We already know that is has a handler if (hasBlockEntityHandler) continue; MappedLegacyBlockItem settings = getMappedBlock(block); if (settings != null && settings.hasBlockEntityHandler()) { hasBlockEntityHandler = true; } } if (!hasBlockEntityHandler) continue; // We need to handle a Block Entity :( for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { int block = palette.idAt(x, y, z); MappedLegacyBlockItem settings = getMappedBlock(block); if (settings == null || !settings.hasBlockEntityHandler()) continue; Pos pos = new Pos(x, (y + (i << 4)), z); // Already handled above if (tags.containsKey(pos)) continue; CompoundTag tag = new CompoundTag(); tag.putInt("x", x + (chunk.getX() << 4)); tag.putInt("y", y + (i << 4)); tag.putInt("z", z + (chunk.getZ() << 4)); settings.getBlockEntityHandler().handleCompoundTag(block, tag); chunk.getBlockEntities().add(tag); } } } } } protected CompoundTag getNamedTag(String text) { CompoundTag tag = new CompoundTag(); CompoundTag displayTag = new CompoundTag(); tag.put("display", displayTag); text = "§r" + text; displayTag.putString("Name", jsonNameFormat ? ComponentUtil.legacyToJsonString(text) : text); return tag; } private @Nullable MappedLegacyBlockItem getMappedBlock(int id, int data) { MappedLegacyBlockItem mapping = blockReplacements.get(compress(id, data)); return mapping != null ? mapping : blockReplacements.get(compress(id, -1)); } private @Nullable MappedLegacyBlockItem getMappedItem(int id, int data) { MappedLegacyBlockItem mapping = itemReplacements.get(compress(id, data)); return mapping != null ? mapping : itemReplacements.get(compress(id, -1)); } private @Nullable MappedLegacyBlockItem getMappedBlock(int rawId) { int id = IdAndData.getId(rawId); int data = IdAndData.getData(rawId); return getMappedBlock(id, data); } protected JsonObject readMappingsFile(final String name) { return BackwardsMappingDataLoader.INSTANCE.loadFromDataDir(name); } protected int compress(final int id, final int data) { // Using IdAndData for the internal storage can cause id overlaps in edge cases and would lead to wrong data return (id << 16) | (data & 0xFFFF); } private record Pos(int x, short y, int z) { public Pos(int x, int y, int z) { this(x, (short) y, z); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/LegacyEnchantmentRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; import com.viaversion.nbt.tag.ByteTag; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viaversion.api.minecraft.item.Item; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class LegacyEnchantmentRewriter { private final Map enchantmentMappings = new HashMap<>(); private final String nbtTagName; private final boolean dummyEnchantment; private Set hideLevelForEnchants; public LegacyEnchantmentRewriter(String nbtTagName) { this(nbtTagName, true); } public LegacyEnchantmentRewriter(String nbtTagName, boolean dummyEnchantment) { this.nbtTagName = nbtTagName; this.dummyEnchantment = dummyEnchantment; } public void registerEnchantment(int id, String replacementLore) { enchantmentMappings.put((short) id, replacementLore); } public void handleToClient(Item item) { CompoundTag tag = item.tag(); if (tag == null) return; if (tag.getListTag("ench") != null) { rewriteEnchantmentsToClient(tag, false); } if (tag.getListTag("StoredEnchantments") != null) { rewriteEnchantmentsToClient(tag, true); } } public void handleToServer(Item item) { CompoundTag tag = item.tag(); if (tag == null) return; if (tag.getListTag(nbtTagName + "|ench", CompoundTag.class) != null) { rewriteEnchantmentsToServer(tag, false); } if (tag.getListTag(nbtTagName + "|StoredEnchantments", CompoundTag.class) != null) { rewriteEnchantmentsToServer(tag, true); } } public void rewriteEnchantmentsToClient(CompoundTag tag, boolean storedEnchant) { String key = storedEnchant ? "StoredEnchantments" : "ench"; ListTag enchantments = tag.getListTag(key, CompoundTag.class); ListTag remappedEnchantments = new ListTag<>(CompoundTag.class); List lore = new ArrayList<>(); for (CompoundTag enchantmentEntry : enchantments.copy()) { NumberTag idTag = enchantmentEntry.getNumberTag("id"); if (idTag == null) continue; short newId = idTag.asShort(); String enchantmentName = enchantmentMappings.get(newId); if (enchantmentName != null) { enchantments.remove(enchantmentEntry); NumberTag levelTag = enchantmentEntry.getNumberTag("lvl"); short level = levelTag != null ? levelTag.asShort() : 1; if (hideLevelForEnchants != null && hideLevelForEnchants.contains(newId)) { lore.add(new StringTag(enchantmentName)); } else { lore.add(new StringTag(enchantmentName + " " + EnchantmentRewriter.getRomanNumber(level))); } remappedEnchantments.add(enchantmentEntry); } } if (!lore.isEmpty()) { if (this.dummyEnchantment && !storedEnchant && enchantments.isEmpty()) { CompoundTag dummyEnchantment = new CompoundTag(); dummyEnchantment.putShort("id", (short) 0); dummyEnchantment.putShort("lvl", (short) 0); enchantments.add(dummyEnchantment); tag.put(nbtTagName + "|dummyEnchant", new ByteTag(false)); NumberTag hideFlags = tag.getNumberTag("HideFlags"); if (hideFlags == null) { hideFlags = new IntTag(); } else { tag.putInt(nbtTagName + "|oldHideFlags", hideFlags.asByte()); } int flags = hideFlags.asByte() | 1; tag.putInt("HideFlags", flags); } tag.put(nbtTagName + "|" + key, remappedEnchantments); CompoundTag display = tag.getCompoundTag("display"); if (display == null) { tag.put("display", display = new CompoundTag()); } ListTag loreTag = display.getListTag("Lore", StringTag.class); if (loreTag == null) { display.put("Lore", loreTag = new ListTag<>(StringTag.class)); } lore.addAll(loreTag.getValue()); loreTag.setValue(lore); } } public void rewriteEnchantmentsToServer(CompoundTag tag, boolean storedEnchant) { String key = storedEnchant ? "StoredEnchantments" : "ench"; ListTag enchantments = tag.getListTag(key, CompoundTag.class); if (enchantments == null) { enchantments = new ListTag<>(CompoundTag.class); } if (!storedEnchant && tag.remove(nbtTagName + "|dummyEnchant") != null) { for (CompoundTag enchantment : enchantments.copy()) { NumberTag idTag = enchantment.getNumberTag("id"); NumberTag levelTag = enchantment.getNumberTag("lvl"); short id = idTag != null ? idTag.asShort() : 0; short level = levelTag != null ? levelTag.asShort() : 0; if (id == 0 && level == 0) { enchantments.remove(enchantment); } } Tag hideFlags = tag.remove(nbtTagName + "|oldHideFlags"); if (hideFlags instanceof IntTag intTag) { tag.putInt("HideFlags", intTag.asByte()); } else { tag.remove("HideFlags"); } } CompoundTag display = tag.getCompoundTag("display"); // A few null checks just to be safe, though they shouldn't actually be ListTag lore = display != null ? display.getListTag("Lore", StringTag.class) : null; ListTag remappedEnchantments = (ListTag) tag.remove(nbtTagName + "|" + key); for (CompoundTag enchantment : remappedEnchantments.copy()) { enchantments.add(enchantment); if (lore != null && !lore.isEmpty()) { lore.remove(lore.get(0)); } } if (lore != null && lore.isEmpty()) { display.remove("Lore"); if (display.isEmpty()) { tag.remove("display"); } } tag.put(key, enchantments); } public void setHideLevelForEnchants(int... enchants) { this.hideLevelForEnchants = new HashSet<>(); for (int enchant : enchants) { hideLevelForEnchants.add((short) enchant); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/LegacyEntityRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.entities.storage.EntityObjectData; import com.viaversion.viabackwards.api.entities.storage.EntityReplacement; import com.viaversion.viabackwards.api.entities.storage.WrappedEntityData; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.ObjectType; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.entitydata.EntityDataType; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_9; import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.api.type.Types; import java.util.HashMap; import java.util.List; import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; public abstract class LegacyEntityRewriter> extends EntityRewriterBase { private final Map objectTypes = new HashMap<>(); protected LegacyEntityRewriter(T protocol) { this(protocol, EntityDataTypes1_9.STRING, EntityDataTypes1_9.BOOLEAN); } protected LegacyEntityRewriter(T protocol, EntityDataType displayType, EntityDataType displayVisibilityType) { super(protocol, displayType, 2, displayVisibilityType, 3); } protected EntityObjectData mapObjectType(ObjectType oldObjectType, ObjectType replacement, int data) { EntityObjectData entData = new EntityObjectData(protocol, oldObjectType.getType().name(), oldObjectType.getId(), replacement.getId(), data); objectTypes.put(oldObjectType, entData); return entData; } protected @Nullable EntityReplacement getObjectData(ObjectType type) { return objectTypes.get(type); } protected void registerRespawn(C packetType) { protocol.registerClientbound(packetType, new PacketHandlers() { @Override public void register() { map(Types.INT); handler(wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(protocol.getClass()); if (clientWorld.setEnvironment(wrapper.get(Types.INT, 0))) { tracker(wrapper.user()).clearEntities(); } }); } }); } protected void registerJoinGame(C packetType, EntityType playerType) { protocol.registerClientbound(packetType, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Entity ID map(Types.UNSIGNED_BYTE); // 1 - Gamemode map(Types.INT); // 2 - Dimension handler(wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(protocol.getClass()); clientWorld.setEnvironment(wrapper.get(Types.INT, 1)); final int entityId = wrapper.get(Types.INT, 0); tracker(wrapper.user()).addEntity(entityId, playerType); tracker(wrapper.user()).setClientEntityId(entityId); }); } }); } protected PacketHandler getMobSpawnRewriter(Type> dataType, IdSetter idSetter) { return wrapper -> { int entityId = wrapper.get(Types.VAR_INT, 0); EntityType type = tracker(wrapper.user()).entityType(entityId); if (type == null) { return; } List entityDataList = wrapper.get(dataType, 0); handleEntityData(entityId, entityDataList, wrapper.user()); EntityReplacement entityReplacement = entityDataForType(type); if (entityReplacement != null) { idSetter.setId(wrapper, entityReplacement.replacementId()); if (entityReplacement.hasBaseData()) { entityReplacement.defaultData().createData(new WrappedEntityData(entityDataList)); } } }; } public PacketHandler getMobSpawnRewriter(Type> dataType) { return getMobSpawnRewriter(dataType, (wrapper, id) -> wrapper.set(Types.UNSIGNED_BYTE, 0, (short) id)); } public PacketHandler getMobSpawnRewriter1_11(Type> dataType) { return getMobSpawnRewriter(dataType, (wrapper, id) -> wrapper.set(Types.VAR_INT, 1, id)); } protected PacketHandler getObjectTrackerHandler() { return wrapper -> { int id = wrapper.get(Types.BYTE, 0); int data = wrapper.get(Types.INT, 0); EntityType type = objectTypeFromId(id, data); if (type == null) { return; } tracker(wrapper.user()).addEntity(wrapper.get(Types.VAR_INT, 0), type); }; } protected PacketHandler getTrackerAndDataHandler(Type> dataType, EntityType entityType) { return wrapper -> { tracker(wrapper.user()).addEntity(wrapper.get(Types.VAR_INT, 0), entityType); List entityDataList = wrapper.get(dataType, 0); handleEntityData(wrapper.get(Types.VAR_INT, 0), entityDataList, wrapper.user()); }; } protected PacketHandler getObjectRewriter(ObjectTypeGetter objectGetter) { return wrapper -> { int id = wrapper.get(Types.BYTE, 0); int data = wrapper.get(Types.INT, 0); ObjectType type = objectGetter.get(id, data); if (type == null) { return; } EntityReplacement replacement = getObjectData(type); if (replacement != null) { wrapper.set(Types.BYTE, 0, (byte) replacement.replacementId()); if (replacement.objectData() != -1) { wrapper.set(Types.INT, 0, replacement.objectData()); } } }; } @FunctionalInterface protected interface IdSetter { void setId(PacketWrapper wrapper, int id); } @FunctionalInterface protected interface ObjectTypeGetter { ObjectType get(int id, int data); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/LegacySoundRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viaversion.api.rewriter.RewriterBase; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectOpenHashMap; @Deprecated public abstract class LegacySoundRewriter> extends RewriterBase { protected final Int2ObjectMap soundRewrites = new Int2ObjectOpenHashMap<>(64); protected LegacySoundRewriter(T protocol) { super(protocol); } public SoundData added(int id, int replacement) { return added(id, replacement, -1); } public SoundData added(int id, int replacement, float newPitch) { SoundData data = new SoundData(replacement, true, newPitch, true); soundRewrites.put(id, data); return data; } public SoundData removed(int id) { SoundData data = new SoundData(-1, false, -1, false); soundRewrites.put(id, data); return data; } public int handleSounds(int soundId) { int newSoundId = soundId; SoundData data = soundRewrites.get(soundId); if (data != null) return data.replacementSound(); for (Int2ObjectMap.Entry entry : soundRewrites.int2ObjectEntrySet()) { if (soundId > entry.getIntKey()) { if (entry.getValue().added()) { newSoundId--; } else { newSoundId++; } } } return newSoundId; } public boolean hasPitch(int soundId) { SoundData data = soundRewrites.get(soundId); return data != null && data.changePitch(); } public float handlePitch(int soundId) { SoundData data = soundRewrites.get(soundId); return data != null ? data.newPitch() : 1F; } public record SoundData(int replacementSound, boolean changePitch, float newPitch, boolean added) { } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/MapColorRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.rewriter.IdRewriteFunction; public final class MapColorRewriter { /** * Rewrite map colors using the provided id rewriter. * * @param wrapper packet wrapper * @param rewriter id rewriter returning mapped colors, or -1 if unmapped * @param iconCount number of icons to read */ public static void rewriteMapColors(PacketWrapper wrapper, IdRewriteFunction rewriter, int iconCount) { for (int i = 0; i < iconCount; i++) { wrapper.passthrough(Types.VAR_INT); // Type wrapper.passthrough(Types.BYTE); // X wrapper.passthrough(Types.BYTE); // Z wrapper.passthrough(Types.BYTE); // Direction wrapper.passthrough(Types.OPTIONAL_COMPONENT); // Display Name } short columns = wrapper.passthrough(Types.UNSIGNED_BYTE); if (columns < 1) { return; } wrapper.passthrough(Types.UNSIGNED_BYTE); // Rows wrapper.passthrough(Types.UNSIGNED_BYTE); // X wrapper.passthrough(Types.UNSIGNED_BYTE); // Z byte[] data = wrapper.passthrough(Types.BYTE_ARRAY_PRIMITIVE); for (int i = 0; i < data.length; i++) { int color = data[i] & 0xFF; int mappedColor = rewriter.rewrite(color); if (mappedColor != -1) { data[i] = (byte) mappedColor; } } } /** * Returns a packethandler to rewrite map data color ids. Reading starts from the icon count. * * @param rewriter id rewriter returning mapped colors, or -1 if unmapped * @return packethandler to rewrite map data color ids */ public static PacketHandler getRewriteHandler(IdRewriteFunction rewriter) { return wrapper -> rewriteMapColors(wrapper, rewriter, wrapper.passthrough(Types.VAR_INT)); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/SoundRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; import com.viaversion.viaversion.api.data.Mappings; import com.viaversion.viaversion.api.protocol.AbstractProtocol; import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.type.Types; public class SoundRewriter extends com.viaversion.viaversion.rewriter.SoundRewriter { public SoundRewriter(final AbstractProtocol protocol) { super(protocol); } public void registerNamedSound(final C packetType) { if (protocol.getMappingData() == null || Mappings.isFullIdentity(protocol.getMappingData().getFullSoundMappings())) { return; } protocol.registerClientbound(packetType, wrapper -> { wrapper.passthrough(Types.STRING); // Sound identifier getNamedSoundHandler().handle(wrapper); }); } public void registerStopSound(final C packetType) { if (protocol.getMappingData() == null || Mappings.isFullIdentity(protocol.getMappingData().getFullSoundMappings())) { return; } protocol.registerClientbound(packetType, wrapper -> { final byte flags = wrapper.passthrough(Types.BYTE); if ((flags & 0x01) != 0) { wrapper.passthrough(Types.VAR_INT); // Source } if ((flags & 0x02) == 0) return; // No sound specified final String soundId = wrapper.read(Types.STRING); final String mappedId = protocol.getMappingData().getFullSoundMappings().mappedIdentifier(soundId); if (mappedId == null) { // No mapping found wrapper.write(Types.STRING, soundId); return; } if (!mappedId.isEmpty()) { wrapper.write(Types.STRING, mappedId); } else { // Cancel if set to empty wrapper.cancel(); } }); } public PacketHandler getNamedSoundHandler() { return wrapper -> { final String soundId = wrapper.get(Types.STRING, 0); final String mappedId = protocol.getMappingData().getFullSoundMappings().mappedIdentifier(soundId); if (mappedId == null) { return; } if (!mappedId.isEmpty()) { wrapper.set(Types.STRING, 0, mappedId); } else { wrapper.cancel(); } }; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/StructuredEnchantmentRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters; import com.viaversion.nbt.tag.ByteTag; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.utils.ChatUtil; import com.viaversion.viaversion.api.data.Mappings; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.item.data.Enchantments; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntMap; import com.viaversion.viaversion.libs.fastutil.objects.ObjectIterator; import com.viaversion.viaversion.rewriter.IdRewriteFunction; import com.viaversion.viaversion.util.ComponentUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static com.viaversion.viabackwards.api.rewriters.EnchantmentRewriter.ENCHANTMENT_LEVEL_TRANSLATION; public class StructuredEnchantmentRewriter { protected final BackwardsStructuredItemRewriter itemRewriter; private boolean rewriteIds = true; public StructuredEnchantmentRewriter(final BackwardsStructuredItemRewriter itemRewriter) { this.itemRewriter = itemRewriter; } public void handleToClient(final Item item) { final StructuredDataContainer data = item.dataContainer(); final BackwardsMappingData mappingData = itemRewriter.protocol().getMappingData(); final IdRewriteFunction idRewriteFunction = id -> { final Mappings mappings = mappingData.getEnchantmentMappings(); return mappings.getNewId(id); }; final DescriptionSupplier descriptionSupplier = (id, level) -> { final String remappedName = mappingData.mappedEnchantmentName(id); return ComponentUtil.jsonStringToTag(ChatUtil.legacyToJsonString("§7" + remappedName, ENCHANTMENT_LEVEL_TRANSLATION.formatted(level), true)); }; rewriteEnchantmentsToClient(data, StructuredDataKey.ENCHANTMENTS1_20_5, idRewriteFunction, descriptionSupplier, false); rewriteEnchantmentsToClient(data, StructuredDataKey.STORED_ENCHANTMENTS1_20_5, idRewriteFunction, descriptionSupplier, true); } public void handleToServer(final Item item) { final StructuredDataContainer data = item.dataContainer(); final CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA); if (customData != null) { rewriteEnchantmentsToServer(data, customData, StructuredDataKey.ENCHANTMENTS1_20_5); rewriteEnchantmentsToServer(data, customData, StructuredDataKey.STORED_ENCHANTMENTS1_20_5); } } public void rewriteEnchantmentsToClient(final StructuredDataContainer data, final StructuredDataKey key, final IdRewriteFunction rewriteFunction, final DescriptionSupplier descriptionSupplier, final boolean storedEnchant) { final Enchantments enchantments = data.get(key); if (enchantments == null || enchantments.size() == 0) { return; } final List loreToAdd = new ArrayList<>(); boolean removedEnchantments = false; boolean updatedLore = false; final ObjectIterator iterator = enchantments.enchantments().int2IntEntrySet().iterator(); final List updatedIds = new ArrayList<>(); while (iterator.hasNext()) { final Int2IntMap.Entry entry = iterator.next(); final int id = entry.getIntKey(); final int mappedId = rewriteFunction.rewrite(id); final int level = entry.getIntValue(); if (mappedId != -1) { if (rewriteIds) { // Update the map after to iteration to preserve the current ids before possibly saving the original, avoid CME updatedIds.add(new PendingIdChange(id, mappedId, level)); } continue; } if (!removedEnchantments) { // Backup original before doing modifications final CompoundTag customData = customData(data); itemRewriter.saveListTag(customData, asTag(enchantments), key.identifier()); removedEnchantments = true; } final Tag description = descriptionSupplier.get(id, level); if (description != null && enchantments.showInTooltip()) { loreToAdd.add(description); updatedLore = true; } iterator.remove(); } // Remove all first, then add the new ones for (final PendingIdChange change : updatedIds) { enchantments.remove(change.id()); } for (final PendingIdChange change : updatedIds) { enchantments.add(change.mappedId(), change.level()); } if (removedEnchantments) { final CompoundTag tag = customData(data); if (!storedEnchant && enchantments.size() == 0) { // Add glint override if there are no enchantments left final Boolean glintOverride = data.get(StructuredDataKey.ENCHANTMENT_GLINT_OVERRIDE); if (glintOverride != null) { tag.putBoolean(itemRewriter.nbtTagName("glint"), glintOverride); } else { tag.putBoolean(itemRewriter.nbtTagName("noglint"), true); } data.set(StructuredDataKey.ENCHANTMENT_GLINT_OVERRIDE, true); } if (enchantments.showInTooltip()) { tag.putBoolean(itemRewriter.nbtTagName("show_" + key.identifier()), true); } } if (updatedLore) { // Save original lore final CompoundTag tag = customData(data); final Tag[] lore = data.get(StructuredDataKey.LORE); if (lore != null) { final List loreList = Arrays.asList(lore); itemRewriter.saveGenericTagList(tag, loreList, "lore"); loreToAdd.addAll(loreList); } else { tag.putBoolean(itemRewriter.nbtTagName("nolore"), true); } data.set(StructuredDataKey.LORE, loreToAdd.toArray(new Tag[0])); } } private CompoundTag customData(final StructuredDataContainer data) { CompoundTag tag = data.get(StructuredDataKey.CUSTOM_DATA); if (tag == null) { tag = new CompoundTag(); data.set(StructuredDataKey.CUSTOM_DATA, tag); } return tag; } private ListTag asTag(final Enchantments enchantments) { final ListTag listTag = new ListTag<>(CompoundTag.class); for (final Int2IntMap.Entry entry : enchantments.enchantments().int2IntEntrySet()) { final CompoundTag enchantment = new CompoundTag(); enchantment.putInt("id", entry.getIntKey()); enchantment.putInt("lvl", entry.getIntValue()); listTag.add(enchantment); } return listTag; } public void rewriteEnchantmentsToServer(final StructuredDataContainer data, final CompoundTag tag, final StructuredDataKey key) { final ListTag enchantmentsTag = itemRewriter.removeListTag(tag, key.identifier(), CompoundTag.class); if (enchantmentsTag == null) { return; } final Tag glintTag = tag.remove(itemRewriter.nbtTagName("glint")); if (glintTag instanceof ByteTag) { data.set(StructuredDataKey.ENCHANTMENT_GLINT_OVERRIDE, ((NumberTag) glintTag).asBoolean()); } else if (tag.remove(itemRewriter.nbtTagName("noglint")) != null) { data.remove(StructuredDataKey.ENCHANTMENT_GLINT_OVERRIDE); } final List lore = itemRewriter.removeGenericTagList(tag, "lore"); if (lore != null) { data.set(StructuredDataKey.LORE, lore.toArray(new Tag[0])); } else if (tag.remove(itemRewriter.nbtTagName("nolore")) != null) { data.remove(StructuredDataKey.LORE); } final Enchantments enchantments = new Enchantments(tag.remove(itemRewriter.nbtTagName("show_" + key.identifier())) != null); for (final CompoundTag enchantment : enchantmentsTag) { enchantments.add(enchantment.getInt("id"), enchantment.getInt("lvl")); } data.set(key, enchantments); } public void setRewriteIds(final boolean rewriteIds) { this.rewriteIds = rewriteIds; } @FunctionalInterface public interface DescriptionSupplier { Tag get(int id, int level); } private record PendingIdChange(int id, int mappedId, int level) { } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/text/JsonNBTComponentRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters.text; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.TranslatableMappings; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; import com.viaversion.viaversion.libs.gson.JsonObject; import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; public class JsonNBTComponentRewriter extends com.viaversion.viaversion.rewriter.text.JsonNBTComponentRewriter implements TranslatableRewriter { private final Map translatables; public JsonNBTComponentRewriter(final BackwardsProtocol protocol, final ReadType type) { super(protocol, type); this.translatables = TranslatableMappings.translatablesFor(protocol); } public JsonNBTComponentRewriter(final BackwardsProtocol protocol, final ReadType type, final String version) { super(protocol, type); this.translatables = TranslatableMappings.translatablesFor(version); } @Override protected void handleTranslate(final JsonObject root, final String translate) { final String newTranslate = mappedTranslationKey(translate); if (newTranslate != null) { root.addProperty("translate", newTranslate); } } @Override protected void handleTranslate(final UserConnection connection, final CompoundTag parentTag, final StringTag translateTag) { final String newTranslate = mappedTranslationKey(translateTag.getValue()); if (newTranslate != null) { parentTag.put("translate", new StringTag(newTranslate)); } } @Override public @Nullable String mappedTranslationKey(final String translationKey) { return translatables.get(translationKey); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/text/NBTComponentRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters.text; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.TranslatableMappings; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; import com.viaversion.viaversion.libs.gson.JsonObject; import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; public class NBTComponentRewriter extends com.viaversion.viaversion.rewriter.text.NBTComponentRewriter implements TranslatableRewriter { private final Map translatables; public NBTComponentRewriter(final BackwardsProtocol protocol) { super(protocol); this.translatables = TranslatableMappings.translatablesFor(protocol); } public NBTComponentRewriter(final BackwardsProtocol protocol, final String version) { super(protocol); this.translatables = TranslatableMappings.translatablesFor(version); } @Override protected void handleTranslate(final JsonObject root, final String translate) { final String newTranslate = mappedTranslationKey(translate); if (newTranslate != null) { root.addProperty("translate", newTranslate); } } @Override protected void handleTranslate(final UserConnection connection, final CompoundTag parentTag, final StringTag translateTag) { final String newTranslate = mappedTranslationKey(translateTag.getValue()); if (newTranslate != null) { parentTag.put("translate", new StringTag(newTranslate)); } } @Override public @Nullable String mappedTranslationKey(final String translationKey) { return translatables.get(translationKey); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/api/rewriters/text/TranslatableRewriter.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.api.rewriters.text; import com.viaversion.viaversion.api.rewriter.ComponentRewriter; import org.checkerframework.checker.nullness.qual.Nullable; public interface TranslatableRewriter extends ComponentRewriter { @Nullable String mappedTranslationKey(String translationKey); } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/item/DataItemWithExtras.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.item; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viaversion.api.minecraft.item.DataItem; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.libs.gson.JsonParser; import com.viaversion.viaversion.libs.gson.JsonPrimitive; import com.viaversion.viaversion.libs.gson.JsonSyntaxException; import java.util.ArrayList; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; /** * Prevent expensive parsing/toString by checking against cached JsonElement instances, used from 1.14 to 1.20.5. *

* When using this, be careful not to break caching by modifying the display tag directly. */ public final class DataItemWithExtras extends DataItem { private JsonElement name; private List lore; public DataItemWithExtras(final Item from) { setIdentifier(from.identifier()); setAmount(from.amount()); setData(from.data()); setTag(from.tag()); if (tag() == null) { return; } final CompoundTag display = tag().getCompoundTag("display"); if (display == null) { return; } final StringTag name = display.getStringTag("Name"); if (name != null) { this.name = parse(name.getValue()); } final ListTag lore = display.getListTag("Lore", StringTag.class); if (lore != null) { this.lore = new ArrayList<>(lore.size()); for (int i = 0; i < lore.size(); i++) { this.lore.add(parse(lore.get(i).getValue())); } } } public @Nullable JsonElement name() { return name; } public @Nullable StringTag rawName() { if (tag() == null) { return null; } final CompoundTag display = tag().getCompoundTag("display"); return display != null ? display.getStringTag("Name") : null; } public @Nullable List lore() { return lore; } public @Nullable ListTag rawLore() { if (tag() == null) { return null; } final CompoundTag display = tag().getCompoundTag("display"); return display != null ? display.getListTag("Lore", StringTag.class) : null; } private JsonElement parse(final String value) { try { return JsonParser.parseString(value); } catch (final JsonSyntaxException e) { return new JsonPrimitive(value); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/registration/BackwardsRegistrations.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.registration; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.protocol.shared_registration.SharedRegistrations; public final class BackwardsRegistrations { private static final SharedRegistrations REGISTRATIONS = SharedRegistrations.create(); public static void apply() { REGISTRATIONS.registrations() .range(ProtocolVersion.v1_10, ProtocolVersion.v1_19_3, RegistryRegistrations::registerNamedSound1_10) .since(ProtocolVersion.v1_14, RegistryRegistrations::registerStopSound1_14) .register(); } public static SharedRegistrations registrations() { return REGISTRATIONS; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/registration/RegistryRegistrations.java ================================================ /* * This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.registration; import com.viaversion.viabackwards.api.rewriters.SoundRewriter; import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; import com.viaversion.viaversion.protocol.shared_registration.PacketBound; import com.viaversion.viaversion.protocol.shared_registration.RegistrationContext; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ClientboundPackets1_14; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; final class RegistryRegistrations { static void registerNamedSound1_10(final RegistrationContext ctx) { ctx.clientbound(ClientboundPackets1_9_3.CUSTOM_SOUND, new SoundRewriter<>(ctx.protocol())::registerNamedSound, PacketBound.REMOVED_AT_MAX); } static void registerStopSound1_14(final RegistrationContext ctx) { ctx.clientbound(ClientboundPackets1_14.STOP_SOUND, new SoundRewriter<>(ctx.protocol())::registerStopSound); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/template/BlockItemPacketRewriter99_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.template; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.rewriters.BackwardsStructuredItemRewriter; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPacket26_1; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundPacket1_21_9; // To replace if needed: // ChunkType1_21_5 // RecipeDisplayRewriter final class BlockItemPacketRewriter99_1 extends BackwardsStructuredItemRewriter { public BlockItemPacketRewriter99_1(final Protocol99_1To98_1 protocol) { super(protocol); } @Override public void registerPackets() { } @Override protected void handleItemDataComponentsToClient(final UserConnection connection, final Item item, final StructuredDataContainer container) { super.handleItemDataComponentsToClient(connection, item, container); // downgradeData(item, container); // static import VV method } @Override protected void handleItemDataComponentsToServer(final UserConnection connection, final Item item, final StructuredDataContainer container) { super.handleItemDataComponentsToServer(connection, item, container); // upgradeData(item, container); // static import VV method } @Override protected void restoreBackupData(final Item item, final StructuredDataContainer container, final CompoundTag customData) { super.restoreBackupData(item, container, customData); // restore any data if needed here } @Override protected void backupInconvertibleData(final UserConnection connection, final Item item, final StructuredDataContainer dataContainer, final CompoundTag backupTag) { super.backupInconvertibleData(connection, item, dataContainer, backupTag); // back up any data if needed here, called before the method below } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/template/ComponentRewriter99_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.template; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.text.NBTComponentRewriter; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPacket26_1; final class ComponentRewriter99_1 extends NBTComponentRewriter { public ComponentRewriter99_1(final BackwardsProtocol protocol) { super(protocol); } @Override protected void handleShowItem(final UserConnection connection, final CompoundTag itemTag, final CompoundTag componentsTag) { super.handleShowItem(connection, itemTag, componentsTag); if (componentsTag == null) { return; } // Remove or update data from componentsTag // Newly added data which is not handled otherwise needs to be removed to prevent errors on the client } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/template/EntityPacketRewriter99_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.template; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_11; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes26_1; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPacket26_1; // Replace if needed // VersionedTypes final class EntityPacketRewriter99_1 extends EntityRewriter { private static final EntityDataTypes26_1 MAPPED_DATA_TYPES = VersionedTypes.V26_1.entityDataTypes; public EntityPacketRewriter99_1(final Protocol99_1To98_1 protocol) { super(protocol, MAPPED_DATA_TYPES.optionalComponentType, MAPPED_DATA_TYPES.booleanType); } @Override public void registerPackets() { } @Override protected void registerRewrites() { dataTypeMapper().register(); /* ... or like this for additions and removals that are not at the very end dataTypeMapper() .added(MAPPED_DATA_TYPES.catSoundVariant) .removed(MAPPED_DATA_TYPES.cowSoundVariant) .skip(MAPPED_DATA_TYPES.pigSoundVariant) // if neither removed nor added, but the value type has to be changed separately .register();*/ registerEntityDataTypeHandler1_20_3( MAPPED_DATA_TYPES.itemType, MAPPED_DATA_TYPES.blockStateType, MAPPED_DATA_TYPES.optionalBlockStateType, MAPPED_DATA_TYPES.particleType, MAPPED_DATA_TYPES.particlesType, MAPPED_DATA_TYPES.componentType, MAPPED_DATA_TYPES.optionalComponentType ); // Remove entity data of new entity type // filter().type(EntityTypes1_21_11.SNIFFER).removeIndex(newIndex); } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); // mapEntityTypeWithData(EntityTypes1_21_11.SNIFFER, EntityTypes1_21_11.RAVAGER).tagName(); } @Override public EntityType typeFromId(final int type) { return EntityTypes1_21_11.getTypeFromId(type); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/template/Protocol99_1To98_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.template; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter; import com.viaversion.viabackwards.api.rewriters.text.NBTComponentRewriter; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_11; 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.chunk.ChunkType26_1; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.api.type.types.version.VersionedTypesHolder; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.data.item.ItemHasherBase; import com.viaversion.viaversion.protocols.v1_21_11to26_1.Protocol1_21_11To26_1; import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPacket26_1; import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPackets26_1; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.rewriter.RecipeDisplayRewriter1_21_5; import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPackets1_21_6; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundConfigurationPackets1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundConfigurationPackets1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundPacket1_21_9; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.RecipeDisplayRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.block.BlockRewriter1_21_5; import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap; // Placeholders to replace (in the entire package): // Protocol1_21_11To26_1 (the ViaVersion protocol class the mappings depend on) // ClientboundPacket26_1 // ServerboundPacket1_21_9 // ClientboundConfigurationPackets1_21 // ServerboundConfigurationPackets1_20_5 // EntityTypes1_21_11 (UNMAPPED type) // VersionedTypes.V26_1 // 99.1, 98.1 final class Protocol99_1To98_1 extends BackwardsProtocol { // ViaBackwards uses its own mappings and also needs a translatablerewriter for translation mappings public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("99.1", "98.1", Protocol1_21_11To26_1.class); // Change the VV (!) protocol class private final EntityPacketRewriter99_1 entityRewriter = new EntityPacketRewriter99_1(this); private final BlockItemPacketRewriter99_1 itemRewriter = new BlockItemPacketRewriter99_1(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final NBTComponentRewriter translatableRewriter = new ComponentRewriter99_1(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BackwardsRegistryRewriter registryDataRewriter = new BackwardsRegistryRewriter(this); private final BlockRewriter blockRewriter = new BlockRewriter1_21_5<>(this, ChunkType26_1::new); private final RecipeDisplayRewriter recipeRewriter = new RecipeDisplayRewriter1_21_5<>(this); public Protocol99_1To98_1() { super(ClientboundPacket26_1.class, ClientboundPacket26_1.class, ServerboundPacket1_21_9.class, ServerboundPacket1_21_9.class); } @Override protected void registerPackets() { super.registerPackets(); } @Override public void init(final UserConnection connection) { addEntityTracker(connection, new EntityTrackerBase(connection, EntityTypes1_21_11.PLAYER)); addItemHasher(connection, new ItemHasherBase(this, connection)); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter99_1 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter99_1 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public RecipeDisplayRewriter getRecipeRewriter() { return recipeRewriter; } @Override public BackwardsRegistryRewriter getRegistryDataRewriter() { return registryDataRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public NBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } @Override public VersionedTypesHolder types() { return VersionedTypes.V26_1; } @Override public VersionedTypesHolder mappedTypes() { return VersionedTypes.V26_1; } @Override protected PacketTypesProvider createPacketTypesProvider() { return new SimplePacketTypesProvider<>( packetTypeMap(unmappedClientboundPacketType, ClientboundPackets26_1.class, ClientboundConfigurationPackets1_21_9.class), packetTypeMap(mappedClientboundPacketType, ClientboundPackets26_1.class, ClientboundConfigurationPackets1_21_9.class), packetTypeMap(mappedServerboundPacketType, ServerboundPackets1_21_6.class, ServerboundConfigurationPackets1_21_9.class), packetTypeMap(unmappedServerboundPacketType, ServerboundPackets1_21_6.class, ServerboundConfigurationPackets1_21_9.class) ); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_10to1_9_3/Protocol1_10To1_9_3.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_10to1_9_3; 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.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_10to1_9_3.rewriter.BlockItemPacketRewriter1_10; import com.viaversion.viabackwards.protocol.v1_10to1_9_3.rewriter.EntityPacketRewriter1_10; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_10; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.protocol.remapper.ValueTransformer; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ServerboundPackets1_9_3; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; public class Protocol1_10To1_9_3 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.10", "1.9.4"); private static final ValueTransformer TO_OLD_PITCH = new ValueTransformer<>(Types.UNSIGNED_BYTE) { public Short transform(PacketWrapper packetWrapper, Float inputValue) { return (short) Math.round(inputValue * 63.5F); } }; private final EntityPacketRewriter1_10 entityRewriter = new EntityPacketRewriter1_10(this); private final BlockItemPacketRewriter1_10 itemRewriter = new BlockItemPacketRewriter1_10(this); public Protocol1_10To1_9_3() { super(ClientboundPackets1_9_3.class, ClientboundPackets1_9_3.class, ServerboundPackets1_9_3.class, ServerboundPackets1_9_3.class); } @Override protected void registerPackets() { entityRewriter.register(); itemRewriter.register(); SoundRewriter soundRewriter = new SoundRewriter<>(this); replaceClientbound(ClientboundPackets1_9_3.CUSTOM_SOUND, new PacketHandlers() { @Override public void register() { map(Types.STRING); // 0 - Sound name map(Types.VAR_INT); // 1 - Sound Category map(Types.INT); // 2 - x map(Types.INT); // 3 - y map(Types.INT); // 4 - z map(Types.FLOAT); // 5 - Volume map(Types.FLOAT, TO_OLD_PITCH); // 6 - Pitch handler(soundRewriter.getNamedSoundHandler()); } }); replaceClientbound(ClientboundPackets1_9_3.SOUND, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Sound name map(Types.VAR_INT); // 1 - Sound Category map(Types.INT); // 2 - x map(Types.INT); // 3 - y map(Types.INT); // 4 - z map(Types.FLOAT); // 5 - Volume map(Types.FLOAT, TO_OLD_PITCH); // 6 - Pitch handler(soundRewriter.getSoundHandler()); } }); registerServerbound(ServerboundPackets1_9_3.RESOURCE_PACK, new PacketHandlers() { @Override public void register() { read(Types.STRING); // 0 - Hash map(Types.VAR_INT); // 1 - Result } }); JsonNBTComponentRewriter componentRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); componentRewriter.registerComponentPacket(ClientboundPackets1_9_3.CHAT); } @Override public void init(UserConnection user) { user.addEntityTracker(this.getClass(), new EntityTrackerBase(user, EntityTypes1_10.EntityType.PLAYER)); user.addClientWorld(this.getClass(), new ClientWorld()); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_10 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_10 getItemRewriter() { return itemRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_10to1_9_3/rewriter/BlockItemPacketRewriter1_10.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_10to1_9_3.rewriter; import com.viaversion.viabackwards.api.rewriters.LegacyBlockItemRewriter; import com.viaversion.viabackwards.protocol.v1_10to1_9_3.Protocol1_10To1_9_3; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_9_3; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ServerboundPackets1_9_3; public class BlockItemPacketRewriter1_10 extends LegacyBlockItemRewriter { public BlockItemPacketRewriter1_10(Protocol1_10To1_9_3 protocol) { super(protocol, "1.10"); } @Override protected void registerPackets() { registerBlockChange(ClientboundPackets1_9_3.BLOCK_UPDATE); registerMultiBlockChange(ClientboundPackets1_9_3.CHUNK_BLOCKS_UPDATE); registerSetSlot(ClientboundPackets1_9_3.CONTAINER_SET_SLOT); registerSetContent(ClientboundPackets1_9_3.CONTAINER_SET_CONTENT); registerSetEquippedItem(ClientboundPackets1_9_3.SET_EQUIPPED_ITEM); registerCustomPayloadTradeList(ClientboundPackets1_9_3.CUSTOM_PAYLOAD); registerContainerClick(ServerboundPackets1_9_3.CONTAINER_CLICK); registerSetCreativeModeSlot(ServerboundPackets1_9_3.SET_CREATIVE_MODE_SLOT); protocol.registerClientbound(ClientboundPackets1_9_3.LEVEL_CHUNK, wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_10To1_9_3.class); ChunkType1_9_3 type = ChunkType1_9_3.forEnvironment(clientWorld.getEnvironment()); Chunk chunk = wrapper.passthrough(type); handleChunk(chunk); }); // Rewrite entity data items protocol.getEntityRewriter().filter().handler((event, data) -> { if (data.dataType().type().equals(Types.ITEM1_8)) // Is Item data.setValue(handleItemToClient(event.user(), (Item) data.getValue())); }); // Particle protocol.registerClientbound(ClientboundPackets1_9_3.LEVEL_PARTICLES, new PacketHandlers() { @Override public void register() { map(Types.INT); map(Types.BOOLEAN); map(Types.FLOAT); map(Types.FLOAT); map(Types.FLOAT); map(Types.FLOAT); map(Types.FLOAT); map(Types.FLOAT); map(Types.FLOAT); map(Types.INT); handler(wrapper -> { int id = wrapper.get(Types.INT, 0); if (id == 46) { // new falling_dust wrapper.set(Types.INT, 0, 38); // -> block_dust } }); } }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_10to1_9_3/rewriter/EntityPacketRewriter1_10.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_10to1_9_3.rewriter; import com.viaversion.viabackwards.api.entities.storage.EntityReplacement; import com.viaversion.viabackwards.api.entities.storage.WrappedEntityData; import com.viaversion.viabackwards.api.rewriters.LegacyEntityRewriter; import com.viaversion.viabackwards.protocol.v1_10to1_9_3.Protocol1_10To1_9_3; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_10; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_11; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_9; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; import java.util.List; public class EntityPacketRewriter1_10 extends LegacyEntityRewriter { public EntityPacketRewriter1_10(Protocol1_10To1_9_3 protocol) { super(protocol); } @Override protected void registerPackets() { protocol.registerClientbound(ClientboundPackets1_9_3.ADD_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.UUID); // 1 - UUID map(Types.BYTE); // 2 - Type map(Types.DOUBLE); // 3 - x map(Types.DOUBLE); // 4 - y map(Types.DOUBLE); // 5 - z map(Types.BYTE); // 6 - Pitch map(Types.BYTE); // 7 - Yaw map(Types.INT); // 8 - data // Track Entity handler(getObjectTrackerHandler()); handler(getObjectRewriter(EntityTypes1_11.ObjectType::findById)); handler(protocol.getItemRewriter().getFallingBlockHandler()); } }); registerTracker(ClientboundPackets1_9_3.ADD_EXPERIENCE_ORB, EntityTypes1_10.EntityType.EXPERIENCE_ORB); registerTracker(ClientboundPackets1_9_3.ADD_GLOBAL_ENTITY, EntityTypes1_10.EntityType.LIGHTNING_BOLT); protocol.registerClientbound(ClientboundPackets1_9_3.ADD_MOB, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.UUID); // 1 - UUID map(Types.UNSIGNED_BYTE); // 2 - Entity Type map(Types.DOUBLE); // 3 - X map(Types.DOUBLE); // 4 - Y map(Types.DOUBLE); // 5 - Z map(Types.BYTE); // 6 - Yaw map(Types.BYTE); // 7 - Pitch map(Types.BYTE); // 8 - Head Pitch map(Types.SHORT); // 9 - Velocity X map(Types.SHORT); // 10 - Velocity Y map(Types.SHORT); // 11 - Velocity Z map(Types.ENTITY_DATA_LIST1_9); // 12 - Entity data // Track entity handler(getTrackerHandler(Types.UNSIGNED_BYTE, 0)); // Rewrite entity type / data handler(wrapper -> { int entityId = wrapper.get(Types.VAR_INT, 0); EntityType type = tracker(wrapper.user()).entityType(entityId); if (type == null) { return; } List entityDataList = wrapper.get(Types.ENTITY_DATA_LIST1_9, 0); handleEntityData(wrapper.get(Types.VAR_INT, 0), entityDataList, wrapper.user()); EntityReplacement entityReplacement = entityDataForType(type); if (entityReplacement != null) { WrappedEntityData storage = new WrappedEntityData(entityDataList); wrapper.set(Types.UNSIGNED_BYTE, 0, (short) entityReplacement.replacementId()); if (entityReplacement.hasBaseData()) entityReplacement.defaultData().createData(storage); } }); } }); registerTracker(ClientboundPackets1_9_3.ADD_PAINTING, EntityTypes1_10.EntityType.PAINTING); registerJoinGame(ClientboundPackets1_9_3.LOGIN, EntityTypes1_10.EntityType.PLAYER); registerRespawn(ClientboundPackets1_9_3.RESPAWN); protocol.registerClientbound(ClientboundPackets1_9_3.ADD_PLAYER, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.UUID); // 1 - Player UUID map(Types.DOUBLE); // 2 - X map(Types.DOUBLE); // 3 - Y map(Types.DOUBLE); // 4 - Z map(Types.BYTE); // 5 - Yaw map(Types.BYTE); // 6 - Pitch map(Types.ENTITY_DATA_LIST1_9); // 7 - Entity data list handler(getTrackerAndDataHandler(Types.ENTITY_DATA_LIST1_9, EntityTypes1_11.EntityType.PLAYER)); } }); registerRemoveEntities(ClientboundPackets1_9_3.REMOVE_ENTITIES); registerSetEntityData(ClientboundPackets1_9_3.SET_ENTITY_DATA, Types.ENTITY_DATA_LIST1_9); } @Override protected void registerRewrites() { mapEntityTypeWithData(EntityTypes1_10.EntityType.POLAR_BEAR, EntityTypes1_10.EntityType.SHEEP).plainName(); // Change the sheep color when the polar bear is standing up (index 13 -> Standing up) filter().type(EntityTypes1_10.EntityType.POLAR_BEAR).index(13).handler((event, data) -> { boolean b = (boolean) data.getValue(); data.setTypeAndValue(EntityDataTypes1_9.BYTE, b ? (byte) (14 & 0x0F) : (byte) (0)); }); // Handle husk (index 13 -> Zombie Type) filter().type(EntityTypes1_10.EntityType.ZOMBIE).index(13).handler((event, data) -> { if ((int) data.getValue() == 6) { // Is type Husk data.setValue(0); } }); // Handle Stray (index 12 -> Skeleton Type) filter().type(EntityTypes1_10.EntityType.SKELETON).index(12).handler((event, data) -> { if ((int) data.getValue() == 2) { data.setValue(0); // Change to default skeleton } }); // Added index from incorrect entity data hierarchy, where potions add to item entities filter().type(EntityTypes1_10.EntityType.POTION).addIndex(6); // Handle the missing NoGravity tag for every entity data filter().removeIndex(5); } @Override public EntityType typeFromId(int typeId) { return EntityTypes1_10.EntityType.findById(typeId); } @Override public EntityType objectTypeFromId(int typeId, int data) { return EntityTypes1_10.ObjectType.getEntityType(typeId, data); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_11_1to1_11/Protocol1_11_1To1_11.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_11_1to1_11; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.protocol.v1_11_1to1_11.rewriter.EntityPacketRewriter1_11_1; import com.viaversion.viabackwards.protocol.v1_11_1to1_11.rewriter.ItemPacketRewriter1_11_1; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_11; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ServerboundPackets1_9_3; public class Protocol1_11_1To1_11 extends BackwardsProtocol { private final EntityPacketRewriter1_11_1 entityRewriter = new EntityPacketRewriter1_11_1(this); private final ItemPacketRewriter1_11_1 itemRewriter = new ItemPacketRewriter1_11_1(this); public Protocol1_11_1To1_11() { super(ClientboundPackets1_9_3.class, ClientboundPackets1_9_3.class, ServerboundPackets1_9_3.class, ServerboundPackets1_9_3.class); } @Override public void init(UserConnection user) { user.addEntityTracker(this.getClass(), new EntityTrackerBase(user, EntityTypes1_11.EntityType.PLAYER)); user.addClientWorld(this.getClass(), new ClientWorld()); } @Override public EntityPacketRewriter1_11_1 getEntityRewriter() { return entityRewriter; } @Override public ItemPacketRewriter1_11_1 getItemRewriter() { return itemRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_11_1to1_11/rewriter/EntityPacketRewriter1_11_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_11_1to1_11.rewriter; import com.viaversion.viabackwards.api.rewriters.LegacyEntityRewriter; import com.viaversion.viabackwards.protocol.v1_11_1to1_11.Protocol1_11_1To1_11; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_11; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; public class EntityPacketRewriter1_11_1 extends LegacyEntityRewriter { public EntityPacketRewriter1_11_1(Protocol1_11_1To1_11 protocol) { super(protocol); } @Override protected void registerPackets() { protocol.registerClientbound(ClientboundPackets1_9_3.ADD_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.UUID); // 1 - UUID map(Types.BYTE); // 2 - Type map(Types.DOUBLE); // 3 - x map(Types.DOUBLE); // 4 - y map(Types.DOUBLE); // 5 - z map(Types.BYTE); // 6 - Pitch map(Types.BYTE); // 7 - Yaw map(Types.INT); // 8 - data // Track Entity handler(getObjectTrackerHandler()); handler(getObjectRewriter(EntityTypes1_11.ObjectType::findById)); } }); registerTracker(ClientboundPackets1_9_3.ADD_EXPERIENCE_ORB, EntityTypes1_11.EntityType.EXPERIENCE_ORB); registerTracker(ClientboundPackets1_9_3.ADD_GLOBAL_ENTITY, EntityTypes1_11.EntityType.LIGHTNING_BOLT); protocol.registerClientbound(ClientboundPackets1_9_3.ADD_MOB, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.UUID); // 1 - UUID map(Types.VAR_INT); // 2 - Entity Type map(Types.DOUBLE); // 3 - X map(Types.DOUBLE); // 4 - Y map(Types.DOUBLE); // 5 - Z map(Types.BYTE); // 6 - Yaw map(Types.BYTE); // 7 - Pitch map(Types.BYTE); // 8 - Head Pitch map(Types.SHORT); // 9 - Velocity X map(Types.SHORT); // 10 - Velocity Y map(Types.SHORT); // 11 - Velocity Z map(Types.ENTITY_DATA_LIST1_9); // 12 - Entity data // Track entity handler(getTrackerHandler()); // Rewrite entity type / data handler(getMobSpawnRewriter1_11(Types.ENTITY_DATA_LIST1_9)); } }); registerTracker(ClientboundPackets1_9_3.ADD_PAINTING, EntityTypes1_11.EntityType.PAINTING); registerJoinGame(ClientboundPackets1_9_3.LOGIN, EntityTypes1_11.EntityType.PLAYER); registerRespawn(ClientboundPackets1_9_3.RESPAWN); protocol.registerClientbound(ClientboundPackets1_9_3.ADD_PLAYER, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.UUID); // 1 - Player UUID map(Types.DOUBLE); // 2 - X map(Types.DOUBLE); // 3 - Y map(Types.DOUBLE); // 4 - Z map(Types.BYTE); // 5 - Yaw map(Types.BYTE); // 6 - Pitch map(Types.ENTITY_DATA_LIST1_9); // 7 - Entity data list handler(getTrackerAndDataHandler(Types.ENTITY_DATA_LIST1_9, EntityTypes1_11.EntityType.PLAYER)); } }); registerRemoveEntities(ClientboundPackets1_9_3.REMOVE_ENTITIES); registerSetEntityData(ClientboundPackets1_9_3.SET_ENTITY_DATA, Types.ENTITY_DATA_LIST1_9); } @Override protected void registerRewrites() { // Handle non-existing firework entity data (index 7 entity id for boosting) filter().type(EntityTypes1_11.EntityType.FIREWORK_ROCKET).cancel(7); // Handle non-existing pig entity data (index 14 - boost time) filter().type(EntityTypes1_11.EntityType.PIG).cancel(14); } @Override public EntityType typeFromId(int typeId) { return EntityTypes1_11.EntityType.findById(typeId); } @Override public EntityType objectTypeFromId(int typeId, int data) { return EntityTypes1_11.ObjectType.getEntityType(typeId, data); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_11_1to1_11/rewriter/ItemPacketRewriter1_11_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_11_1to1_11.rewriter; import com.viaversion.viabackwards.api.rewriters.LegacyBlockItemRewriter; import com.viaversion.viabackwards.api.rewriters.LegacyEnchantmentRewriter; import com.viaversion.viabackwards.protocol.v1_11_1to1_11.Protocol1_11_1To1_11; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ServerboundPackets1_9_3; public class ItemPacketRewriter1_11_1 extends LegacyBlockItemRewriter { private LegacyEnchantmentRewriter enchantmentRewriter; public ItemPacketRewriter1_11_1(Protocol1_11_1To1_11 protocol) { super(protocol, "1.11.1"); } @Override protected void registerPackets() { registerSetSlot(ClientboundPackets1_9_3.CONTAINER_SET_SLOT); registerSetContent(ClientboundPackets1_9_3.CONTAINER_SET_CONTENT); registerSetEquippedItem(ClientboundPackets1_9_3.SET_EQUIPPED_ITEM); registerCustomPayloadTradeList(ClientboundPackets1_9_3.CUSTOM_PAYLOAD); registerContainerClick(ServerboundPackets1_9_3.CONTAINER_CLICK); registerSetCreativeModeSlot(ServerboundPackets1_9_3.SET_CREATIVE_MODE_SLOT); // Handle item entity data protocol.getEntityRewriter().filter().handler((event, data) -> { if (data.dataType().type().equals(Types.ITEM1_8)) { // Is Item data.setValue(handleItemToClient(event.user(), (Item) data.getValue())); } }); } @Override protected void registerRewrites() { enchantmentRewriter = new LegacyEnchantmentRewriter(nbtTagName()); enchantmentRewriter.registerEnchantment(22, "§7Sweeping Edge"); } @Override public Item handleItemToClient(UserConnection connection, Item item) { if (item == null) return null; super.handleItemToClient(connection, item); enchantmentRewriter.handleToClient(item); return item; } @Override public Item handleItemToServer(UserConnection connection, Item item) { if (item == null) return null; item = super.handleItemToServer(connection, item); enchantmentRewriter.handleToServer(item); return item; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_11to1_10/Protocol1_11To1_10.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_11to1_10; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_11to1_10.rewriter.BlockItemPacketRewriter1_11; import com.viaversion.viabackwards.protocol.v1_11to1_10.rewriter.EntityPacketRewriter1_11; import com.viaversion.viabackwards.protocol.v1_11to1_10.rewriter.PlayerPacketRewriter1_11; import com.viaversion.viabackwards.protocol.v1_11to1_10.storage.WindowTracker; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_11; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ServerboundPackets1_9_3; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; public class Protocol1_11To1_10 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.11", "1.10"); private final EntityPacketRewriter1_11 entityRewriter = new EntityPacketRewriter1_11(this); private final BlockItemPacketRewriter1_11 itemRewriter = new BlockItemPacketRewriter1_11(this); private JsonNBTComponentRewriter componentRewriter; public Protocol1_11To1_10() { super(ClientboundPackets1_9_3.class, ClientboundPackets1_9_3.class, ServerboundPackets1_9_3.class, ServerboundPackets1_9_3.class); } @Override protected void registerPackets() { entityRewriter.register(); itemRewriter.register(); PlayerPacketRewriter1_11.register(this); componentRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); componentRewriter.registerComponentPacket(ClientboundPackets1_9_3.CHAT); } @Override public void init(UserConnection user) { user.addEntityTracker(this.getClass(), new EntityTrackerBase(user, EntityTypes1_11.EntityType.PLAYER)); user.addClientWorld(this.getClass(), new ClientWorld()); if (!user.has(WindowTracker.class)) { user.put(new WindowTracker()); } } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_11 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_11 getItemRewriter() { return itemRewriter; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return componentRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_11to1_10/data/SplashPotionMappings1_10.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_11to1_10.data; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntOpenHashMap; public class SplashPotionMappings1_10 { private static final Int2IntMap DATA = new Int2IntOpenHashMap(14, 0.99F); static { DATA.defaultReturnValue(-1); DATA.put(2039713, 5); // night vision DATA.put(8356754, 7); // invisibility DATA.put(2293580, 9); // jump boost DATA.put(14981690, 12); // fire resistance DATA.put(8171462, 14); // swiftness DATA.put(5926017, 17); // slowness DATA.put(3035801, 19); // water breathing DATA.put(16262179, 21); // instant health DATA.put(4393481, 23); // instant damage DATA.put(5149489, 25); // poison DATA.put(13458603, 28); // regeneration DATA.put(9643043, 31); // strength DATA.put(4738376, 34); // weakness DATA.put(3381504, 36); // luck } public static int getOldData(int data) { return DATA.get(data); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_11to1_10/rewriter/BlockItemPacketRewriter1_11.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_11to1_10.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.api.data.MappedLegacyBlockItem; import com.viaversion.viabackwards.api.rewriters.LegacyBlockItemRewriter; import com.viaversion.viabackwards.api.rewriters.LegacyEnchantmentRewriter; import com.viaversion.viabackwards.protocol.v1_11to1_10.Protocol1_11To1_10; import com.viaversion.viabackwards.protocol.v1_11to1_10.storage.ChestedHorseStorage; import com.viaversion.viabackwards.protocol.v1_11to1_10.storage.WindowTracker; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.data.entity.StoredEntityData; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_11; import com.viaversion.viaversion.api.minecraft.item.DataItem; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_9_3; import com.viaversion.viaversion.protocols.v1_10to1_11.data.EntityMappings1_11; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ServerboundPackets1_9_3; import com.viaversion.viaversion.util.IdAndData; import java.util.Arrays; import java.util.Optional; public class BlockItemPacketRewriter1_11 extends LegacyBlockItemRewriter { private LegacyEnchantmentRewriter enchantmentRewriter; public BlockItemPacketRewriter1_11(Protocol1_11To1_10 protocol) { super(protocol, "1.11"); } @Override protected void registerPackets() { registerBlockChange(ClientboundPackets1_9_3.BLOCK_UPDATE); registerMultiBlockChange(ClientboundPackets1_9_3.CHUNK_BLOCKS_UPDATE); protocol.registerClientbound(ClientboundPackets1_9_3.CONTAINER_SET_SLOT, new PacketHandlers() { @Override public void register() { map(Types.BYTE); // 0 - Window ID map(Types.SHORT); // 1 - Slot ID map(Types.ITEM1_8); // 2 - Slot Value handler(wrapper -> handleItemToClient(wrapper.user(), wrapper.get(Types.ITEM1_8, 0))); // Handle Llama handler(wrapper -> { if (isLlama(wrapper.user())) { Optional horse = getChestedHorse(wrapper.user()); if (horse.isEmpty()) { return; } ChestedHorseStorage storage = horse.get(); int currentSlot = wrapper.get(Types.SHORT, 0); wrapper.set(Types.SHORT, 0, ((Integer) (currentSlot = getNewSlotId(storage, currentSlot))).shortValue()); wrapper.set(Types.ITEM1_8, 0, getNewItem(storage, currentSlot, wrapper.get(Types.ITEM1_8, 0))); } }); } }); protocol.registerClientbound(ClientboundPackets1_9_3.CONTAINER_SET_CONTENT, new PacketHandlers() { @Override public void register() { map(Types.UNSIGNED_BYTE); // 0 - Window ID map(Types.ITEM1_8_SHORT_ARRAY); // 1 - Window Values handler(wrapper -> { Item[] stacks = wrapper.get(Types.ITEM1_8_SHORT_ARRAY, 0); for (int i = 0; i < stacks.length; i++) stacks[i] = handleItemToClient(wrapper.user(), stacks[i]); if (isLlama(wrapper.user())) { Optional horse = getChestedHorse(wrapper.user()); if (horse.isEmpty()) { return; } ChestedHorseStorage storage = horse.get(); stacks = Arrays.copyOf(stacks, !storage.isChested() ? 38 : 53); for (int i = stacks.length - 1; i >= 0; i--) { stacks[getNewSlotId(storage, i)] = stacks[i]; stacks[i] = getNewItem(storage, i, stacks[i]); } wrapper.set(Types.ITEM1_8_SHORT_ARRAY, 0, stacks); } }); } }); registerSetEquippedItem(ClientboundPackets1_9_3.SET_EQUIPPED_ITEM); registerCustomPayloadTradeList(ClientboundPackets1_9_3.CUSTOM_PAYLOAD); protocol.registerServerbound(ServerboundPackets1_9_3.CONTAINER_CLICK, new PacketHandlers() { @Override public void register() { map(Types.BYTE); // 0 - Window ID map(Types.SHORT); // 1 - Slot map(Types.BYTE); // 2 - Button map(Types.SHORT); // 3 - Action number map(Types.VAR_INT); // 4 - Mode map(Types.ITEM1_8); // 5 - Clicked Item handler(wrapper -> handleItemToServer(wrapper.user(), wrapper.get(Types.ITEM1_8, 0))); // Llama slot handler(wrapper -> { if (isLlama(wrapper.user())) { Optional horse = getChestedHorse(wrapper.user()); if (horse.isEmpty()) { return; } ChestedHorseStorage storage = horse.get(); int clickSlot = wrapper.get(Types.SHORT, 0); int correctSlot = getOldSlotId(storage, clickSlot); wrapper.set(Types.SHORT, 0, ((Integer) correctSlot).shortValue()); } }); } }); registerSetCreativeModeSlot(ServerboundPackets1_9_3.SET_CREATIVE_MODE_SLOT); protocol.registerClientbound(ClientboundPackets1_9_3.LEVEL_CHUNK, wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_11To1_10.class); ChunkType1_9_3 type = ChunkType1_9_3.forEnvironment(clientWorld.getEnvironment()); // Use the 1.10 Chunk type since nothing changed. Chunk chunk = wrapper.passthrough(type); handleChunk(chunk); // only patch it for signs for now for (CompoundTag tag : chunk.getBlockEntities()) { StringTag idTag = tag.getStringTag("id"); if (idTag == null) continue; String id = idTag.getValue(); if (id.equals("minecraft:sign")) { idTag.setValue("Sign"); } } }); protocol.registerClientbound(ClientboundPackets1_9_3.BLOCK_ENTITY_DATA, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_8); // 0 - Position map(Types.UNSIGNED_BYTE); // 1 - Action map(Types.NAMED_COMPOUND_TAG); // 2 - NBT handler(wrapper -> { // Remove on shulkerbox decleration if (wrapper.get(Types.UNSIGNED_BYTE, 0) == 10) { wrapper.cancel(); } // Handler Spawners if (wrapper.get(Types.UNSIGNED_BYTE, 0) == 1) { CompoundTag tag = wrapper.get(Types.NAMED_COMPOUND_TAG, 0); EntityMappings1_11.toClientSpawner(tag, true); } }); } }); protocol.registerClientbound(ClientboundPackets1_9_3.OPEN_SCREEN, new PacketHandlers() { @Override public void register() { map(Types.UNSIGNED_BYTE); // 0 - Window ID map(Types.STRING); // 1 - Window Type map(Types.COMPONENT); // 2 - Title map(Types.UNSIGNED_BYTE); // 3 - Slots handler(wrapper -> { int entityId = -1; // Passthrough Entity ID if (wrapper.get(Types.STRING, 0).equals("EntityHorse")) { entityId = wrapper.passthrough(Types.INT); } // Rewrite window title protocol.getComponentRewriter().processText(wrapper.user(), wrapper.get(Types.COMPONENT, 0)); // Track Inventory String inventory = wrapper.get(Types.STRING, 0); WindowTracker windowTracker = wrapper.user().get(WindowTracker.class); windowTracker.setInventory(inventory); windowTracker.setEntityId(entityId); // Change llama slotcount to the donkey one if (isLlama(wrapper.user())) { wrapper.set(Types.UNSIGNED_BYTE, 1, (short) 17); } }); } }); protocol.registerClientbound(ClientboundPackets1_9_3.CONTAINER_CLOSE, new PacketHandlers() { @Override public void register() { // Inventory tracking handler(wrapper -> { WindowTracker windowTracker = wrapper.user().get(WindowTracker.class); windowTracker.setInventory(null); windowTracker.setEntityId(-1); }); } }); protocol.registerServerbound(ServerboundPackets1_9_3.CONTAINER_CLOSE, new PacketHandlers() { @Override public void register() { // Inventory tracking handler(wrapper -> { WindowTracker windowTracker = wrapper.user().get(WindowTracker.class); windowTracker.setInventory(null); windowTracker.setEntityId(-1); }); } }); protocol.getEntityRewriter().filter().handler((event, data) -> { if (data.dataType().type().equals(Types.ITEM1_8)) // Is Item data.setValue(handleItemToClient(event.user(), (Item) data.getValue())); }); } @Override protected void registerRewrites() { // Handle spawner block entity (map to itself with custom handler) MappedLegacyBlockItem data = itemReplacements.computeIfAbsent(IdAndData.toRawData(52), s -> new MappedLegacyBlockItem(52)); data.setBlockEntityHandler((b, tag) -> EntityMappings1_11.toClientSpawner(tag, true)); enchantmentRewriter = new LegacyEnchantmentRewriter(nbtTagName()); enchantmentRewriter.registerEnchantment(71, "§cCurse of Vanishing"); enchantmentRewriter.registerEnchantment(10, "§cCurse of Binding"); enchantmentRewriter.setHideLevelForEnchants(71, 10); // Curses do not display their level } @Override public Item handleItemToClient(UserConnection connection, Item item) { if (item == null) return null; super.handleItemToClient(connection, item); CompoundTag tag = item.tag(); if (tag == null) return item; // Rewrite spawn eggs (id checks are done in the method itself) EntityMappings1_11.toClientItem(item, true); enchantmentRewriter.handleToClient(item); return item; } @Override public Item handleItemToServer(UserConnection connection, Item item) { if (item == null) return null; item = super.handleItemToServer(connection, item); CompoundTag tag = item.tag(); if (tag == null) return item; // Rewrite spawn eggs (id checks are done in the method itself) EntityMappings1_11.toServerItem(item, true); enchantmentRewriter.handleToServer(item); return item; } private boolean isLlama(UserConnection user) { WindowTracker tracker = user.get(WindowTracker.class); if (tracker.getInventory() != null && tracker.getInventory().equals("EntityHorse")) { EntityTracker entTracker = user.getEntityTracker(Protocol1_11To1_10.class); StoredEntityData entityData = entTracker.entityData(tracker.getEntityId()); return entityData != null && entityData.type().is(EntityTypes1_11.EntityType.LLAMA); } return false; } private Optional getChestedHorse(UserConnection user) { WindowTracker tracker = user.get(WindowTracker.class); if (tracker.getInventory() != null && tracker.getInventory().equals("EntityHorse")) { EntityTracker entTracker = user.getEntityTracker(Protocol1_11To1_10.class); StoredEntityData entityData = entTracker.entityData(tracker.getEntityId()); if (entityData != null) return Optional.of(entityData.get(ChestedHorseStorage.class)); } return Optional.empty(); } private int getNewSlotId(ChestedHorseStorage storage, int slotId) { int totalSlots = !storage.isChested() ? 38 : 53; int strength = storage.isChested() ? storage.getLiamaStrength() : 0; int startNonExistingFormula = 2 + 3 * strength; int offsetForm = 15 - (3 * strength); if (slotId >= startNonExistingFormula && totalSlots > (slotId + offsetForm)) return offsetForm + slotId; if (slotId == 1) return 0; return slotId; } private int getOldSlotId(ChestedHorseStorage storage, int slotId) { int strength = storage.isChested() ? storage.getLiamaStrength() : 0; int startNonExistingFormula = 2 + 3 * strength; int endNonExistingFormula = 2 + 3 * (storage.isChested() ? 5 : 0); int offsetForm = endNonExistingFormula - startNonExistingFormula; if (slotId == 1 || slotId >= startNonExistingFormula && slotId < endNonExistingFormula) return 0; if (slotId >= endNonExistingFormula) return slotId - offsetForm; if (slotId == 0) return 1; return slotId; } private Item getNewItem(ChestedHorseStorage storage, int slotId, Item current) { int strength = storage.isChested() ? storage.getLiamaStrength() : 0; int startNonExistingFormula = 2 + 3 * strength; int endNonExistingFormula = 2 + 3 * (storage.isChested() ? 5 : 0); if (slotId >= startNonExistingFormula && slotId < endNonExistingFormula) return new DataItem(166, (byte) 1, (short) 0, getNamedTag("§4SLOT DISABLED")); if (slotId == 1) return null; return current; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_11to1_10/rewriter/EntityPacketRewriter1_11.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_11to1_10.rewriter; import com.viaversion.viabackwards.api.entities.storage.WrappedEntityData; import com.viaversion.viabackwards.api.rewriters.LegacyEntityRewriter; import com.viaversion.viabackwards.protocol.v1_11to1_10.Protocol1_11To1_10; import com.viaversion.viabackwards.protocol.v1_11to1_10.data.SplashPotionMappings1_10; import com.viaversion.viabackwards.protocol.v1_11to1_10.storage.ChestedHorseStorage; import com.viaversion.viaversion.api.data.entity.StoredEntityData; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_11; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_9; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; import java.util.List; public class EntityPacketRewriter1_11 extends LegacyEntityRewriter { public EntityPacketRewriter1_11(Protocol1_11To1_10 protocol) { super(protocol); } @Override protected void registerPackets() { protocol.registerClientbound(ClientboundPackets1_9_3.LEVEL_EVENT, new PacketHandlers() { @Override public void register() { map(Types.INT); map(Types.BLOCK_POSITION1_8); map(Types.INT); handler(wrapper -> { int type = wrapper.get(Types.INT, 0); if (type == 2002 || type == 2007) { // 2007 potion id doesn't exist in 1.10 if (type == 2007) { wrapper.set(Types.INT, 0, 2002); } int mappedData = SplashPotionMappings1_10.getOldData(wrapper.get(Types.INT, 1)); if (mappedData != -1) { wrapper.set(Types.INT, 1, mappedData); } } }); } }); protocol.registerClientbound(ClientboundPackets1_9_3.ADD_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.UUID); // 1 - UUID map(Types.BYTE); // 2 - Type map(Types.DOUBLE); // 3 - x map(Types.DOUBLE); // 4 - y map(Types.DOUBLE); // 5 - z map(Types.BYTE); // 6 - Pitch map(Types.BYTE); // 7 - Yaw map(Types.INT); // 8 - data // Track Entity handler(getObjectTrackerHandler()); handler(getObjectRewriter(EntityTypes1_11.ObjectType::findById)); handler(protocol.getItemRewriter().getFallingBlockHandler()); } }); registerTracker(ClientboundPackets1_9_3.ADD_EXPERIENCE_ORB, EntityTypes1_11.EntityType.EXPERIENCE_ORB); registerTracker(ClientboundPackets1_9_3.ADD_GLOBAL_ENTITY, EntityTypes1_11.EntityType.LIGHTNING_BOLT); protocol.registerClientbound(ClientboundPackets1_9_3.ADD_MOB, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.UUID); // 1 - UUID map(Types.VAR_INT, Types.UNSIGNED_BYTE); // 2 - Entity Type map(Types.DOUBLE); // 3 - X map(Types.DOUBLE); // 4 - Y map(Types.DOUBLE); // 5 - Z map(Types.BYTE); // 6 - Yaw map(Types.BYTE); // 7 - Pitch map(Types.BYTE); // 8 - Head Pitch map(Types.SHORT); // 9 - Velocity X map(Types.SHORT); // 10 - Velocity Y map(Types.SHORT); // 11 - Velocity Z map(Types.ENTITY_DATA_LIST1_9); // 12 - Entity data // Track entity handler(getTrackerHandler(Types.UNSIGNED_BYTE, 0)); // Rewrite entity type / data handler(getMobSpawnRewriter(Types.ENTITY_DATA_LIST1_9)); // Sub 1.11 clients will error if the list is empty handler(wrapper -> { List entityDataList = wrapper.get(Types.ENTITY_DATA_LIST1_9, 0); if (entityDataList.isEmpty()) { entityDataList.add(new EntityData(0, EntityDataTypes1_9.BYTE, (byte) 0)); } }); } }); registerTracker(ClientboundPackets1_9_3.ADD_PAINTING, EntityTypes1_11.EntityType.PAINTING); registerJoinGame(ClientboundPackets1_9_3.LOGIN, EntityTypes1_11.EntityType.PLAYER); registerRespawn(ClientboundPackets1_9_3.RESPAWN); protocol.registerClientbound(ClientboundPackets1_9_3.ADD_PLAYER, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.UUID); // 1 - Player UUID map(Types.DOUBLE); // 2 - X map(Types.DOUBLE); // 3 - Y map(Types.DOUBLE); // 4 - Z map(Types.BYTE); // 5 - Yaw map(Types.BYTE); // 6 - Pitch map(Types.ENTITY_DATA_LIST1_9); // 7 - Entity data list handler(getTrackerAndDataHandler(Types.ENTITY_DATA_LIST1_9, EntityTypes1_11.EntityType.PLAYER)); handler(wrapper -> { // Sub 1.11 clients will cry if the list is empty List entityDataList = wrapper.get(Types.ENTITY_DATA_LIST1_9, 0); if (entityDataList.isEmpty()) { entityDataList.add(new EntityData(0, EntityDataTypes1_9.BYTE, (byte) 0)); } }); } }); registerRemoveEntities(ClientboundPackets1_9_3.REMOVE_ENTITIES); registerSetEntityData(ClientboundPackets1_9_3.SET_ENTITY_DATA, Types.ENTITY_DATA_LIST1_9); protocol.registerClientbound(ClientboundPackets1_9_3.ENTITY_EVENT, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Entity ID map(Types.BYTE); // 1 - Entity Status handler(wrapper -> { final int entityId = wrapper.get(Types.INT, 0); if (entityId != tracker(wrapper.user()).clientEntityId()) { // Entity events are sent for all players, but we only want to apply this for the self player return; } final byte entityStatus = wrapper.get(Types.BYTE, 0); if (entityStatus == 35) { // TODO spawn particles? wrapper.clearPacket(); wrapper.setPacketType(ClientboundPackets1_9_3.GAME_EVENT); wrapper.write(Types.UNSIGNED_BYTE, (short) 10); // Play Elder Guardian animation wrapper.write(Types.FLOAT, 0F); } }); } }); } @Override protected void registerRewrites() { // Guardian mapEntityTypeWithData(EntityTypes1_11.EntityType.ELDER_GUARDIAN, EntityTypes1_11.EntityType.GUARDIAN); // Skeletons mapEntityTypeWithData(EntityTypes1_11.EntityType.WITHER_SKELETON, EntityTypes1_11.EntityType.SKELETON).spawnEntityData(storage -> storage.add(getSkeletonTypeData(1))); mapEntityTypeWithData(EntityTypes1_11.EntityType.STRAY, EntityTypes1_11.EntityType.SKELETON).plainName().spawnEntityData(storage -> storage.add(getSkeletonTypeData(2))); // Zombies mapEntityTypeWithData(EntityTypes1_11.EntityType.HUSK, EntityTypes1_11.EntityType.ZOMBIE).plainName().spawnEntityData(storage -> handleZombieType(storage, 6)); mapEntityTypeWithData(EntityTypes1_11.EntityType.ZOMBIE_VILLAGER, EntityTypes1_11.EntityType.ZOMBIE).spawnEntityData(storage -> handleZombieType(storage, 1)); // Horses mapEntityTypeWithData(EntityTypes1_11.EntityType.HORSE, EntityTypes1_11.EntityType.HORSE).spawnEntityData(storage -> storage.add(getHorseDataType(0))); // Nob able to ride the horse without having the EntityDataType sent. mapEntityTypeWithData(EntityTypes1_11.EntityType.DONKEY, EntityTypes1_11.EntityType.HORSE).spawnEntityData(storage -> storage.add(getHorseDataType(1))); mapEntityTypeWithData(EntityTypes1_11.EntityType.MULE, EntityTypes1_11.EntityType.HORSE).spawnEntityData(storage -> storage.add(getHorseDataType(2))); mapEntityTypeWithData(EntityTypes1_11.EntityType.SKELETON_HORSE, EntityTypes1_11.EntityType.HORSE).spawnEntityData(storage -> storage.add(getHorseDataType(4))); mapEntityTypeWithData(EntityTypes1_11.EntityType.ZOMBIE_HORSE, EntityTypes1_11.EntityType.HORSE).spawnEntityData(storage -> storage.add(getHorseDataType(3))); // New mobs mapEntityTypeWithData(EntityTypes1_11.EntityType.EVOKER_FANGS, EntityTypes1_11.EntityType.SHULKER); mapEntityTypeWithData(EntityTypes1_11.EntityType.EVOKER, EntityTypes1_11.EntityType.VILLAGER).plainName(); mapEntityTypeWithData(EntityTypes1_11.EntityType.VEX, EntityTypes1_11.EntityType.BAT).plainName(); mapEntityTypeWithData(EntityTypes1_11.EntityType.VINDICATOR, EntityTypes1_11.EntityType.VILLAGER).plainName().spawnEntityData(storage -> storage.add(new EntityData(13, EntityDataTypes1_9.VAR_INT, 4))); // Base Profession mapEntityTypeWithData(EntityTypes1_11.EntityType.LLAMA, EntityTypes1_11.EntityType.HORSE).plainName().spawnEntityData(storage -> storage.add(getHorseDataType(1))); mapEntityTypeWithData(EntityTypes1_11.EntityType.LLAMA_SPIT, EntityTypes1_11.EntityType.SNOWBALL); mapObjectType(EntityTypes1_11.ObjectType.LLAMA_SPIT, EntityTypes1_11.ObjectType.SNOWBALL, -1); // Replace with endertorchthingies mapObjectType(EntityTypes1_11.ObjectType.EVOKER_FANGS, EntityTypes1_11.ObjectType.FALLING_BLOCK, 198 | 1 << 12); // Handle ElderGuardian & target entity data filter().type(EntityTypes1_11.EntityType.GUARDIAN).index(12).handler((event, data) -> { boolean b = (boolean) data.getValue(); int bitmask = b ? 0x02 : 0; if (event.entityType() == EntityTypes1_11.EntityType.ELDER_GUARDIAN) { bitmask |= 0x04; } data.setTypeAndValue(EntityDataTypes1_9.BYTE, (byte) bitmask); }); // Handle skeleton swing filter().type(EntityTypes1_11.EntityType.ABSTRACT_SKELETON).index(12).toIndex(13); /* ZOMBIE CHANGES */ filter().type(EntityTypes1_11.EntityType.ZOMBIE).handler((event, data) -> { switch (data.id()) { case 13 -> event.cancel(); case 14 -> event.setIndex(15); case 15 -> event.setIndex(14); case 16 -> { // Profession event.setIndex(13); data.setValue(1 + (int) data.getValue()); } } }); // Handle Evocation Illager filter().type(EntityTypes1_11.EntityType.EVOKER).index(12).handler((event, data) -> { event.setIndex(13); data.setTypeAndValue(EntityDataTypes1_9.VAR_INT, ((Byte) data.getValue()).intValue()); // Change the profession for the states }); // Handle Vex (Remove this field completely since the position is not updated correctly when idling for bats. Sad ): filter().type(EntityTypes1_11.EntityType.VEX).index(12).handler((event, data) -> { data.setValue((byte) 0x00); }); // Handle VindicationIllager filter().type(EntityTypes1_11.EntityType.VINDICATOR).index(12).handler((event, data) -> { event.setIndex(13); data.setTypeAndValue(EntityDataTypes1_9.VAR_INT, ((Number) data.getValue()).intValue() == 1 ? 2 : 4); }); /* HORSES */ // Handle horse flags filter().type(EntityTypes1_11.EntityType.ABSTRACT_HORSE).index(13).handler((event, data) -> { StoredEntityData entityData = storedEntityData(event); byte b = (byte) data.getValue(); if (entityData.has(ChestedHorseStorage.class) && entityData.get(ChestedHorseStorage.class).isChested()) { b |= 0x08; // Chested data.setValue(b); } }); // Create chested horse storage filter().type(EntityTypes1_11.EntityType.CHESTED_HORSE).handler((event, data) -> { StoredEntityData entityData = storedEntityData(event); if (!entityData.has(ChestedHorseStorage.class)) { entityData.put(new ChestedHorseStorage()); } }); // Handle horse armor filter().type(EntityTypes1_11.EntityType.HORSE).index(16).toIndex(17); // Handle chested horse filter().type(EntityTypes1_11.EntityType.CHESTED_HORSE).index(15).handler((event, data) -> { StoredEntityData entityData = storedEntityData(event); ChestedHorseStorage storage = entityData.get(ChestedHorseStorage.class); boolean b = (boolean) data.getValue(); storage.setChested(b); event.cancel(); }); // Get rid of Liama entity data filter().type(EntityTypes1_11.EntityType.LLAMA).handler((event, data) -> { StoredEntityData entityData = storedEntityData(event); ChestedHorseStorage storage = entityData.get(ChestedHorseStorage.class); int index = event.index(); // Store them for later (: switch (index) { case 16 -> { storage.setLiamaStrength((int) data.getValue()); event.cancel(); } case 17 -> { storage.setLiamaCarpetColor((int) data.getValue()); event.cancel(); } case 18 -> { storage.setLiamaVariant((int) data.getValue()); event.cancel(); } } }); // Handle Horse (Correct owner) filter().type(EntityTypes1_11.EntityType.ABSTRACT_HORSE).index(14).toIndex(16); // Handle villager - Change non-existing profession filter().type(EntityTypes1_11.EntityType.VILLAGER).index(13).handler((event, data) -> { if ((int) data.getValue() == 5) { data.setValue(0); } }); // handle new Shulker color data filter().type(EntityTypes1_11.EntityType.SHULKER).cancel(15); } /* 0 - Skeleton 1 - Wither Skeleton 2 - Stray */ private EntityData getSkeletonTypeData(int type) { return new EntityData(12, EntityDataTypes1_9.VAR_INT, type); } /* 0 - Zombie 1-5 - Villager with profession 6 - Husk */ private EntityData getZombieTypeData(int type) { return new EntityData(13, EntityDataTypes1_9.VAR_INT, type); } private void handleZombieType(WrappedEntityData storage, int type) { EntityData meta = storage.get(13); if (meta == null) { storage.add(getZombieTypeData(type)); } } /* Horse 0 Donkey 1 Mule 2 Zombie horse 3 Skeleton horse 4 */ private EntityData getHorseDataType(int type) { return new EntityData(14, EntityDataTypes1_9.VAR_INT, type); } @Override public EntityType typeFromId(int typeId) { return EntityTypes1_11.EntityType.findById(typeId); } @Override public EntityType objectTypeFromId(int typeId, int data) { return EntityTypes1_11.ObjectType.getEntityType(typeId, data); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_11to1_10/rewriter/PlayerPacketRewriter1_11.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_11to1_10.rewriter; import com.viaversion.viabackwards.protocol.v1_11to1_10.Protocol1_11To1_10; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.protocol.remapper.ValueTransformer; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.libs.gson.JsonObject; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ServerboundPackets1_9_3; import com.viaversion.viaversion.util.ComponentUtil; public class PlayerPacketRewriter1_11 { private static final ValueTransformer TO_NEW_FLOAT = new ValueTransformer<>(Types.FLOAT) { @Override public Float transform(PacketWrapper wrapper, Short inputValue) { return inputValue / 16f; } }; public static void register(Protocol1_11To1_10 protocol) { protocol.registerClientbound(ClientboundPackets1_9_3.SET_TITLES, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Action handler(wrapper -> { int action = wrapper.get(Types.VAR_INT, 0); if (action == 2) { // Handle the new ActionBar JsonElement message = wrapper.read(Types.COMPONENT); wrapper.clearPacket(); wrapper.setPacketType(ClientboundPackets1_9_3.CHAT); // https://bugs.mojang.com/browse/MC-119145 String legacy = ComponentUtil.jsonToLegacy(message); message = new JsonObject(); message.getAsJsonObject().addProperty("text", legacy); wrapper.write(Types.COMPONENT, message); wrapper.write(Types.BYTE, (byte) 2); } else if (action > 2) { wrapper.set(Types.VAR_INT, 0, action - 1); // Move everything one position down } }); } }); protocol.registerClientbound(ClientboundPackets1_9_3.TAKE_ITEM_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Collected entity id map(Types.VAR_INT); // 1 - Collector entity id handler(wrapper -> wrapper.read(Types.VAR_INT)); // Ignore item pickup count } }); protocol.registerServerbound(ServerboundPackets1_9_3.USE_ITEM_ON, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_8); // 0 - Location map(Types.VAR_INT); // 1 - Face map(Types.VAR_INT); // 2 - Hand map(Types.UNSIGNED_BYTE, TO_NEW_FLOAT); map(Types.UNSIGNED_BYTE, TO_NEW_FLOAT); map(Types.UNSIGNED_BYTE, TO_NEW_FLOAT); } }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_11to1_10/storage/ChestedHorseStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_11to1_10.storage; public class ChestedHorseStorage { private boolean chested; private int liamaStrength; private int liamaCarpetColor = -1; private int liamaVariant; public boolean isChested() { return chested; } public void setChested(boolean chested) { this.chested = chested; } public int getLiamaStrength() { return liamaStrength; } public void setLiamaStrength(int liamaStrength) { this.liamaStrength = liamaStrength; } public int getLiamaCarpetColor() { return liamaCarpetColor; } public void setLiamaCarpetColor(int liamaCarpetColor) { this.liamaCarpetColor = liamaCarpetColor; } public int getLiamaVariant() { return liamaVariant; } public void setLiamaVariant(int liamaVariant) { this.liamaVariant = liamaVariant; } @Override public String toString() { return "ChestedHorseStorage{" + "chested=" + chested + ", liamaStrength=" + liamaStrength + ", liamaCarpetColor=" + liamaCarpetColor + ", liamaVariant=" + liamaVariant + '}'; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_11to1_10/storage/WindowTracker.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_11to1_10.storage; import com.viaversion.viaversion.api.connection.StorableObject; public class WindowTracker implements StorableObject { private String inventory; private int entityId = -1; public String getInventory() { return inventory; } public void setInventory(String inventory) { this.inventory = inventory; } public int getEntityId() { return entityId; } public void setEntityId(int entityId) { this.entityId = entityId; } @Override public String toString() { return "WindowTracker{" + "inventory='" + inventory + '\'' + ", entityId=" + entityId + '}'; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_12_1to1_12/Protocol1_12_1To1_12.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_12_1to1_12; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viaversion.protocols.v1_11_1to1_12.packet.ClientboundPackets1_12; import com.viaversion.viaversion.protocols.v1_11_1to1_12.packet.ServerboundPackets1_12; import com.viaversion.viaversion.protocols.v1_12to1_12_1.packet.ClientboundPackets1_12_1; import com.viaversion.viaversion.protocols.v1_12to1_12_1.packet.ServerboundPackets1_12_1; public class Protocol1_12_1To1_12 extends BackwardsProtocol { public Protocol1_12_1To1_12() { super(ClientboundPackets1_12_1.class, ClientboundPackets1_12.class, ServerboundPackets1_12_1.class, ServerboundPackets1_12.class); } @Override protected void registerPackets() { cancelClientbound(ClientboundPackets1_12_1.PLACE_GHOST_RECIPE); cancelServerbound(ServerboundPackets1_12.CRAFTING_RECIPE_PLACEMENT); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_12_2to1_12_1/Protocol1_12_2To1_12_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_12_2to1_12_1; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.protocol.v1_12_2to1_12_1.storage.KeepAliveTracker; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_12to1_12_1.packet.ClientboundPackets1_12_1; import com.viaversion.viaversion.protocols.v1_12to1_12_1.packet.ServerboundPackets1_12_1; public class Protocol1_12_2To1_12_1 extends BackwardsProtocol { public Protocol1_12_2To1_12_1() { super(ClientboundPackets1_12_1.class, ClientboundPackets1_12_1.class, ServerboundPackets1_12_1.class, ServerboundPackets1_12_1.class); } @Override protected void registerPackets() { registerClientbound(ClientboundPackets1_12_1.KEEP_ALIVE, new PacketHandlers() { @Override public void register() { handler(packetWrapper -> { Long keepAlive = packetWrapper.read(Types.LONG); packetWrapper.user().get(KeepAliveTracker.class).setKeepAlive(keepAlive); packetWrapper.write(Types.VAR_INT, keepAlive.hashCode()); }); } }); registerServerbound(ServerboundPackets1_12_1.KEEP_ALIVE, new PacketHandlers() { @Override public void register() { handler(packetWrapper -> { int keepAlive = packetWrapper.read(Types.VAR_INT); long realKeepAlive = packetWrapper.user().get(KeepAliveTracker.class).getKeepAlive(); if (keepAlive != Long.hashCode(realKeepAlive)) { packetWrapper.cancel(); // Wrong data, cancel packet return; } packetWrapper.write(Types.LONG, realKeepAlive); // Reset KeepAliveTracker (to prevent sending same valid value in a row causing a timeout) packetWrapper.user().get(KeepAliveTracker.class).setKeepAlive(Integer.MAX_VALUE); }); } }); } @Override public void init(UserConnection userConnection) { userConnection.put(new KeepAliveTracker()); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_12_2to1_12_1/storage/KeepAliveTracker.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_12_2to1_12_1.storage; import com.viaversion.viaversion.api.connection.StorableObject; public class KeepAliveTracker implements StorableObject { private long keepAlive = Integer.MAX_VALUE; public long getKeepAlive() { return keepAlive; } public void setKeepAlive(long keepAlive) { this.keepAlive = keepAlive; } @Override public String toString() { return "KeepAliveTracker{" + "keepAlive=" + keepAlive + '}'; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_12to1_11_1/Protocol1_12To1_11_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_12to1_11_1; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.rewriter.BlockItemPacketRewriter1_12; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.rewriter.ComponentRewriter1_12; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.rewriter.EntityPacketRewriter1_12; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.rewriter.SoundPacketRewriter1_12; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.storage.ShoulderTracker; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_12; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.v1_11_1to1_12.packet.ClientboundPackets1_12; import com.viaversion.viaversion.protocols.v1_11_1to1_12.packet.ServerboundPackets1_12; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ServerboundPackets1_9_3; import com.viaversion.viaversion.util.ComponentUtil; import com.viaversion.viaversion.util.SerializerVersion; public class Protocol1_12To1_11_1 extends BackwardsProtocol { private static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.12", "1.11"); private final EntityPacketRewriter1_12 entityRewriter = new EntityPacketRewriter1_12(this); private final BlockItemPacketRewriter1_12 itemRewriter = new BlockItemPacketRewriter1_12(this); private final ComponentRewriter1_12 componentRewriter = new ComponentRewriter1_12(this); public Protocol1_12To1_11_1() { super(ClientboundPackets1_12.class, ClientboundPackets1_9_3.class, ServerboundPackets1_12.class, ServerboundPackets1_9_3.class); } @Override protected void registerPackets() { super.registerPackets(); componentRewriter.registerComponentPacket(ClientboundPackets1_12.CHAT); new SoundPacketRewriter1_12(this).register(); registerClientbound(ClientboundPackets1_12.SET_TITLES, wrapper -> { int action = wrapper.passthrough(Types.VAR_INT); if (action >= 0 && action <= 2) { // Should be done globally in the component rewriter, but /shrug for now String component = wrapper.read(Types.COMPONENT).toString(); wrapper.write(Types.COMPONENT, ComponentUtil.convertJsonOrEmpty(component, SerializerVersion.V1_12, SerializerVersion.V1_9)); } }); cancelClientbound(ClientboundPackets1_12.UPDATE_ADVANCEMENTS); cancelClientbound(ClientboundPackets1_12.RECIPE); cancelClientbound(ClientboundPackets1_12.SELECT_ADVANCEMENTS_TAB); } @Override public void init(UserConnection user) { user.addEntityTracker(this.getClass(), new EntityTrackerBase(user, EntityTypes1_12.EntityType.PLAYER)); user.addClientWorld(this.getClass(), new ClientWorld()); user.put(new ShoulderTracker(user)); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_12 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_12 getItemRewriter() { return itemRewriter; } @Override public ComponentRewriter1_12 getComponentRewriter() { return componentRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_12to1_11_1/data/BlockColors1_11_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_12to1_11_1.data; public class BlockColors1_11_1 { private static final String[] COLORS = new String[16]; static { COLORS[0] = "White"; COLORS[1] = "Orange"; COLORS[2] = "Magenta"; COLORS[3] = "Light Blue"; COLORS[4] = "Yellow"; COLORS[5] = "Lime"; COLORS[6] = "Pink"; COLORS[7] = "Gray"; COLORS[8] = "Light Gray"; COLORS[9] = "Cyan"; COLORS[10] = "Purple"; COLORS[11] = "Blue"; COLORS[12] = "Brown"; COLORS[13] = "Green"; COLORS[14] = "Red"; COLORS[15] = "Black"; } public static String get(int key) { return key >= 0 && key < COLORS.length ? COLORS[key] : "Unknown color"; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_12to1_11_1/data/MapColorMappings1_11_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_12to1_11_1.data; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntOpenHashMap; public class MapColorMappings1_11_1 { private static final Int2IntMap MAPPING = new Int2IntOpenHashMap(64, 0.99F); static { MAPPING.defaultReturnValue(-1); MAPPING.put(144, 59); // (148, 124, 114) -> (148, 124, 114) MAPPING.put(145, 56); // (180, 153, 139) -> (180, 153, 139) MAPPING.put(146, 56); // (209, 177, 161) -> (209, 177, 161) MAPPING.put(147, 45); // (111, 94, 85) -> (111, 94, 85) MAPPING.put(148, 63); // (112, 58, 25) -> (112, 58, 25) MAPPING.put(149, 60); // (137, 71, 31) -> (137, 71, 31) MAPPING.put(150, 60); // (159, 82, 36) -> (159, 82, 36) MAPPING.put(151, 136); // (84, 43, 19) -> (84, 43, 19) MAPPING.put(152, 83); // (105, 61, 76) -> (105, 61, 76) MAPPING.put(153, 83); // (129, 75, 93) -> (129, 75, 93) MAPPING.put(154, 80); // (149, 87, 108) -> (149, 87, 108) MAPPING.put(155, 115); // (79, 46, 57) -> (79, 46, 57) MAPPING.put(156, 39); // (79, 76, 97) -> (79, 76, 97) MAPPING.put(157, 39); // (97, 93, 119) -> (97, 93, 119) MAPPING.put(158, 36); // (112, 108, 138) -> (112, 108, 138) MAPPING.put(159, 47); // (59, 57, 73) -> (59, 57, 73) MAPPING.put(160, 60); // (131, 94, 25) -> (131, 94, 25) MAPPING.put(161, 61); // (160, 115, 31) -> (160, 115, 31) MAPPING.put(162, 62); // (186, 133, 36) -> (186, 133, 36) MAPPING.put(163, 137); // (98, 70, 19) -> (98, 70, 19) MAPPING.put(164, 108); // (73, 83, 37) -> (73, 83, 37) MAPPING.put(165, 108); // (89, 101, 46) -> (89, 101, 46) MAPPING.put(166, 109); // (103, 117, 53) -> (103, 117, 53) MAPPING.put(167, 111); // (55, 62, 28) -> (55, 62, 28) MAPPING.put(168, 112); // (113, 54, 55) -> (113, 54, 55) MAPPING.put(169, 113); // (138, 66, 67) -> (138, 66, 67) MAPPING.put(170, 114); // (160, 77, 78) -> (160, 77, 78) MAPPING.put(171, 115); // (85, 41, 41) -> (85, 41, 41) MAPPING.put(172, 118); // (40, 29, 25) -> (40, 29, 25) MAPPING.put(173, 107); // (49, 35, 30) -> (49, 35, 30) MAPPING.put(174, 107); // (57, 41, 35) -> (57, 41, 35) MAPPING.put(175, 118); // (30, 22, 19) -> (30, 22, 19) MAPPING.put(176, 91); // (95, 76, 69) -> (95, 76, 69) MAPPING.put(177, 45); // (116, 92, 85) -> (116, 92, 85) MAPPING.put(178, 46); // (135, 107, 98) -> (135, 107, 98) MAPPING.put(179, 47); // (71, 57, 52) -> (71, 57, 52) MAPPING.put(180, 85); // (61, 65, 65) -> (61, 65, 65) MAPPING.put(181, 44); // (75, 79, 79) -> (75, 79, 79) MAPPING.put(182, 27); // (87, 92, 92) -> (87, 92, 92) MAPPING.put(183, 84); // (46, 49, 49) -> (46, 49, 49) MAPPING.put(184, 83); // (86, 52, 62) -> (86, 52, 62) MAPPING.put(185, 83); // (105, 63, 76) -> (105, 63, 76) MAPPING.put(186, 83); // (122, 73, 88) -> (122, 73, 88) MAPPING.put(187, 84); // (65, 39, 47) -> (65, 39, 47) MAPPING.put(188, 84); // (54, 44, 65) -> (54, 44, 65) MAPPING.put(189, 71); // (66, 53, 79) -> (66, 53, 79) MAPPING.put(190, 71); // (76, 62, 92) -> (76, 62, 92) MAPPING.put(191, 87); // (40, 33, 49) -> (40, 33, 49) MAPPING.put(192, 107); // (54, 35, 25) -> (54, 35, 25) MAPPING.put(193, 139); // (66, 43, 30) -> (66, 43, 30) MAPPING.put(194, 43); // (76, 50, 35) -> (76, 50, 35) MAPPING.put(195, 107); // (40, 26, 19) -> (40, 26, 19) MAPPING.put(196, 111); // (54, 58, 30) -> (54, 58, 30) MAPPING.put(197, 111); // (66, 71, 36) -> (66, 71, 36) MAPPING.put(198, 111); // (76, 82, 42) -> (76, 82, 42) MAPPING.put(199, 107); // (40, 43, 22) -> (40, 43, 22) MAPPING.put(200, 112); // (100, 42, 32) -> (100, 42, 32) MAPPING.put(201, 113); // (123, 52, 40) -> (123, 52, 40) MAPPING.put(202, 113); // (142, 60, 46) -> (142, 60, 46) MAPPING.put(203, 115); // (75, 32, 24) -> (75, 32, 24) MAPPING.put(204, 116); // (26, 16, 11) -> (26, 16, 11) MAPPING.put(205, 117); // (32, 19, 14) -> (32, 19, 14) MAPPING.put(206, 107); // (37, 22, 16) -> (37, 22, 16) MAPPING.put(207, 119); // (20, 12, 8) -> (20, 12, 8) } public static int getNearestOldColor(int color) { return MAPPING.getOrDefault(color, color); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_12to1_11_1/rewriter/BlockItemPacketRewriter1_12.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_12to1_11_1.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntArrayTag; import com.viaversion.nbt.tag.LongArrayTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.rewriters.LegacyBlockItemRewriter; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.Protocol1_12To1_11_1; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.data.MapColorMappings1_11_1; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_9_3; import com.viaversion.viaversion.protocols.v1_11_1to1_12.packet.ClientboundPackets1_12; import com.viaversion.viaversion.protocols.v1_11_1to1_12.packet.ServerboundPackets1_12; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ServerboundPackets1_9_3; import com.viaversion.viaversion.util.ComponentUtil; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.SerializerVersion; import java.util.Iterator; import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; public class BlockItemPacketRewriter1_12 extends LegacyBlockItemRewriter { public BlockItemPacketRewriter1_12(Protocol1_12To1_11_1 protocol) { super(protocol, "1.12"); } @Override protected void registerPackets() { registerBlockChange(ClientboundPackets1_12.BLOCK_UPDATE); registerMultiBlockChange(ClientboundPackets1_12.CHUNK_BLOCKS_UPDATE); protocol.registerClientbound(ClientboundPackets1_12.MAP_ITEM_DATA, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.BYTE); map(Types.BOOLEAN); handler(wrapper -> { int count = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < count * 3; i++) { wrapper.passthrough(Types.BYTE); } }); handler(wrapper -> { short columns = wrapper.passthrough(Types.UNSIGNED_BYTE); if (columns <= 0) return; wrapper.passthrough(Types.UNSIGNED_BYTE); // Rows wrapper.passthrough(Types.UNSIGNED_BYTE); // X wrapper.passthrough(Types.UNSIGNED_BYTE); // Z byte[] data = wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); for (int i = 0; i < data.length; i++) { short color = (short) (data[i] & 0xFF); if (color > 143) { color = (short) MapColorMappings1_11_1.getNearestOldColor(color); data[i] = (byte) color; } } wrapper.write(Types.BYTE_ARRAY_PRIMITIVE, data); }); } }); registerSetSlot(ClientboundPackets1_12.CONTAINER_SET_SLOT); registerSetContent(ClientboundPackets1_12.CONTAINER_SET_CONTENT); registerSetEquippedItem(ClientboundPackets1_12.SET_EQUIPPED_ITEM); registerCustomPayloadTradeList(ClientboundPackets1_12.CUSTOM_PAYLOAD); protocol.registerServerbound(ServerboundPackets1_9_3.CONTAINER_CLICK, new PacketHandlers() { @Override public void register() { map(Types.BYTE); // 0 - Window ID map(Types.SHORT); // 1 - Slot map(Types.BYTE); // 2 - Button map(Types.SHORT); // 3 - Action number map(Types.VAR_INT); // 4 - Mode map(Types.ITEM1_8); // 5 - Clicked Item handler(wrapper -> { if (wrapper.get(Types.VAR_INT, 0) == 1) { // Shift click // https://github.com/ViaVersion/ViaVersion/pull/754 // Previously clients grab the item from the clicked slot *before* it has // been moved however now they grab the slot item *after* it has been moved // and send that in the packet. wrapper.set(Types.ITEM1_8, 0, null); // Set null item (probably will work) // Apologize (may happen in some cases, maybe if inventory is full?) PacketWrapper confirm = wrapper.create(ServerboundPackets1_12.CONTAINER_ACK); confirm.write(Types.BYTE, wrapper.get(Types.BYTE, 0)); confirm.write(Types.SHORT, wrapper.get(Types.SHORT, 1)); confirm.write(Types.BOOLEAN, true); // Success - not used wrapper.sendToServer(Protocol1_12To1_11_1.class); wrapper.cancel(); confirm.sendToServer(Protocol1_12To1_11_1.class); return; } Item item = wrapper.get(Types.ITEM1_8, 0); handleItemToServer(wrapper.user(), item); }); } }); registerSetCreativeModeSlot(ServerboundPackets1_9_3.SET_CREATIVE_MODE_SLOT); protocol.registerClientbound(ClientboundPackets1_12.LEVEL_CHUNK, wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_12To1_11_1.class); ChunkType1_9_3 type = ChunkType1_9_3.forEnvironment(clientWorld.getEnvironment()); // Use the 1.9.4 Chunk type since nothing changed. Chunk chunk = wrapper.passthrough(type); handleChunk(chunk); for (final CompoundTag tag : chunk.getBlockEntities()) { final String id = tag.getString("id"); if (id == null) { continue; } if (Key.stripMinecraftNamespace(id).equals("sign")) { handleSignText(tag); } } }); protocol.registerClientbound(ClientboundPackets1_12.BLOCK_ENTITY_DATA, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_8); // 0 - Position map(Types.UNSIGNED_BYTE); // 1 - Action map(Types.NAMED_COMPOUND_TAG); // 2 - NBT handler(wrapper -> { final short type = wrapper.get(Types.UNSIGNED_BYTE, 0); if (type == 9) { final CompoundTag tag = wrapper.get(Types.NAMED_COMPOUND_TAG, 0); handleSignText(tag); } else if (type == 11) { // Remove bed color wrapper.cancel(); } }); } }); protocol.getEntityRewriter().filter().handler((event, data) -> { if (data.dataType().type().equals(Types.ITEM1_8)) // Is Item data.setValue(handleItemToClient(event.user(), (Item) data.getValue())); }); protocol.registerServerbound(ServerboundPackets1_9_3.CLIENT_COMMAND, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Action ID handler(wrapper -> { // Open Inventory if (wrapper.get(Types.VAR_INT, 0) == 2) { wrapper.cancel(); } }); } }); } private void handleSignText(final CompoundTag tag) { // Push signs through component conversion, fixes https://github.com/ViaVersion/ViaBackwards/issues/835 for (int i = 0; i < 4; i++) { final StringTag lineTag = tag.getStringTag("Text" + (i + 1)); if (lineTag == null) { continue; } lineTag.setValue(ComponentUtil.convertJsonOrEmpty(lineTag.getValue(), SerializerVersion.V1_12, SerializerVersion.V1_9).toString()); } } @Override public @Nullable Item handleItemToClient(UserConnection connection, Item item) { if (item == null) return null; super.handleItemToClient(connection, item); if (item.tag() != null) { CompoundTag backupTag = new CompoundTag(); if (handleNbtToClient(item.tag(), backupTag)) { item.tag().put("Via|LongArrayTags", backupTag); } } return item; } private boolean handleNbtToClient(CompoundTag compoundTag, CompoundTag backupTag) { // Long array tags were introduced in 1.12 - just remove them // Only save the removed tags instead of blindly copying the entire nbt again Iterator> iterator = compoundTag.iterator(); boolean hasLongArrayTag = false; while (iterator.hasNext()) { Map.Entry entry = iterator.next(); if (entry.getValue() instanceof CompoundTag tag) { CompoundTag nestedBackupTag = new CompoundTag(); backupTag.put(entry.getKey(), nestedBackupTag); hasLongArrayTag |= handleNbtToClient(tag, nestedBackupTag); } else if (entry.getValue() instanceof LongArrayTag tag) { backupTag.put(entry.getKey(), fromLongArrayTag(tag)); iterator.remove(); hasLongArrayTag = true; } } return hasLongArrayTag; } @Override public @Nullable Item handleItemToServer(UserConnection connection, Item item) { if (item == null) return null; item = super.handleItemToServer(connection, item); if (item.tag() != null) { if (item.tag().remove("Via|LongArrayTags") instanceof CompoundTag tag) { handleNbtToServer(item.tag(), tag); } } return item; } private void handleNbtToServer(CompoundTag compoundTag, CompoundTag backupTag) { // Restore the removed long array tags for (Map.Entry entry : backupTag) { if (entry.getValue() instanceof CompoundTag) { CompoundTag nestedTag = compoundTag.getCompoundTag(entry.getKey()); handleNbtToServer(nestedTag, (CompoundTag) entry.getValue()); } else { compoundTag.put(entry.getKey(), fromIntArrayTag((IntArrayTag) entry.getValue())); } } } private IntArrayTag fromLongArrayTag(LongArrayTag tag) { int[] intArray = new int[tag.length() * 2]; long[] longArray = tag.getValue(); int i = 0; for (long l : longArray) { intArray[i++] = (int) (l >> 32); intArray[i++] = (int) l; } return new IntArrayTag(intArray); } private LongArrayTag fromIntArrayTag(IntArrayTag tag) { long[] longArray = new long[tag.length() / 2]; int[] intArray = tag.getValue(); for (int i = 0, j = 0; i < intArray.length; i += 2, j++) { longArray[j] = (long) intArray[i] << 32 | ((long) intArray[i + 1] & 0xFFFFFFFFL); } return new LongArrayTag(longArray); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_12to1_11_1/rewriter/ComponentRewriter1_12.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_12to1_11_1.rewriter; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.Protocol1_12To1_11_1; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.libs.gson.JsonObject; import com.viaversion.viaversion.protocols.v1_11_1to1_12.packet.ClientboundPackets1_12; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; public class ComponentRewriter1_12 extends JsonNBTComponentRewriter { public ComponentRewriter1_12(Protocol1_12To1_11_1 protocol) { super(protocol, ComponentRewriterBase.ReadType.JSON); } @Override public void processText(UserConnection connection, JsonElement element) { super.processText(connection, element); if (element == null || !element.isJsonObject()) { return; } JsonObject object = element.getAsJsonObject(); JsonElement keybind = object.remove("keybind"); if (keybind == null) { return; } //TODO Add nicer text for the key, also use this component rewriter in more packets object.addProperty("text", keybind.getAsString()); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_12to1_11_1/rewriter/EntityPacketRewriter1_12.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_12to1_11_1.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.rewriters.LegacyEntityRewriter; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.Protocol1_12To1_11_1; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.storage.ParrotStorage; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.storage.ShoulderTracker; import com.viaversion.viaversion.api.data.entity.StoredEntityData; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_12; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_12; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.v1_11_1to1_12.packet.ClientboundPackets1_12; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; public class EntityPacketRewriter1_12 extends LegacyEntityRewriter { public EntityPacketRewriter1_12(Protocol1_12To1_11_1 protocol) { super(protocol); } @Override protected void registerPackets() { protocol.registerClientbound(ClientboundPackets1_12.ADD_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.UUID); // 1 - UUID map(Types.BYTE); // 2 - Type map(Types.DOUBLE); // 3 - x map(Types.DOUBLE); // 4 - y map(Types.DOUBLE); // 5 - z map(Types.BYTE); // 6 - Pitch map(Types.BYTE); // 7 - Yaw map(Types.INT); // 8 - data // Track Entity handler(getObjectTrackerHandler()); handler(getObjectRewriter(EntityTypes1_12.ObjectType::findById)); handler(protocol.getItemRewriter().getFallingBlockHandler()); } }); registerTracker(ClientboundPackets1_12.ADD_EXPERIENCE_ORB, EntityTypes1_12.EntityType.EXPERIENCE_ORB); registerTracker(ClientboundPackets1_12.ADD_GLOBAL_ENTITY, EntityTypes1_12.EntityType.LIGHTNING_BOLT); protocol.registerClientbound(ClientboundPackets1_12.ADD_MOB, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.UUID); // 1 - UUID map(Types.VAR_INT); // 2 - Entity Type map(Types.DOUBLE); // 3 - X map(Types.DOUBLE); // 4 - Y map(Types.DOUBLE); // 5 - Z map(Types.BYTE); // 6 - Yaw map(Types.BYTE); // 7 - Pitch map(Types.BYTE); // 8 - Head Pitch map(Types.SHORT); // 9 - Velocity X map(Types.SHORT); // 10 - Velocity Y map(Types.SHORT); // 11 - Velocity Z map(Types.ENTITY_DATA_LIST1_12, Types.ENTITY_DATA_LIST1_9); // 12 - Entity data // Track entity handler(getTrackerHandler()); // Rewrite entity type / data handler(getMobSpawnRewriter1_11(Types.ENTITY_DATA_LIST1_9)); } }); registerTracker(ClientboundPackets1_12.ADD_PAINTING, EntityTypes1_12.EntityType.PAINTING); protocol.registerClientbound(ClientboundPackets1_12.ADD_PLAYER, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.UUID); // 1 - Player UUID map(Types.DOUBLE); // 2 - X map(Types.DOUBLE); // 3 - Y map(Types.DOUBLE); // 4 - Z map(Types.BYTE); // 5 - Yaw map(Types.BYTE); // 6 - Pitch map(Types.ENTITY_DATA_LIST1_12, Types.ENTITY_DATA_LIST1_9); // 7 - Entity data list handler(getTrackerAndDataHandler(Types.ENTITY_DATA_LIST1_9, EntityTypes1_12.EntityType.PLAYER)); } }); protocol.registerClientbound(ClientboundPackets1_12.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Entity ID map(Types.UNSIGNED_BYTE); // 1 - Gamemode map(Types.INT); // 2 - Dimension handler(getDimensionHandler(1)); handler(getPlayerTrackerHandler()); handler(wrapper -> { ShoulderTracker tracker = wrapper.user().get(ShoulderTracker.class); tracker.setEntityId(wrapper.get(Types.INT, 0)); }); // Send fake inventory achievement handler(packetWrapper -> { PacketWrapper wrapper = PacketWrapper.create(ClientboundPackets1_9_3.AWARD_STATS, packetWrapper.user()); wrapper.write(Types.VAR_INT, 1); wrapper.write(Types.STRING, "achievement.openInventory"); wrapper.write(Types.VAR_INT, 1); wrapper.scheduleSend(Protocol1_12To1_11_1.class); }); } }); registerRespawn(ClientboundPackets1_12.RESPAWN); registerRemoveEntities(ClientboundPackets1_12.REMOVE_ENTITIES); registerSetEntityData(ClientboundPackets1_12.SET_ENTITY_DATA, Types.ENTITY_DATA_LIST1_12, Types.ENTITY_DATA_LIST1_9); protocol.registerClientbound(ClientboundPackets1_12.UPDATE_ATTRIBUTES, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.INT); handler(wrapper -> { int size = wrapper.get(Types.INT, 0); int newSize = size; for (int i = 0; i < size; i++) { String key = wrapper.read(Types.STRING); // Remove new attribute if (key.equals("generic.flyingSpeed")) { newSize--; wrapper.read(Types.DOUBLE); int modSize = wrapper.read(Types.VAR_INT); for (int j = 0; j < modSize; j++) { wrapper.read(Types.UUID); wrapper.read(Types.DOUBLE); wrapper.read(Types.BYTE); } } else { wrapper.write(Types.STRING, key); wrapper.passthrough(Types.DOUBLE); int modSize = wrapper.passthrough(Types.VAR_INT); for (int j = 0; j < modSize; j++) { wrapper.passthrough(Types.UUID); wrapper.passthrough(Types.DOUBLE); wrapper.passthrough(Types.BYTE); } } } if (newSize != size) { wrapper.set(Types.INT, 0, newSize); } }); } }); } @Override protected void registerRewrites() { mapEntityTypeWithData(EntityTypes1_12.EntityType.PARROT, EntityTypes1_12.EntityType.BAT).plainName().spawnEntityData(storage -> storage.add(new EntityData(12, EntityDataTypes1_12.BYTE, (byte) 0x00))); mapEntityTypeWithData(EntityTypes1_12.EntityType.ILLUSIONER, EntityTypes1_12.EntityType.EVOKER).plainName(); filter().handler((event, data) -> { if (data.dataType() == EntityDataTypes1_12.COMPONENT) { protocol.getComponentRewriter().processText(event.user(), (JsonElement) data.getValue()); } }); // Handle Illager filter().type(EntityTypes1_12.EntityType.EVOKER).removeIndex(12); filter().type(EntityTypes1_12.EntityType.ILLUSIONER).index(0).handler((event, data) -> { byte mask = (byte) data.getValue(); if ((mask & 0x20) == 0x20) { mask &= ~0x20; } data.setValue(mask); }); // Create Parrot storage filter().type(EntityTypes1_12.EntityType.PARROT).handler((event, data) -> { StoredEntityData entityData = storedEntityData(event); if (!entityData.has(ParrotStorage.class)) { entityData.put(new ParrotStorage()); } }); // Parrot remove animal entity data filter().type(EntityTypes1_12.EntityType.PARROT).cancel(12); // Is baby filter().type(EntityTypes1_12.EntityType.PARROT).index(13).handler((event, data) -> { StoredEntityData entityData = storedEntityData(event); ParrotStorage storage = entityData.get(ParrotStorage.class); boolean isSitting = (((byte) data.getValue()) & 0x01) == 0x01; boolean isTamed = (((byte) data.getValue()) & 0x04) == 0x04; if (!storage.isTamed() && isTamed) { // TODO do something to let the user know it's done } storage.setTamed(isTamed); if (isSitting) { event.setIndex(12); data.setValue((byte) 0x01); storage.setSitting(true); } else if (storage.isSitting()) { event.setIndex(12); data.setValue((byte) 0x00); storage.setSitting(false); } else { event.cancel(); } }); // Flags (Is sitting etc, might be useful in the future filter().type(EntityTypes1_12.EntityType.PARROT).cancel(14); // Owner filter().type(EntityTypes1_12.EntityType.PARROT).cancel(15); // Variant // Left shoulder entity data filter().type(EntityTypes1_12.EntityType.PLAYER).index(15).handler((event, data) -> { CompoundTag tag = (CompoundTag) data.getValue(); ShoulderTracker tracker = event.user().get(ShoulderTracker.class); if (tag.isEmpty() && tracker.getLeftShoulder() != null) { tracker.setLeftShoulder(null); tracker.update(); } else if (tag.getStringTag("id") != null && event.entityId() == tracker.getEntityId()) { String id = tag.getStringTag("id").getValue(); if (tracker.getLeftShoulder() == null || !tracker.getLeftShoulder().equals(id)) { tracker.setLeftShoulder(id); tracker.update(); } } event.cancel(); }); // Right shoulder entity data filter().type(EntityTypes1_12.EntityType.PLAYER).index(16).handler((event, data) -> { CompoundTag tag = (CompoundTag) event.data().getValue(); ShoulderTracker tracker = event.user().get(ShoulderTracker.class); if (tag.isEmpty() && tracker.getRightShoulder() != null) { tracker.setRightShoulder(null); tracker.update(); } else if (tag.getStringTag("id") != null && event.entityId() == tracker.getEntityId()) { String id = tag.getStringTag("id").getValue(); if (tracker.getRightShoulder() == null || !tracker.getRightShoulder().equals(id)) { tracker.setRightShoulder(id); tracker.update(); } } event.cancel(); }); } @Override public EntityType typeFromId(int typeId) { return EntityTypes1_12.EntityType.findById(typeId); } @Override public EntityType objectTypeFromId(int typeId, int data) { return EntityTypes1_12.ObjectType.getEntityType(typeId, data); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_12to1_11_1/rewriter/SoundPacketRewriter1_12.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_12to1_11_1.rewriter; import com.viaversion.viabackwards.api.rewriters.LegacySoundRewriter; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.Protocol1_12To1_11_1; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_11_1to1_12.packet.ClientboundPackets1_12; public class SoundPacketRewriter1_12 extends LegacySoundRewriter { public SoundPacketRewriter1_12(Protocol1_12To1_11_1 protocol) { super(protocol); } @Override protected void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_12.SOUND, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Sound name map(Types.VAR_INT); // 1 - Sound Category map(Types.INT); // 2 - x map(Types.INT); // 3 - y map(Types.INT); // 4 - z map(Types.FLOAT); // 5 - Volume map(Types.FLOAT); // 6 - Pitch handler(wrapper -> { int oldId = wrapper.get(Types.VAR_INT, 0); int newId = handleSounds(oldId); if (newId == -1) { wrapper.cancel(); return; } if (hasPitch(oldId)) { wrapper.set(Types.FLOAT, 1, handlePitch(oldId)); } wrapper.set(Types.VAR_INT, 0, newId); }); } }); } @Override protected void registerRewrites() { //TODO use the diff file to also have named sound remaps // (there were *A LOT* of refactored names) // Replacement sounds, suggestions are always welcome // Automatically generated from PAaaS added(26, 277, 1.4f); // block.end_portal.spawn -> entity.lightning.thunder added(27, -1); // block.end_portal_frame.fill added(72, 70); // block.note.bell -> block.note.harp added(73, 70); // block.note.chime -> block.note.harp added(74, 70); // block.note.flute -> block.note.harp added(75, 70); // block.note.guitar -> block.note.harp added(80, 70); // block.note.xylophone -> block.note.harp added(150, -1); // entity.boat.paddle_land added(151, -1); // entity.boat.paddle_water added(152, -1); // entity.bobber.retrieve added(195, -1); // entity.endereye.death added(274, 198, 0.8f); // entity.illusion_illager.ambient -> entity.evocation_illager.ambient added(275, 199, 0.8f); // entity.illusion_illager.cast_spell -> entity.evocation_illager.cast_spell added(276, 200, 0.8f); // entity.illusion_illager.death -> entity.evocation_illager.death added(277, 201, 0.8f); // entity.illusion_illager.hurt -> entity.evocation_illager.hurt added(278, 191, 0.9f); // entity.illusion_illager.mirror_move -> entity.endermen.teleport added(279, 203, 1.5f); // entity.illusion_illager.prepare_blindness -> entity.evocation_illager.prepare_summon added(280, 202, 0.8f); // entity.illusion_illager.prepare_mirror -> entity.evocation_illager.prepare_attack added(319, 133, 0.6f); // entity.parrot.ambient -> entity.bat.ambient added(320, 134, 1.7f); // entity.parrot.death -> entity.bat.death added(321, 219, 1.5f); // entity.parrot.eat -> entity.generic.eat added(322, 136, 0.7f); // entity.parrot.fly -> entity.bat.loop added(323, 135, 1.6f); // entity.parrot.hurt -> entity.bat.hurt added(324, 138, 1.5f); // entity.parrot.imitate.blaze -> entity.blaze.ambient added(325, 163, 1.5f); // entity.parrot.imitate.creeper -> entity.creeper.primed added(326, 170, 1.5f); // entity.parrot.imitate.elder_guardian -> entity.elder_guardian.ambient added(327, 178, 1.5f); // entity.parrot.imitate.enderdragon -> entity.enderdragon.ambient added(328, 186, 1.5f); // entity.parrot.imitate.enderman -> entity.endermen.ambient added(329, 192, 1.5f); // entity.parrot.imitate.endermite -> entity.endermite.ambient added(330, 198, 1.5f); // entity.parrot.imitate.evocation_illager -> entity.evocation_illager.ambient added(331, 226, 1.5f); // entity.parrot.imitate.ghast -> entity.ghast.ambient added(332, 259, 1.5f); // entity.parrot.imitate.husk -> entity.husk.ambient added(333, 198, 1.3f); // entity.parrot.imitate.illusion_illager -> entity.evocation_illager.ambient added(334, 291, 1.5f); // entity.parrot.imitate.magmacube -> entity.magmacube.squish added(335, 321, 1.5f); // entity.parrot.imitate.polar_bear -> entity.polar_bear.ambient added(336, 337, 1.5f); // entity.parrot.imitate.shulker -> entity.shulker.ambient added(337, 347, 1.5f); // entity.parrot.imitate.silverfish -> entity.silverfish.ambient added(338, 351, 1.5f); // entity.parrot.imitate.skeleton -> entity.skeleton.ambient added(339, 363, 1.5f); // entity.parrot.imitate.slime -> entity.slime.squish added(340, 376, 1.5f); // entity.parrot.imitate.spider -> entity.spider.ambient added(341, 385, 1.5f); // entity.parrot.imitate.stray -> entity.stray.ambient added(342, 390, 1.5f); // entity.parrot.imitate.vex -> entity.vex.ambient added(343, 400, 1.5f); // entity.parrot.imitate.vindication_illager -> entity.vindication_illager.ambient added(344, 403, 1.5f); // entity.parrot.imitate.witch -> entity.witch.ambient added(345, 408, 1.5f); // entity.parrot.imitate.wither -> entity.wither.ambient added(346, 414, 1.5f); // entity.parrot.imitate.wither_skeleton -> entity.wither_skeleton.ambient added(347, 418, 1.5f); // entity.parrot.imitate.wolf -> entity.wolf.ambient added(348, 427, 1.5f); // entity.parrot.imitate.zombie -> entity.zombie.ambient added(349, 438, 1.5f); // entity.parrot.imitate.zombie_pigman -> entity.zombie_pig.ambient added(350, 442, 1.5f); // entity.parrot.imitate.zombie_villager -> entity.zombie_villager.ambient added(351, 155); // entity.parrot.step -> entity.chicken.step added(368, 316); // entity.player.hurt_drown -> entity.player.hurt added(369, 316); // entity.player.hurt_on_fire -> entity.player.hurt // No replacement sounds for these, since it could be confusing, the toast doesn't show up added(544, -1); // ui.toast.in added(545, -1); // ui.toast.out added(546, 317, 1.5f); // ui.toast.challenge_complete } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_12to1_11_1/storage/ParrotStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_12to1_11_1.storage; public class ParrotStorage { private boolean tamed = true; private boolean sitting = true; public boolean isTamed() { return tamed; } public void setTamed(boolean tamed) { this.tamed = tamed; } public boolean isSitting() { return sitting; } public void setSitting(boolean sitting) { this.sitting = sitting; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_12to1_11_1/storage/ShoulderTracker.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_12to1_11_1.storage; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.protocol.v1_12to1_11_1.Protocol1_12To1_11_1; import com.viaversion.viaversion.api.connection.StoredObject; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_11_1to1_12.packet.ClientboundPackets1_12; import com.viaversion.viaversion.util.ComponentUtil; import com.viaversion.viaversion.util.Key; import java.util.Locale; public class ShoulderTracker extends StoredObject { private int entityId; private String leftShoulder; private String rightShoulder; public ShoulderTracker(UserConnection user) { super(user); } public void update() { PacketWrapper wrapper = PacketWrapper.create(ClientboundPackets1_12.CHAT, getUser()); try { wrapper.write(Types.COMPONENT, ComponentUtil.plainToJson(generateString())); } catch (final Exception e) { throw new RuntimeException(e); } wrapper.write(Types.BYTE, (byte) 2); try { wrapper.scheduleSend(Protocol1_12To1_11_1.class); } catch (Exception e) { ViaBackwards.getPlatform().getLogger().severe("Failed to send the shoulder indication"); e.printStackTrace(); } } // Does actionbar not support json colors? :( private String generateString() { StringBuilder builder = new StringBuilder(); // Empty spaces because the non-json formatting is weird builder.append(" "); if (leftShoulder == null) { builder.append("§4§lNothing"); } else { builder.append("§2§l").append(getName(leftShoulder)); } builder.append("§8§l <- §7§lShoulders§8§l -> "); if (rightShoulder == null) { builder.append("§4§lNothing"); } else { builder.append("§2§l").append(getName(rightShoulder)); } return builder.toString(); } private String getName(String current) { current = Key.stripMinecraftNamespace(current); String[] array = current.split("_"); StringBuilder builder = new StringBuilder(); for (String s : array) { builder.append(s.substring(0, 1).toUpperCase(Locale.ROOT)) .append(s.substring(1)) .append(" "); } return builder.toString(); } public int getEntityId() { return entityId; } public void setEntityId(int entityId) { this.entityId = entityId; } public String getLeftShoulder() { return leftShoulder; } public void setLeftShoulder(String leftShoulder) { this.leftShoulder = leftShoulder; } public String getRightShoulder() { return rightShoulder; } public void setRightShoulder(String rightShoulder) { this.rightShoulder = rightShoulder; } @Override public String toString() { return "ShoulderTracker{" + "entityId=" + entityId + ", leftShoulder='" + leftShoulder + '\'' + ", rightShoulder='" + rightShoulder + '\'' + '}'; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13_1to1_13/Protocol1_13_1To1_13.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13_1to1_13; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_13_1to1_13.rewriter.CommandRewriter1_13_1; import com.viaversion.viabackwards.protocol.v1_13_1to1_13.rewriter.EntityPacketRewriter1_13_1; import com.viaversion.viabackwards.protocol.v1_13_1to1_13.rewriter.ItemPacketRewriter1_13_1; import com.viaversion.viabackwards.protocol.v1_13_1to1_13.rewriter.WorldPacketRewriter1_13_1; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_13; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.protocol.remapper.ValueTransformer; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.libs.gson.JsonObject; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ServerboundPackets1_13; import com.viaversion.viaversion.protocols.v1_13to1_13_1.Protocol1_13To1_13_1; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; import com.viaversion.viaversion.util.ComponentUtil; public class Protocol1_13_1To1_13 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.13.2", "1.13", Protocol1_13To1_13_1.class); private final EntityPacketRewriter1_13_1 entityRewriter = new EntityPacketRewriter1_13_1(this); private final ItemPacketRewriter1_13_1 itemRewriter = new ItemPacketRewriter1_13_1(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final JsonNBTComponentRewriter translatableRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); private final TagRewriter tagRewriter = new TagRewriter<>(this); public Protocol1_13_1To1_13() { super(ClientboundPackets1_13.class, ClientboundPackets1_13.class, ServerboundPackets1_13.class, ServerboundPackets1_13.class); } @Override protected void registerPackets() { super.registerPackets(); WorldPacketRewriter1_13_1.register(this); new CommandRewriter1_13_1(this).registerDeclareCommands(ClientboundPackets1_13.COMMANDS); registerServerbound(ServerboundPackets1_13.COMMAND_SUGGESTION, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.STRING, new ValueTransformer<>(Types.STRING) { @Override public String transform(PacketWrapper wrapper, String inputValue) { // 1.13 starts sending slash at start, so we remove it for compatibility return !inputValue.startsWith("/") ? "/" + inputValue : inputValue; } }); } }); registerServerbound(ServerboundPackets1_13.EDIT_BOOK, wrapper -> { final Item item = itemRewriter.handleItemToServer(wrapper.user(), wrapper.read(Types.ITEM1_13)); wrapper.write(Types.ITEM1_13, item); wrapper.passthrough(Types.BOOLEAN); wrapper.write(Types.VAR_INT, 0); }); registerClientbound(ClientboundPackets1_13.OPEN_SCREEN, new PacketHandlers() { @Override public void register() { map(Types.UNSIGNED_BYTE); // Id map(Types.STRING); // Window Type handler(wrapper -> { JsonElement title = wrapper.passthrough(Types.COMPONENT); translatableRewriter.processText(wrapper.user(), title); if (ViaBackwards.getConfig().fix1_13FormattedInventoryTitle()) { if (title.isJsonObject() && title.getAsJsonObject().size() == 1 && title.getAsJsonObject().has("translate")) { // Hotfix simple translatable components from being converted to legacy text return; } // https://bugs.mojang.com/browse/MC-124543 JsonObject legacyComponent = new JsonObject(); legacyComponent.addProperty("text", ComponentUtil.jsonToLegacy(title)); wrapper.set(Types.COMPONENT, 0, legacyComponent); } }); } }); registerClientbound(ClientboundPackets1_13.COMMAND_SUGGESTIONS, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Transaction id map(Types.VAR_INT); // Start map(Types.VAR_INT); // Length map(Types.VAR_INT); // Count handler(wrapper -> { int start = wrapper.get(Types.VAR_INT, 1); wrapper.set(Types.VAR_INT, 1, start - 1); // Offset by +1 to take into account / at beginning // Passthrough suggestions int count = wrapper.get(Types.VAR_INT, 3); for (int i = 0; i < count; i++) { wrapper.passthrough(Types.STRING); wrapper.passthrough(Types.OPTIONAL_COMPONENT); // Tooltip } }); } }); replaceClientbound(ClientboundPackets1_13.BOSS_EVENT, new PacketHandlers() { @Override public void register() { map(Types.UUID); map(Types.VAR_INT); handler(wrapper -> { int action = wrapper.get(Types.VAR_INT, 0); if (action == 0 || action == 3) { translatableRewriter.processText(wrapper.user(), wrapper.passthrough(Types.COMPONENT)); if (action == 0) { wrapper.passthrough(Types.FLOAT); wrapper.passthrough(Types.VAR_INT); wrapper.passthrough(Types.VAR_INT); short flags = wrapper.read(Types.UNSIGNED_BYTE); if ((flags & 0x04) != 0) flags |= 0x02; wrapper.write(Types.UNSIGNED_BYTE, flags); } } }); } }); tagRewriter.register(ClientboundPackets1_13.UPDATE_TAGS, RegistryType.ITEM); } @Override public void init(UserConnection user) { user.addEntityTracker(getClass(), new EntityTrackerBase(user, EntityTypes1_13.EntityType.PLAYER)); user.addClientWorld(getClass(), new ClientWorld()); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_13_1 getEntityRewriter() { return entityRewriter; } @Override public ItemPacketRewriter1_13_1 getItemRewriter() { return itemRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } public JsonNBTComponentRewriter translatableRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13_1to1_13/rewriter/CommandRewriter1_13_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13_1to1_13.rewriter; import com.viaversion.viabackwards.protocol.v1_13_1to1_13.Protocol1_13_1To1_13; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.rewriter.CommandRewriter; import org.checkerframework.checker.nullness.qual.Nullable; public class CommandRewriter1_13_1 extends CommandRewriter { public CommandRewriter1_13_1(Protocol1_13_1To1_13 protocol) { super(protocol); this.parserHandlers.put("minecraft:dimension", wrapper -> wrapper.write(Types.VAR_INT, 0)); // Single word } @Override public @Nullable String handleArgumentType(String argumentType) { if (argumentType.equals("minecraft:column_pos")) { return "minecraft:vec2"; } else if (argumentType.equals("minecraft:dimension")) { return "brigadier:string"; } return super.handleArgumentType(argumentType); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13_1to1_13/rewriter/EntityPacketRewriter1_13_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13_1to1_13.rewriter; import com.viaversion.viabackwards.api.rewriters.LegacyEntityRewriter; import com.viaversion.viabackwards.protocol.v1_13_1to1_13.Protocol1_13_1To1_13; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_13; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_13; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import java.util.List; public class EntityPacketRewriter1_13_1 extends LegacyEntityRewriter { public EntityPacketRewriter1_13_1(Protocol1_13_1To1_13 protocol) { super(protocol); } @Override protected void registerPackets() { protocol.registerClientbound(ClientboundPackets1_13.ADD_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.UUID); // 1 - UUID map(Types.BYTE); // 2 - Type map(Types.DOUBLE); // 3 - X map(Types.DOUBLE); // 4 - Y map(Types.DOUBLE); // 5 - Z map(Types.BYTE); // 6 - Pitch map(Types.BYTE); // 7 - Yaw map(Types.INT); // 8 - Data handler(wrapper -> { int entityId = wrapper.get(Types.VAR_INT, 0); byte type = wrapper.get(Types.BYTE, 0); int data = wrapper.get(Types.INT, 0); EntityTypes1_13.EntityType entType = EntityTypes1_13.ObjectType.getEntityType(type, data); if (entType == null) { return; } // Rewrite falling block if (entType.is(EntityTypes1_13.EntityType.FALLING_BLOCK)) { wrapper.set(Types.INT, 0, protocol.getMappingData().getNewBlockStateId(data)); } // Track Entity tracker(wrapper.user()).addEntity(entityId, entType); }); } }); registerTracker(ClientboundPackets1_13.ADD_EXPERIENCE_ORB, EntityTypes1_13.EntityType.EXPERIENCE_ORB); registerTracker(ClientboundPackets1_13.ADD_GLOBAL_ENTITY, EntityTypes1_13.EntityType.LIGHTNING_BOLT); protocol.registerClientbound(ClientboundPackets1_13.ADD_MOB, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.UUID); // 1 - Entity UUID map(Types.VAR_INT); // 2 - Entity Type map(Types.DOUBLE); // 3 - X map(Types.DOUBLE); // 4 - Y map(Types.DOUBLE); // 5 - Z map(Types.BYTE); // 6 - Yaw map(Types.BYTE); // 7 - Pitch map(Types.BYTE); // 8 - Head Pitch map(Types.SHORT); // 9 - Velocity X map(Types.SHORT); // 10 - Velocity Y map(Types.SHORT); // 11 - Velocity Z map(Types1_13.ENTITY_DATA_LIST); // 12 - Entity data // Track Entity handler(getTrackerHandler()); // Rewrite Entity data handler(wrapper -> { List entityDataList = wrapper.get(Types1_13.ENTITY_DATA_LIST, 0); handleEntityData(wrapper.get(Types.VAR_INT, 0), entityDataList, wrapper.user()); }); } }); protocol.registerClientbound(ClientboundPackets1_13.ADD_PLAYER, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.UUID); // 1 - Player UUID map(Types.DOUBLE); // 2 - X map(Types.DOUBLE); // 3 - Y map(Types.DOUBLE); // 4 - Z map(Types.BYTE); // 5 - Yaw map(Types.BYTE); // 6 - Pitch map(Types1_13.ENTITY_DATA_LIST); // 7 - Entity data handler(getTrackerAndDataHandler(Types1_13.ENTITY_DATA_LIST, EntityTypes1_13.EntityType.PLAYER)); } }); registerTracker(ClientboundPackets1_13.ADD_PAINTING, EntityTypes1_13.EntityType.PAINTING); registerJoinGame(ClientboundPackets1_13.LOGIN, EntityTypes1_13.EntityType.PLAYER); registerRespawn(ClientboundPackets1_13.RESPAWN); registerSetEntityData(ClientboundPackets1_13.SET_ENTITY_DATA, Types1_13.ENTITY_DATA_LIST); } @Override protected void registerRewrites() { // Rewrite items & blocks filter().handler((event, data) -> { if (data.dataType() == Types1_13.ENTITY_DATA_TYPES.itemType) { data.setValue(protocol.getItemRewriter().handleItemToClient(event.user(), (Item) data.getValue())); } else if (data.dataType() == Types1_13.ENTITY_DATA_TYPES.optionalBlockStateType) { // Convert to new block id int value = (int) data.getValue(); data.setValue(protocol.getMappingData().getNewBlockStateId(value)); } else if (data.dataType() == Types1_13.ENTITY_DATA_TYPES.particleType) { protocol.getParticleRewriter().rewriteParticle(event.user(), (Particle) data.getValue()); } else if (data.dataType() == Types1_13.ENTITY_DATA_TYPES.optionalComponentType || data.dataType() == Types1_13.ENTITY_DATA_TYPES.componentType) { JsonElement element = data.value(); protocol.translatableRewriter().processText(event.user(), element); } }); // Remove shooter UUID filter().type(EntityTypes1_13.EntityType.ABSTRACT_ARROW).cancel(7); // Move colors to old position filter().type(EntityTypes1_13.EntityType.SPECTRAL_ARROW).index(8).toIndex(7); // Move loyalty level to old position filter().type(EntityTypes1_13.EntityType.TRIDENT).index(8).toIndex(7); // Rewrite Minecart blocks registerBlockStateHandler(EntityTypes1_13.EntityType.ABSTRACT_MINECART, 9); } @Override public EntityType typeFromId(int typeId) { return EntityTypes1_13.EntityType.findById(typeId); } @Override public EntityType objectTypeFromId(int typeId, int data) { return EntityTypes1_13.ObjectType.getEntityType(typeId, data); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13_1to1_13/rewriter/ItemPacketRewriter1_13_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13_1to1_13.rewriter; import com.viaversion.viabackwards.protocol.v1_13_1to1_13.Protocol1_13_1To1_13; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ServerboundPackets1_13; import com.viaversion.viaversion.rewriter.ItemRewriter; public class ItemPacketRewriter1_13_1 extends ItemRewriter { public ItemPacketRewriter1_13_1(Protocol1_13_1To1_13 protocol) { super(protocol, Types.ITEM1_13, Types.ITEM1_13_SHORT_ARRAY); } @Override public void registerPackets() { protocol.registerClientbound(ClientboundPackets1_13.CUSTOM_PAYLOAD, wrapper -> { String channel = wrapper.passthrough(Types.STRING); if (channel.equals("minecraft:trader_list")) { wrapper.passthrough(Types.INT); //Passthrough Window ID int size = wrapper.passthrough(Types.UNSIGNED_BYTE); for (int i = 0; i < size; i++) { passthroughClientboundItem(wrapper); // Input passthroughClientboundItem(wrapper); // Output boolean secondItem = wrapper.passthrough(Types.BOOLEAN); //Has second item if (secondItem) { passthroughClientboundItem(wrapper); // Second item } wrapper.passthrough(Types.BOOLEAN); //Trade disabled wrapper.passthrough(Types.INT); //Number of tools uses wrapper.passthrough(Types.INT); //Maximum number of trade uses } } }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13_1to1_13/rewriter/WorldPacketRewriter1_13_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13_1to1_13.rewriter; import com.viaversion.viabackwards.protocol.v1_13_1to1_13.Protocol1_13_1To1_13; import com.viaversion.viaversion.api.minecraft.BlockFace; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.rewriter.BlockRewriter; public class WorldPacketRewriter1_13_1 { public static void register(Protocol1_13_1To1_13 protocol) { BlockRewriter blockRewriter = BlockRewriter.legacy(protocol); protocol.registerClientbound(ClientboundPackets1_13.LEVEL_CHUNK, wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_13_1To1_13.class); Chunk chunk = wrapper.passthrough(ChunkType1_13.forEnvironment(clientWorld.getEnvironment())); blockRewriter.handleChunk(chunk); }); blockRewriter.registerBlockEvent(ClientboundPackets1_13.BLOCK_EVENT); blockRewriter.registerBlockUpdate(ClientboundPackets1_13.BLOCK_UPDATE); blockRewriter.registerChunkBlocksUpdate(ClientboundPackets1_13.CHUNK_BLOCKS_UPDATE); protocol.registerClientbound(ClientboundPackets1_13.LEVEL_EVENT, new PacketHandlers() { @Override public void register() { map(Types.INT); // Effect Id map(Types.BLOCK_POSITION1_8); // Location map(Types.INT); // Data handler(wrapper -> { int id = wrapper.get(Types.INT, 0); int data = wrapper.get(Types.INT, 1); if (id == 1010) { // Play record wrapper.set(Types.INT, 1, protocol.getMappingData().getNewItemId(data)); } else if (id == 2001) { // Block break + block break sound wrapper.set(Types.INT, 1, protocol.getMappingData().getNewBlockStateId(data)); } else if (id == 2000) { // Smoke switch (data) { // Down case 0, 1 -> { // Up BlockPosition pos = wrapper.get(Types.BLOCK_POSITION1_8, 0); BlockFace relative = data == 0 ? BlockFace.BOTTOM : BlockFace.TOP; wrapper.set(Types.BLOCK_POSITION1_8, 0, pos.getRelative(relative)); // Y Offset wrapper.set(Types.INT, 1, 4); // Self } case 2 -> wrapper.set(Types.INT, 1, 1); // North case 3 -> wrapper.set(Types.INT, 1, 7); // South case 4 -> wrapper.set(Types.INT, 1, 3); // West case 5 -> wrapper.set(Types.INT, 1, 5); // East } } }); } }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13_2to1_13_1/Protocol1_13_2To1_13_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13_2to1_13_1; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.protocol.v1_13_2to1_13_1.rewriter.EntityPacketRewriter1_13_2; import com.viaversion.viabackwards.protocol.v1_13_2to1_13_1.rewriter.ItemPacketRewriter1_13_2; import com.viaversion.viabackwards.protocol.v1_13_2to1_13_1.rewriter.WorldPacketRewriter1_13_2; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ServerboundPackets1_13; public class Protocol1_13_2To1_13_1 extends BackwardsProtocol { public Protocol1_13_2To1_13_1() { super(ClientboundPackets1_13.class, ClientboundPackets1_13.class, ServerboundPackets1_13.class, ServerboundPackets1_13.class); } @Override protected void registerPackets() { ItemPacketRewriter1_13_2.register(this); WorldPacketRewriter1_13_2.register(this); EntityPacketRewriter1_13_2.register(this); registerServerbound(ServerboundPackets1_13.EDIT_BOOK, new PacketHandlers() { @Override public void register() { map(Types.ITEM1_13, Types.ITEM1_13_2); } }); registerClientbound(ClientboundPackets1_13.UPDATE_ADVANCEMENTS, wrapper -> { wrapper.passthrough(Types.BOOLEAN); // Reset/clear int size = wrapper.passthrough(Types.VAR_INT); // Mapping size for (int i = 0; i < size; i++) { wrapper.passthrough(Types.STRING); // Identifier wrapper.passthrough(Types.OPTIONAL_STRING); // Parent // Display data if (wrapper.passthrough(Types.BOOLEAN)) { wrapper.passthrough(Types.COMPONENT); // Title wrapper.passthrough(Types.COMPONENT); // Description Item icon = wrapper.read(Types.ITEM1_13_2); wrapper.write(Types.ITEM1_13, icon); wrapper.passthrough(Types.VAR_INT); // Frame type int flags = wrapper.passthrough(Types.INT); // Flags if ((flags & 1) != 0) wrapper.passthrough(Types.STRING); // Background texture wrapper.passthrough(Types.FLOAT); // X wrapper.passthrough(Types.FLOAT); // Y } wrapper.passthrough(Types.STRING_ARRAY); // Criteria int arrayLength = wrapper.passthrough(Types.VAR_INT); for (int array = 0; array < arrayLength; array++) { wrapper.passthrough(Types.STRING_ARRAY); // String array } } }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13_2to1_13_1/rewriter/EntityPacketRewriter1_13_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13_2to1_13_1.rewriter; import com.viaversion.viabackwards.protocol.v1_13_2to1_13_1.Protocol1_13_2To1_13_1; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.entitydata.EntityDataType; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_13; import com.viaversion.viaversion.api.type.types.version.Types1_13_2; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; public class EntityPacketRewriter1_13_2 { public static void register(Protocol1_13_2To1_13_1 protocol) { protocol.registerClientbound(ClientboundPackets1_13.ADD_MOB, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.UUID); // 1 - Entity UUID map(Types.VAR_INT); // 2 - Entity Type map(Types.DOUBLE); // 3 - X map(Types.DOUBLE); // 4 - Y map(Types.DOUBLE); // 5 - Z map(Types.BYTE); // 6 - Yaw map(Types.BYTE); // 7 - Pitch map(Types.BYTE); // 8 - Head Pitch map(Types.SHORT); // 9 - Velocity X map(Types.SHORT); // 10 - Velocity Y map(Types.SHORT); // 11 - Velocity Z map(Types1_13_2.ENTITY_DATA_LIST, Types1_13.ENTITY_DATA_LIST); // 12 - Entity data handler(EntityPacketRewriter1_13_2::updateEntityData); } }); protocol.registerClientbound(ClientboundPackets1_13.ADD_PLAYER, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.UUID); // 1 - Player UUID map(Types.DOUBLE); // 2 - X map(Types.DOUBLE); // 3 - Y map(Types.DOUBLE); // 4 - Z map(Types.BYTE); // 5 - Yaw map(Types.BYTE); // 6 - Pitch map(Types1_13_2.ENTITY_DATA_LIST, Types1_13.ENTITY_DATA_LIST); // 7 - Entity data handler(EntityPacketRewriter1_13_2::updateEntityData); } }); protocol.registerClientbound(ClientboundPackets1_13.SET_ENTITY_DATA, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types1_13_2.ENTITY_DATA_LIST, Types1_13.ENTITY_DATA_LIST); // 1 - Entity data list handler(EntityPacketRewriter1_13_2::updateEntityData); } }); } private static void updateEntityData(final PacketWrapper wrapper) { for (final EntityData data : wrapper.get(Types1_13.ENTITY_DATA_LIST, 0)) { final EntityDataType dataType = Types1_13.ENTITY_DATA_TYPES.byId(data.dataType().typeId()); data.setDataType(dataType); if (dataType == Types1_13.ENTITY_DATA_TYPES.particleType) { final Particle particle = data.value(); if (particle.id() == 27) { final Item item = particle.getArgument(0).getValue(); particle.set(0, Types.ITEM1_13, item); } } } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13_2to1_13_1/rewriter/ItemPacketRewriter1_13_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13_2to1_13_1.rewriter; import com.viaversion.viabackwards.protocol.v1_13_2to1_13_1.Protocol1_13_2To1_13_1; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ServerboundPackets1_13; public class ItemPacketRewriter1_13_2 { public static void register(Protocol1_13_2To1_13_1 protocol) { protocol.registerClientbound(ClientboundPackets1_13.CONTAINER_SET_SLOT, new PacketHandlers() { @Override public void register() { map(Types.BYTE); // 0 - Window ID map(Types.SHORT); // 1 - Slot ID map(Types.ITEM1_13_2, Types.ITEM1_13); // 2 - Slot Value } }); protocol.registerClientbound(ClientboundPackets1_13.CONTAINER_SET_CONTENT, new PacketHandlers() { @Override public void register() { map(Types.UNSIGNED_BYTE); // 0 - Window ID map(Types.ITEM1_13_2_SHORT_ARRAY, Types.ITEM1_13_SHORT_ARRAY); // 1 - Window Values } }); protocol.registerClientbound(ClientboundPackets1_13.CUSTOM_PAYLOAD, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Channel handler(wrapper -> { String channel = wrapper.get(Types.STRING, 0); if (channel.equals("minecraft:trader_list") || channel.equals("trader_list")) { wrapper.passthrough(Types.INT); // Passthrough Window ID int size = wrapper.passthrough(Types.UNSIGNED_BYTE); for (int i = 0; i < size; i++) { // Input Item wrapper.write(Types.ITEM1_13, wrapper.read(Types.ITEM1_13_2)); // Output Item wrapper.write(Types.ITEM1_13, wrapper.read(Types.ITEM1_13_2)); boolean secondItem = wrapper.passthrough(Types.BOOLEAN); // Has second item if (secondItem) { wrapper.write(Types.ITEM1_13, wrapper.read(Types.ITEM1_13_2)); } wrapper.passthrough(Types.BOOLEAN); // Trade disabled wrapper.passthrough(Types.INT); // Number of tools uses wrapper.passthrough(Types.INT); // Maximum number of trade uses } } }); } }); protocol.registerClientbound(ClientboundPackets1_13.SET_EQUIPPED_ITEM, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.VAR_INT); // 1 - Slot ID map(Types.ITEM1_13_2, Types.ITEM1_13); // 2 - Item } }); protocol.registerClientbound(ClientboundPackets1_13.UPDATE_RECIPES, wrapper -> { int recipesNo = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < recipesNo; i++) { wrapper.passthrough(Types.STRING); // Id String type = wrapper.passthrough(Types.STRING); if (type.equals("crafting_shapeless")) { wrapper.passthrough(Types.STRING); // Group int ingredientsNo = wrapper.passthrough(Types.VAR_INT); for (int i1 = 0; i1 < ingredientsNo; i1++) { wrapper.write(Types.ITEM1_13_ARRAY, wrapper.read(Types.ITEM1_13_2_ARRAY)); } wrapper.write(Types.ITEM1_13, wrapper.read(Types.ITEM1_13_2)); } else if (type.equals("crafting_shaped")) { int ingredientsNo = wrapper.passthrough(Types.VAR_INT) * wrapper.passthrough(Types.VAR_INT); wrapper.passthrough(Types.STRING); // Group for (int i1 = 0; i1 < ingredientsNo; i1++) { wrapper.write(Types.ITEM1_13_ARRAY, wrapper.read(Types.ITEM1_13_2_ARRAY)); } wrapper.write(Types.ITEM1_13, wrapper.read(Types.ITEM1_13_2)); } else if (type.equals("smelting")) { wrapper.passthrough(Types.STRING); // Group // Ingredient start wrapper.write(Types.ITEM1_13_ARRAY, wrapper.read(Types.ITEM1_13_2_ARRAY)); // Ingredient end wrapper.write(Types.ITEM1_13, wrapper.read(Types.ITEM1_13_2)); wrapper.passthrough(Types.FLOAT); // EXP wrapper.passthrough(Types.VAR_INT); // Cooking time } } }); protocol.registerServerbound(ServerboundPackets1_13.CONTAINER_CLICK, new PacketHandlers() { @Override public void register() { map(Types.BYTE); // 0 - Window ID map(Types.SHORT); // 1 - Slot map(Types.BYTE); // 2 - Button map(Types.SHORT); // 3 - Action number map(Types.VAR_INT); // 4 - Mode map(Types.ITEM1_13, Types.ITEM1_13_2); // 5 - Clicked Item } }); protocol.registerServerbound(ServerboundPackets1_13.SET_CREATIVE_MODE_SLOT, new PacketHandlers() { @Override public void register() { map(Types.SHORT); // 0 - Slot map(Types.ITEM1_13, Types.ITEM1_13_2); // 1 - Clicked Item } }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13_2to1_13_1/rewriter/WorldPacketRewriter1_13_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13_2to1_13_1.rewriter; import com.viaversion.viabackwards.protocol.v1_13_2to1_13_1.Protocol1_13_2To1_13_1; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; public class WorldPacketRewriter1_13_2 { public static void register(Protocol1_13_2To1_13_1 protocol) { protocol.registerClientbound(ClientboundPackets1_13.LEVEL_PARTICLES, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Particle ID map(Types.BOOLEAN); // 1 - Long Distance map(Types.FLOAT); // 2 - X map(Types.FLOAT); // 3 - Y map(Types.FLOAT); // 4 - Z map(Types.FLOAT); // 5 - Offset X map(Types.FLOAT); // 6 - Offset Y map(Types.FLOAT); // 7 - Offset Z map(Types.FLOAT); // 8 - Particle Data map(Types.INT); // 9 - Particle Count handler(wrapper -> { int id = wrapper.get(Types.INT, 0); if (id == 27) { wrapper.write(Types.ITEM1_13, wrapper.read(Types.ITEM1_13_2)); } }); } }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/Protocol1_13To1_12_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.data.BackwardsMappingData1_13; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.data.PaintingNames1_13; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.provider.BackwardsBlockEntityProvider; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.rewriter.BlockItemPacketRewriter1_13; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.rewriter.EntityPacketRewriter1_13; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.rewriter.PlayerPacketRewriter1_13; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.rewriter.SoundPacketRewriter1_13; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage.BackwardsBlockStorage; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage.NoteBlockStorage; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage.PlayerPositionStorage1_13; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage.TabCompleteStorage; import com.viaversion.viabackwards.utils.BackwardsProtocolLogger; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_13; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.libs.gson.JsonObject; import com.viaversion.viaversion.libs.gson.JsonParser; import com.viaversion.viaversion.protocols.v1_12_2to1_13.Protocol1_12_2To1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ServerboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12to1_12_1.packet.ClientboundPackets1_12_1; import com.viaversion.viaversion.protocols.v1_12to1_12_1.packet.ServerboundPackets1_12_1; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; import com.viaversion.viaversion.util.ComponentUtil; import com.viaversion.viaversion.util.ProtocolLogger; import org.checkerframework.checker.nullness.qual.Nullable; public class Protocol1_13To1_12_2 extends BackwardsProtocol { public static final BackwardsMappingData1_13 MAPPINGS = new BackwardsMappingData1_13(); public static final ProtocolLogger LOGGER = new BackwardsProtocolLogger(Protocol1_13To1_12_2.class); private final EntityPacketRewriter1_13 entityRewriter = new EntityPacketRewriter1_13(this); private final BlockItemPacketRewriter1_13 blockItemPackets = new BlockItemPacketRewriter1_13(this); private final JsonNBTComponentRewriter translatableRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON) { @Override protected void handleTranslate(JsonObject root, String translate) { String mappedKey = mappedTranslationKey(translate); if (mappedKey != null || (mappedKey = getMappingData().getTranslateMappings().get(translate)) != null) { root.addProperty("translate", mappedKey); } } }; private final JsonNBTComponentRewriter translatableToLegacyRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON) { @Override protected void handleTranslate(JsonObject root, String translate) { String mappedKey = mappedTranslationKey(translate); if (mappedKey != null || (mappedKey = getMappingData().getTranslateMappings().get(translate)) != null) { root.addProperty("translate", Protocol1_12_2To1_13.MAPPINGS.getMojangTranslation().getOrDefault(mappedKey, mappedKey)); } } }; public Protocol1_13To1_12_2() { super(ClientboundPackets1_13.class, ClientboundPackets1_12_1.class, ServerboundPackets1_13.class, ServerboundPackets1_12_1.class); } @Override protected void registerPackets() { super.registerPackets(); PaintingNames1_13.init(); Via.getManager().getProviders().register(BackwardsBlockEntityProvider.class, new BackwardsBlockEntityProvider()); translatableRewriter.registerLoginDisconnect(); translatableRewriter.registerBossEvent(ClientboundPackets1_13.BOSS_EVENT); translatableRewriter.registerComponentPacket(ClientboundPackets1_13.CHAT); translatableRewriter.registerLegacyOpenWindow(ClientboundPackets1_13.OPEN_SCREEN); translatableRewriter.registerComponentPacket(ClientboundPackets1_13.DISCONNECT); translatableRewriter.registerPlayerCombat(ClientboundPackets1_13.PLAYER_COMBAT); translatableRewriter.registerTitle(ClientboundPackets1_13.SET_TITLES); translatableRewriter.registerTabList(ClientboundPackets1_13.TAB_LIST); new PlayerPacketRewriter1_13(this).register(); new SoundPacketRewriter1_13(this).register(); cancelClientbound(ClientboundPackets1_13.TAG_QUERY); cancelClientbound(ClientboundPackets1_13.PLACE_GHOST_RECIPE); cancelClientbound(ClientboundPackets1_13.RECIPE); cancelClientbound(ClientboundPackets1_13.UPDATE_RECIPES); cancelClientbound(ClientboundPackets1_13.UPDATE_TAGS); replaceClientbound(ClientboundPackets1_13.UPDATE_ADVANCEMENTS, PacketWrapper::cancel); cancelServerbound(ServerboundPackets1_12_1.PLACE_RECIPE); cancelServerbound(ServerboundPackets1_12_1.RECIPE_BOOK_UPDATE); } @Override public void init(UserConnection user) { user.addEntityTracker(this.getClass(), new EntityTrackerBase(user, EntityTypes1_13.EntityType.PLAYER)); user.addClientWorld(this.getClass(), new ClientWorld()); user.put(new BackwardsBlockStorage()); user.put(new TabCompleteStorage()); if (ViaBackwards.getConfig().isFix1_13FacePlayer() && !user.has(PlayerPositionStorage1_13.class)) { user.put(new PlayerPositionStorage1_13()); } user.put(new NoteBlockStorage()); } @Override public BackwardsMappingData1_13 getMappingData() { return MAPPINGS; } @Override public ProtocolLogger getLogger() { return LOGGER; } @Override public EntityPacketRewriter1_13 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_13 getItemRewriter() { return blockItemPackets; } // Don't override the parent method public JsonNBTComponentRewriter translatableRewriter() { return translatableRewriter; } public String jsonToLegacy(UserConnection connection, String value) { if (value.isEmpty()) { return ""; } try { return jsonToLegacy(connection, JsonParser.parseString(value)); } catch (Exception e) { e.printStackTrace(); } return ""; } public String jsonToLegacy(UserConnection connection, @Nullable JsonElement value) { if (value == null || value.isJsonNull()) { return ""; } translatableToLegacyRewriter.processText(connection, value); return ComponentUtil.jsonToLegacy(value); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/block_entity_handlers/BannerHandler.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.block_entity_handlers; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.Protocol1_13To1_12_2; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.provider.BackwardsBlockEntityProvider.BackwardsBlockEntityHandler; public class BannerHandler implements BackwardsBlockEntityHandler { private static final int WALL_BANNER_START = 7110; // 4 each private static final int WALL_BANNER_STOP = 7173; private static final int BANNER_START = 6854; // 16 each private static final int BANNER_STOP = 7109; @Override public CompoundTag transform(int blockId, CompoundTag tag) { // Normal banners if (blockId >= BANNER_START && blockId <= BANNER_STOP) { int color = (blockId - BANNER_START) >> 4; tag.putInt("Base", 15 - color); } // Wall banners else if (blockId >= WALL_BANNER_START && blockId <= WALL_BANNER_STOP) { int color = (blockId - WALL_BANNER_START) >> 2; tag.putInt("Base", 15 - color); } else { Protocol1_13To1_12_2.LOGGER.warning("Why does this block have the banner block entity? :(" + tag); } // Invert colors ListTag patternsTag = tag.getListTag("Patterns", CompoundTag.class); if (patternsTag != null) { for (CompoundTag pattern : patternsTag) { NumberTag colorTag = pattern.getNumberTag("Color"); if (colorTag != null) { pattern.putInt("Color", 15 - colorTag.asInt()); // Invert color id } } } return tag; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/block_entity_handlers/BedHandler.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.block_entity_handlers; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.provider.BackwardsBlockEntityProvider; public class BedHandler implements BackwardsBlockEntityProvider.BackwardsBlockEntityHandler { @Override public CompoundTag transform(int blockId, CompoundTag tag) { int offset = blockId - 748; int color = offset >> 4; tag.putInt("color", color); return tag; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/block_entity_handlers/FlowerPotHandler.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.block_entity_handlers; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.provider.BackwardsBlockEntityProvider; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectOpenHashMap; import com.viaversion.viaversion.util.Pair; public class FlowerPotHandler implements BackwardsBlockEntityProvider.BackwardsBlockEntityHandler { private static final Int2ObjectMap> FLOWERS = new Int2ObjectOpenHashMap<>(22, 0.99F); private static final Pair AIR = new Pair<>("minecraft:air", (byte) 0); static { FLOWERS.put(5265, AIR); register(5266, "minecraft:sapling", (byte) 0); register(5267, "minecraft:sapling", (byte) 1); register(5268, "minecraft:sapling", (byte) 2); register(5269, "minecraft:sapling", (byte) 3); register(5270, "minecraft:sapling", (byte) 4); register(5271, "minecraft:sapling", (byte) 5); register(5272, "minecraft:tallgrass", (byte) 2); register(5273, "minecraft:yellow_flower", (byte) 0); register(5274, "minecraft:red_flower", (byte) 0); register(5275, "minecraft:red_flower", (byte) 1); register(5276, "minecraft:red_flower", (byte) 2); register(5277, "minecraft:red_flower", (byte) 3); register(5278, "minecraft:red_flower", (byte) 4); register(5279, "minecraft:red_flower", (byte) 5); register(5280, "minecraft:red_flower", (byte) 6); register(5281, "minecraft:red_flower", (byte) 7); register(5282, "minecraft:red_flower", (byte) 8); register(5283, "minecraft:red_mushroom", (byte) 0); register(5284, "minecraft:brown_mushroom", (byte) 0); register(5285, "minecraft:deadbush", (byte) 0); register(5286, "minecraft:cactus", (byte) 0); } private static void register(int id, String identifier, byte data) { FLOWERS.put(id, new Pair<>(identifier, data)); } public static boolean isFlowah(int id) { return id >= 5265 && id <= 5286; } public Pair getOrDefault(int blockId) { Pair pair = FLOWERS.get(blockId); return pair != null ? pair : AIR; } // TODO THIS IS NEVER CALLED BECAUSE ITS NO LONGER A BLOCK ENTITY :( @Override public CompoundTag transform(int blockId, CompoundTag tag) { Pair item = getOrDefault(blockId); tag.putString("Item", item.key()); tag.putInt("Data", item.value()); return tag; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/block_entity_handlers/PistonHandler.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.block_entity_handlers; 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_13to1_12_2.Protocol1_13To1_12_2; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.provider.BackwardsBlockEntityProvider; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.data.MappingDataLoader; import com.viaversion.viaversion.libs.fastutil.objects.Object2IntMap; import com.viaversion.viaversion.libs.fastutil.objects.Object2IntOpenHashMap; import com.viaversion.viaversion.protocols.v1_12_2to1_13.blockconnections.ConnectionData; import java.util.Map; import java.util.StringJoiner; public class PistonHandler implements BackwardsBlockEntityProvider.BackwardsBlockEntityHandler { private final Object2IntMap pistonIds = new Object2IntOpenHashMap<>(); public PistonHandler() { pistonIds.defaultReturnValue(-1); if (Via.getConfig().isServersideBlockConnections()) { Map keyToId = ConnectionData.getKeyToId(); for (Map.Entry entry : keyToId.entrySet()) { if (!entry.getKey().contains("piston")) { continue; } addEntries(entry.getKey(), entry.getValue()); } } else { ListTag blockStates = MappingDataLoader.INSTANCE.loadNBT("blockstates-1.13.nbt").getListTag("blockstates", StringTag.class); for (int id = 0; id < blockStates.size(); id++) { StringTag state = blockStates.get(id); String key = state.getValue(); if (!key.contains("piston")) { continue; } addEntries(key, id); } } } // There doesn't seem to be a nicer way around it :( private void addEntries(String data, int id) { id = Protocol1_13To1_12_2.MAPPINGS.getNewBlockStateId(id); pistonIds.put(data, id); String substring = data.substring(10); if (!substring.startsWith("piston") && !substring.startsWith("sticky_piston")) return; // Swap properties and add them to the map String[] split = data.substring(0, data.length() - 1).split("\\["); String[] properties = split[1].split(","); data = split[0] + "[" + properties[1] + "," + properties[0] + "]"; pistonIds.put(data, id); } @Override public CompoundTag transform(int blockId, CompoundTag tag) { CompoundTag blockState = tag.getCompoundTag("blockState"); if (blockState == null) return tag; String dataFromTag = getDataFromTag(blockState); if (dataFromTag == null) return tag; int id = pistonIds.getInt(dataFromTag); if (id == -1) { //TODO see why this could be null and if this is bad return tag; } tag.putInt("blockId", id >> 4); tag.putInt("blockData", id & 15); return tag; } // The type hasn't actually been updated in the blockstorage, so we need to construct it private String getDataFromTag(CompoundTag tag) { StringTag name = tag.getStringTag("Name"); if (name == null) return null; CompoundTag properties = tag.getCompoundTag("Properties"); if (properties == null) return name.getValue(); StringJoiner joiner = new StringJoiner(",", name.getValue() + "[", "]"); for (Map.Entry entry : properties) { if (!(entry.getValue() instanceof StringTag)) continue; joiner.add(entry.getKey() + "=" + ((StringTag) entry.getValue()).getValue()); } return joiner.toString(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/block_entity_handlers/SkullHandler.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.block_entity_handlers; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.provider.BackwardsBlockEntityProvider.BackwardsBlockEntityHandler; public class SkullHandler implements BackwardsBlockEntityHandler { private static final int SKULL_START = 5447; @Override public CompoundTag transform(int blockId, CompoundTag tag) { int diff = blockId - SKULL_START; int pos = diff % 20; byte type = (byte) Math.floor(diff / 20f); // Set type tag.putByte("SkullType", type); // Remove wall skulls if (pos < 4) { return tag; } // Add rotation for normal skulls tag.putByte("Rot", (byte) ((pos - 4) & 255)); return tag; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/block_entity_handlers/SpawnerHandler.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.block_entity_handlers; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.data.EntityNameMappings1_12_2; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.provider.BackwardsBlockEntityProvider; public class SpawnerHandler implements BackwardsBlockEntityProvider.BackwardsBlockEntityHandler { @Override public CompoundTag transform(int blockId, CompoundTag tag) { CompoundTag dataTag = tag.getCompoundTag("SpawnData"); if (dataTag != null) { StringTag idTag = dataTag.getStringTag("id"); if (idTag != null) { idTag.setValue(EntityNameMappings1_12_2.rewrite(idTag.getValue())); } } return tag; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/data/BackwardsMappingData1_13.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.data; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectOpenHashMap; import com.viaversion.viaversion.protocols.v1_12_2to1_13.Protocol1_12_2To1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.data.StatisticMappings1_13; import java.util.HashMap; import java.util.Map; public class BackwardsMappingData1_13 extends BackwardsMappingData { private final Int2ObjectMap statisticMappings = new Int2ObjectOpenHashMap<>(); private final Map translateMappings = new HashMap<>(); public BackwardsMappingData1_13() { super("1.13", "1.12", Protocol1_12_2To1_13.class); } @Override public void loadExtras(final CompoundTag data) { super.loadExtras(data); for (Map.Entry entry : StatisticMappings1_13.CUSTOM_STATS.entrySet()) { statisticMappings.put(entry.getValue().intValue(), entry.getKey()); } for (Map.Entry entry : Protocol1_12_2To1_13.MAPPINGS.getTranslateMapping().entrySet()) { translateMappings.put(entry.getValue(), entry.getKey()); } } @Override public int getNewBlockStateId(int id) { // Comparator funkyness: https://github.com/ViaVersion/ViaBackwards/issues/524 if (id >= 5635 && id <= 5650) { if (id < 5639) { id += 4; } else if (id < 5643) { id -= 4; } else if (id < 5647) { id += 4; } else { id -= 4; } } int mappedId = super.getNewBlockStateId(id); // https://github.com/ViaVersion/ViaBackwards/issues/290 return switch (mappedId) { case 1595, 1596, 1597 -> 1584; // brown mushroom block case 1611, 1612, 1613 -> 1600; // red mushroom block default -> mappedId; }; } @Override protected int checkValidity(int id, int mappedId, String type) { // Don't warn for missing ids here return mappedId; } public Int2ObjectMap getStatisticMappings() { return statisticMappings; } public Map getTranslateMappings() { return translateMappings; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/data/EntityIdMappings1_12_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.data; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntOpenHashMap; import com.viaversion.viaversion.protocols.v1_12_2to1_13.data.EntityIdMappings1_13; public class EntityIdMappings1_12_2 { private static final Int2IntMap TYPES = new Int2IntOpenHashMap(); static { TYPES.defaultReturnValue(-1); for (Int2IntMap.Entry entry : EntityIdMappings1_13.getEntityTypes().int2IntEntrySet()) { EntityIdMappings1_12_2.TYPES.put(entry.getIntValue(), entry.getIntKey()); } } public static int getOldId(int type1_13) { return TYPES.get(type1_13); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/data/EntityNameMappings1_12_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.data; import com.viaversion.viaversion.util.Key; import java.util.HashMap; import java.util.Map; import java.util.Objects; public class EntityNameMappings1_12_2 { private static final Map ENTITY_NAMES = new HashMap<>(); static { // CHANGED NAMES IN 1.13 reg("commandblock_minecart", "command_block_minecart"); reg("ender_crystal", "end_crystal"); reg("evocation_fangs", "evoker_fangs"); reg("evocation_illager", "evoker"); reg("eye_of_ender_signal", "eye_of_ender"); reg("fireworks_rocket", "firework_rocket"); reg("illusion_illager", "illusioner"); reg("snowman", "snow_golem"); reg("villager_golem", "iron_golem"); reg("vindication_illager", "vindicator"); reg("xp_bottle", "experience_bottle"); reg("xp_orb", "experience_orb"); } private static void reg(String past, String future) { ENTITY_NAMES.put(Key.namespaced(future), Key.namespaced(past)); } public static String rewrite(String entName) { String entityName = ENTITY_NAMES.get(Key.namespaced(entName)); return Objects.requireNonNullElse(entityName, entName); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/data/NamedSoundMappings1_12_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.data; import com.viaversion.viaversion.protocols.v1_12_2to1_13.data.NamedSoundMappings1_13; import com.viaversion.viaversion.util.Key; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; public class NamedSoundMappings1_12_2 { private static final Map SOUNDS = new HashMap<>(); static { try { Field field = NamedSoundMappings1_13.class.getDeclaredField("oldToNew"); field.setAccessible(true); Map sounds = (Map) field.get(null); sounds.forEach((sound1_12, sound1_13) -> NamedSoundMappings1_12_2.SOUNDS.put(sound1_13, sound1_12)); } catch (NoSuchFieldException | IllegalAccessException ex) { ex.printStackTrace(); } } public static String getOldId(String sound1_13) { return SOUNDS.get(Key.stripMinecraftNamespace(sound1_13)); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/data/PaintingNames1_13.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.data; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectOpenHashMap; public class PaintingNames1_13 { private static final Int2ObjectMap PAINTINGS = new Int2ObjectOpenHashMap<>(26, 0.99F); public static void init() { add("Kebab"); add("Aztec"); add("Alban"); add("Aztec2"); add("Bomb"); add("Plant"); add("Wasteland"); add("Pool"); add("Courbet"); add("Sea"); add("Sunset"); add("Creebet"); add("Wanderer"); add("Graham"); add("Match"); add("Bust"); add("Stage"); add("Void"); add("SkullAndRoses"); add("Wither"); add("Fighters"); add("Pointer"); add("Pigscene"); add("BurningSkull"); add("Skeleton"); add("DonkeyKong"); } private static void add(String motive) { PAINTINGS.put(PAINTINGS.size(), motive); } public static String getStringId(int id) { return PAINTINGS.getOrDefault(id, "kebab"); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/data/ParticleIdMappings1_12_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.data; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.Protocol1_13To1_12_2; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; public class ParticleIdMappings1_12_2 { private static final ParticleData[] particles; static { ParticleHandler blockHandler = new ParticleHandler() { @Override public int[] rewrite(Protocol1_13To1_12_2 protocol, PacketWrapper wrapper) { return rewrite(wrapper.read(Types.VAR_INT)); } @Override public int[] rewrite(Protocol1_13To1_12_2 protocol, List> data) { return rewrite((int) data.get(0).getValue()); } private int[] rewrite(int newType) { int blockType = Protocol1_13To1_12_2.MAPPINGS.getNewBlockStateId(newType); int type = blockType >> 4; int meta = blockType & 15; return new int[]{type + (meta << 12)}; } @Override public boolean isBlockHandler() { return true; } }; particles = new ParticleData[]{ rewrite(16), // (0->16) minecraft:ambient_entity_effect -> mobSpellAmbient rewrite(20), // (1->20) minecraft:angry_villager -> angryVillager rewrite(35), // (2->35) minecraft:barrier -> barrier rewrite(37, blockHandler), // (3->37) minecraft:block -> blockcrack rewrite(4), // (4->4) minecraft:bubble -> bubble rewrite(29), // (5->29) minecraft:cloud -> cloud rewrite(9), // (6->9) minecraft:crit -> crit rewrite(44), // (7->44) minecraft:damage_indicator -> damageIndicator rewrite(42), // (8->42) minecraft:dragon_breath -> dragonbreath rewrite(19), // (9->19) minecraft:dripping_lava -> dripLava rewrite(18), // (10->18) minecraft:dripping_water -> dripWater rewrite(30, new ParticleHandler() { @Override public int[] rewrite(Protocol1_13To1_12_2 protocol, PacketWrapper wrapper) { float r = wrapper.read(Types.FLOAT); float g = wrapper.read(Types.FLOAT); float b = wrapper.read(Types.FLOAT); float scale = wrapper.read(Types.FLOAT); wrapper.set(Types.FLOAT, 3, r); // 5 - Offset X index=3 wrapper.set(Types.FLOAT, 4, g); // 6 - Offset Y index=4 wrapper.set(Types.FLOAT, 5, b); // 7 - Offset Z index=5 wrapper.set(Types.FLOAT, 6, scale); // 8 - Particle Data index=6 wrapper.set(Types.INT, 1, 0); // 9 - Particle Count index=1 enable rgb particle return null; } @Override public int[] rewrite(Protocol1_13To1_12_2 protocol, List> data) { return null; } }), // (11->30) minecraft:dust -> reddust rewrite(13), // (12->13) minecraft:effect -> spell rewrite(41), // (13->41) minecraft:elder_guardian -> mobappearance rewrite(10), // (14->10) minecraft:enchanted_hit -> magicCrit‌ rewrite(25), // (15->25) minecraft:enchant -> enchantmenttable rewrite(43), // (16->43) minecraft:end_rod -> endRod rewrite(15), // (17->15) minecraft:entity_effect -> mobSpell rewrite(2), // (18->2) minecraft:explosion_emitter -> hugeexplosion rewrite(1), // (19->1) minecraft:explosion -> largeexplode rewrite(46, blockHandler), // (20->46) minecraft:falling_dust -> fallingdust rewrite(3), // (21->3) minecraft:firework -> fireworksSpark rewrite(6), // (22->6) minecraft:fishing -> wake rewrite(26), // (23->26) minecraft:flame -> flame rewrite(21), // (24->21) minecraft:happy_villager -> happyVillager rewrite(34), // (25->34) minecraft:heart -> heart rewrite(14), // (26->14) minecraft:instant_effect -> instantSpell rewrite(36, new ParticleHandler() { @Override public int[] rewrite(Protocol1_13To1_12_2 protocol, PacketWrapper wrapper) { return rewrite(protocol, wrapper.read(Types.ITEM1_13)); } @Override public int[] rewrite(Protocol1_13To1_12_2 protocol, List> data) { return rewrite(protocol, (Item) data.get(0).getValue()); } private int[] rewrite(Protocol1_13To1_12_2 protocol, Item newItem) { Item item = protocol.getItemRewriter().handleItemToClient(null, newItem); return new int[]{item.identifier(), item.data()}; } }), // (27->36) minecraft:item -> iconcrack rewrite(33), // (28->33) minecraft:item_slime -> slime rewrite(31), // (29->31) minecraft:item_snowball -> snowballpoof rewrite(12), // (30->12) minecraft:large_smoke -> largesmoke rewrite(27), // (31->27) minecraft:lava -> lava rewrite(22), // (32->22) minecraft:mycelium -> townaura rewrite(23), // (33->23) minecraft:note -> note rewrite(0), // (34->0) minecraft:poof -> explode rewrite(24), // (35->24) minecraft:portal -> portal rewrite(39), // (36->39) minecraft:rain -> droplet rewrite(11), // (37->11) minecraft:smoke -> smoke rewrite(48), // (38->48) minecraft:spit -> spit rewrite(12), // (39->-1) minecraft:squid_ink -> squid_ink -> large_smoke rewrite(45), // (40->45) minecraft:sweep_attack -> sweepAttack‌ rewrite(47), // (41->47) minecraft:totem_of_undying -> totem rewrite(7), // (42->7) minecraft:underwater -> suspended‌ rewrite(5), // (43->5) minecraft:splash -> splash rewrite(17), // (44->17) minecraft:witch -> witchMagic rewrite(4), // (45->4) minecraft:bubble_pop -> bubble rewrite(4), // (46->4) minecraft:current_down -> bubble rewrite(4), // (47->4) minecraft:bubble_column_up -> bubble rewrite(18), // (48->-1) minecraft:nautilus -> nautilus -> dripWater rewrite(18), // (49->18) minecraft:dolphin -> dripWater }; } public static ParticleData getMapping(int id) { return particles[id]; } private static ParticleData rewrite(int replacementId) { return new ParticleData(replacementId); } private static ParticleData rewrite(int replacementId, ParticleHandler handler) { return new ParticleData(replacementId, handler); } public interface ParticleHandler { int[] rewrite(Protocol1_13To1_12_2 protocol, PacketWrapper wrapper); int[] rewrite(Protocol1_13To1_12_2 protocol, List> data); default boolean isBlockHandler() { return false; } } public static final class ParticleData { private final int historyId; private final ParticleHandler handler; private ParticleData(int historyId, ParticleHandler handler) { this.historyId = historyId; this.handler = handler; } private ParticleData(int historyId) { this(historyId, null); } public int @Nullable [] rewriteData(Protocol1_13To1_12_2 protocol, PacketWrapper wrapper) { if (handler == null) return null; return handler.rewrite(protocol, wrapper); } public int @Nullable [] rewriteMeta(Protocol1_13To1_12_2 protocol, List> data) { if (handler == null) return null; return handler.rewrite(protocol, data); } public int getHistoryId() { return historyId; } public ParticleHandler getHandler() { return handler; } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/provider/BackwardsBlockEntityProvider.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.provider; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.block_entity_handlers.BannerHandler; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.block_entity_handlers.BedHandler; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.block_entity_handlers.FlowerPotHandler; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.block_entity_handlers.PistonHandler; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.block_entity_handlers.SkullHandler; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.block_entity_handlers.SpawnerHandler; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage.BackwardsBlockStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.platform.providers.Provider; import com.viaversion.viaversion.util.Key; import java.util.HashMap; import java.util.Map; public class BackwardsBlockEntityProvider implements Provider { private final Map handlers = new HashMap<>(); public BackwardsBlockEntityProvider() { handlers.put("flower_pot", new FlowerPotHandler()); // TODO requires special treatment, manually send handlers.put("bed", new BedHandler()); handlers.put("banner", new BannerHandler()); handlers.put("skull", new SkullHandler()); handlers.put("mob_spawner", new SpawnerHandler()); handlers.put("piston", new PistonHandler()); } /** * Check if a block entity handler is present * * @param key Id of the NBT data ex: minecraft:bed * @return true if present */ public boolean isHandled(String key) { return handlers.containsKey(Key.stripMinecraftNamespace(key)); } /** * Transform blocks to block entities! * * @param user The user * @param position The position of the block entity * @param tag The block entity tag */ public CompoundTag transform(UserConnection user, BlockPosition position, CompoundTag tag) { final StringTag idTag = tag.getStringTag("id"); if (idTag == null) { return tag; } String id = idTag.getValue(); BackwardsBlockEntityHandler handler = handlers.get(Key.stripMinecraftNamespace(id)); if (handler == null) { return tag; } BackwardsBlockStorage storage = user.get(BackwardsBlockStorage.class); Integer blockId = storage.get(position); if (blockId == null) { return tag; } return handler.transform(blockId, tag); } /** * Transform blocks to block entities! * * @param user The user * @param position The position of the block entity * @param id The block entity id */ public CompoundTag transform(UserConnection user, BlockPosition position, String id) { CompoundTag tag = new CompoundTag(); tag.putString("id", id); tag.putInt("x", Math.toIntExact(position.x())); tag.putInt("y", Math.toIntExact(position.y())); tag.putInt("z", Math.toIntExact(position.z())); return this.transform(user, position, tag); } @FunctionalInterface public interface BackwardsBlockEntityHandler { CompoundTag transform(int blockId, CompoundTag tag); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/rewriter/BlockItemPacketRewriter1_13.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.rewriter; import com.google.common.primitives.Ints; import com.viaversion.nbt.tag.ByteTag; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.api.rewriters.EnchantmentRewriter; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.Protocol1_13To1_12_2; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.block_entity_handlers.FlowerPotHandler; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.provider.BackwardsBlockEntityProvider; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage.BackwardsBlockStorage; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage.NoteBlockStorage; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.BlockChangeRecord; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; import com.viaversion.viaversion.api.minecraft.chunks.DataPalette; import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_13; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_9_3; import com.viaversion.viaversion.protocols.v1_12_2to1_13.Protocol1_12_2To1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.data.BlockIdData; import com.viaversion.viaversion.protocols.v1_12_2to1_13.data.SpawnEggMappings1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12to1_12_1.packet.ClientboundPackets1_12_1; import com.viaversion.viaversion.protocols.v1_12to1_12_1.packet.ServerboundPackets1_12_1; import com.viaversion.viaversion.util.ComponentUtil; import com.viaversion.viaversion.util.IdAndData; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.Pair; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; public class BlockItemPacketRewriter1_13 extends BackwardsItemRewriter { private final Map enchantmentMappings = new HashMap<>(); private final String extraNbtTag; public BlockItemPacketRewriter1_13(Protocol1_13To1_12_2 protocol) { super(protocol, Types.ITEM1_13, Types.ITEM1_13_SHORT_ARRAY, Types.ITEM1_8, Types.ITEM1_8_SHORT_ARRAY); extraNbtTag = nbtTagName("2"); } public static boolean isDamageable(int id) { return id >= 256 && id <= 259 // iron shovel, pickaxe, axe, flint and steel || id == 261 // bow || id >= 267 && id <= 279 // iron sword, wooden+stone+diamond swords, shovels, pickaxes, axes || id >= 283 && id <= 286 // gold sword, shovel, pickaxe, axe || id >= 290 && id <= 294 // hoes || id >= 298 && id <= 317 // armors || id == 346 // fishing rod || id == 359 // shears || id == 398 // carrot on a stick || id == 442 // shield || id == 443; // elytra } @Override protected void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_13.COOLDOWN, wrapper -> { int itemId = wrapper.read(Types.VAR_INT); int oldId = protocol.getMappingData().getItemMappings().getNewId(itemId); if (oldId == -1) { wrapper.cancel(); return; } if (SpawnEggMappings1_13.getEntityId(oldId).isPresent()) { wrapper.write(Types.VAR_INT, IdAndData.toRawData(383)); return; } wrapper.write(Types.VAR_INT, IdAndData.getId(oldId)); }); protocol.registerClientbound(ClientboundPackets1_13.BLOCK_EVENT, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_8); // Location map(Types.UNSIGNED_BYTE); // Action Id map(Types.UNSIGNED_BYTE); // Action param map(Types.VAR_INT); // Block Id - /!\ NOT BLOCK STATE ID handler(wrapper -> { int blockId = wrapper.get(Types.VAR_INT, 0); if (blockId == 73) blockId = 25; else if (blockId == 99) blockId = 33; else if (blockId == 92) blockId = 29; else if (blockId == 142) blockId = 54; else if (blockId == 305) blockId = 146; else if (blockId == 249) blockId = 130; else if (blockId == 257) blockId = 138; else if (blockId == 140) blockId = 52; else if (blockId == 472) blockId = 209; else if (blockId >= 483 && blockId <= 498) blockId = blockId - 483 + 219; if (blockId == 25) { // Note block final NoteBlockStorage noteBlockStorage = wrapper.user().get(NoteBlockStorage.class); final BlockPosition position = wrapper.get(Types.BLOCK_POSITION1_8, 0); final Pair update = noteBlockStorage.getNoteBlockUpdate(position); if (update != null) { // Use values from block state update wrapper.set(Types.UNSIGNED_BYTE, 0, update.key().shortValue()); wrapper.set(Types.UNSIGNED_BYTE, 1, update.value().shortValue()); } } wrapper.set(Types.VAR_INT, 0, blockId); }); } }); protocol.registerClientbound(ClientboundPackets1_13.BLOCK_ENTITY_DATA, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_8); // 0 - Position map(Types.UNSIGNED_BYTE); // 1 - Action map(Types.NAMED_COMPOUND_TAG); // 2 - NBT Data handler(wrapper -> { BackwardsBlockEntityProvider provider = Via.getManager().getProviders().get(BackwardsBlockEntityProvider.class); // TODO conduit handling if (wrapper.get(Types.UNSIGNED_BYTE, 0) == 5) { wrapper.cancel(); } wrapper.set(Types.NAMED_COMPOUND_TAG, 0, provider.transform( wrapper.user(), wrapper.get(Types.BLOCK_POSITION1_8, 0), wrapper.get(Types.NAMED_COMPOUND_TAG, 0) )); }); } }); protocol.registerClientbound(ClientboundPackets1_13.FORGET_LEVEL_CHUNK, wrapper -> { int chunkMinX = wrapper.passthrough(Types.INT) << 4; int chunkMinZ = wrapper.passthrough(Types.INT) << 4; int chunkMaxX = chunkMinX + 15; int chunkMaxZ = chunkMinZ + 15; BackwardsBlockStorage blockStorage = wrapper.user().get(BackwardsBlockStorage.class); blockStorage.getBlocks().entrySet().removeIf(entry -> { BlockPosition position = entry.getKey(); return position.x() >= chunkMinX && position.z() >= chunkMinZ && position.x() <= chunkMaxX && position.z() <= chunkMaxZ; }); }); // Block Change protocol.registerClientbound(ClientboundPackets1_13.BLOCK_UPDATE, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_8); // 0 - Position handler(wrapper -> { int blockState = wrapper.read(Types.VAR_INT); BlockPosition position = wrapper.get(Types.BLOCK_POSITION1_8, 0); // Note block special treatment if (blockState >= 249 && blockState <= 748) { // Note block states id range wrapper.user().get(NoteBlockStorage.class).storeNoteBlockUpdate(position, blockState); } // Store blocks BackwardsBlockStorage storage = wrapper.user().get(BackwardsBlockStorage.class); storage.checkAndStore(position, blockState); wrapper.write(Types.VAR_INT, protocol.getMappingData().getNewBlockStateId(blockState)); // Flower pot special treatment flowerPotSpecialTreatment(wrapper.user(), blockState, position); }); } }); // Multi Block Change protocol.registerClientbound(ClientboundPackets1_13.CHUNK_BLOCKS_UPDATE, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Chunk X map(Types.INT); // 1 - Chunk Z map(Types.BLOCK_CHANGE_ARRAY); handler(wrapper -> { BackwardsBlockStorage storage = wrapper.user().get(BackwardsBlockStorage.class); for (BlockChangeRecord record : wrapper.get(Types.BLOCK_CHANGE_ARRAY, 0)) { int chunkX = wrapper.get(Types.INT, 0); int chunkZ = wrapper.get(Types.INT, 1); int block = record.getBlockId(); BlockPosition position = new BlockPosition( record.getSectionX() + (chunkX * 16), record.getY(), record.getSectionZ() + (chunkZ * 16)); // Store if needed storage.checkAndStore(position, block); // Flower pot special treatment flowerPotSpecialTreatment(wrapper.user(), block, position); // Change to old id record.setBlockId(protocol.getMappingData().getNewBlockStateId(block)); } }); } }); protocol.registerClientbound(ClientboundPackets1_13.LEVEL_CHUNK, wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_13To1_12_2.class); ChunkType1_9_3 type_old = ChunkType1_9_3.forEnvironment(clientWorld.getEnvironment()); ChunkType1_13 type = ChunkType1_13.forEnvironment(clientWorld.getEnvironment()); Chunk chunk = wrapper.read(type); // Handle Block Entities before block rewrite BackwardsBlockEntityProvider provider = Via.getManager().getProviders().get(BackwardsBlockEntityProvider.class); BackwardsBlockStorage storage = wrapper.user().get(BackwardsBlockStorage.class); for (CompoundTag tag : chunk.getBlockEntities()) { StringTag idTag = tag.getStringTag("id"); if (idTag == null) continue; String id = idTag.getValue(); // Ignore if we don't handle it if (!provider.isHandled(id)) continue; int sectionIndex = tag.getNumberTag("y").asInt() >> 4; if (sectionIndex < 0 || sectionIndex > 15) { // 1.17 chunks continue; } ChunkSection section = chunk.getSections()[sectionIndex]; int x = tag.getNumberTag("x").asInt(); short y = tag.getNumberTag("y").asShort(); int z = tag.getNumberTag("z").asInt(); BlockPosition position = new BlockPosition(x, y, z); int block = section.palette(PaletteType.BLOCKS).idAt(x & 0xF, y & 0xF, z & 0xF); storage.checkAndStore(position, block); provider.transform(wrapper.user(), position, tag); } // Rewrite new blocks to old blocks for (int i = 0; i < chunk.getSections().length; i++) { ChunkSection section = chunk.getSections()[i]; if (section == null) { continue; } DataPalette palette = section.palette(PaletteType.BLOCKS); // Flower pots require a special treatment, they are no longer block entities :( for (int y = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { for (int x = 0; x < 16; x++) { int block = palette.idAt(x, y, z); // Check if the block is a flower if (FlowerPotHandler.isFlowah(block)) { BlockPosition pos = new BlockPosition( (x + (chunk.getX() << 4)), (short) (y + (i << 4)), (z + (chunk.getZ() << 4)) ); // Store block storage.checkAndStore(pos, block); CompoundTag nbt = provider.transform(wrapper.user(), pos, "minecraft:flower_pot"); chunk.getBlockEntities().add(nbt); } } } } for (int j = 0; j < palette.size(); j++) { int mappedBlockStateId = protocol.getMappingData().getNewBlockStateId(palette.idByIndex(j)); palette.setIdByIndex(j, mappedBlockStateId); } } if (chunk.isBiomeData()) { for (int i = 0; i < 256; i++) { int biome = chunk.getBiomeData()[i]; int newId = switch (biome) { case 40, 41, 42, 43 -> 9; // end biomes case 47, 48, 49 -> 24; // deep ocean biomes case 50 -> 10; // deep frozen... let's just pick the frozen variant case 44, 45, 46 -> 0; // the other new ocean biomes default -> -1; }; if (newId != -1) { chunk.getBiomeData()[i] = newId; } } } wrapper.write(type_old, chunk); }); protocol.registerClientbound(ClientboundPackets1_13.LEVEL_EVENT, new PacketHandlers() { @Override public void register() { map(Types.INT); // Effect Id map(Types.BLOCK_POSITION1_8); // Location map(Types.INT); // Data handler(wrapper -> { int id = wrapper.get(Types.INT, 0); int data = wrapper.get(Types.INT, 1); if (id == 1010) { // Play record wrapper.set(Types.INT, 1, protocol.getMappingData().getItemMappings().getNewId(data) >> 4); } else if (id == 2001) { // Block break + block break sound data = protocol.getMappingData().getNewBlockStateId(data); int blockId = data >> 4; int blockData = data & 0xF; wrapper.set(Types.INT, 1, (blockId & 0xFFF) | (blockData << 12)); } }); } }); protocol.registerClientbound(ClientboundPackets1_13.MAP_ITEM_DATA, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.BYTE); map(Types.BOOLEAN); handler(wrapper -> { int iconCount = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < iconCount; i++) { int type = wrapper.read(Types.VAR_INT); byte x = wrapper.read(Types.BYTE); byte z = wrapper.read(Types.BYTE); byte direction = wrapper.read(Types.BYTE); wrapper.read(Types.OPTIONAL_COMPONENT); if (type > 9) { wrapper.set(Types.VAR_INT, 1, wrapper.get(Types.VAR_INT, 1) - 1); continue; } wrapper.write(Types.BYTE, (byte) ((type << 4) | (direction & 0x0F))); wrapper.write(Types.BYTE, x); wrapper.write(Types.BYTE, z); } }); } }); } @Override protected void registerRewrites() { enchantmentMappings.put("minecraft:loyalty", "§7Loyalty"); enchantmentMappings.put("minecraft:impaling", "§7Impaling"); enchantmentMappings.put("minecraft:riptide", "§7Riptide"); enchantmentMappings.put("minecraft:channeling", "§7Channeling"); } @Override public Item handleItemToClient(UserConnection connection, Item item) { if (item == null) return null; // Custom mappings/super call moved down int originalId = item.identifier(); Integer rawId = null; boolean gotRawIdFromTag = false; CompoundTag tag = item.tag(); // Use tag to get original ID and data Tag originalIdTag; if (tag != null && (originalIdTag = tag.remove(extraNbtTag)) instanceof NumberTag) { rawId = ((NumberTag) originalIdTag).asInt(); gotRawIdFromTag = true; } if (rawId == null) { // Look for custom mappings item = super.handleItemToClient(connection, item); // Handle one-way special case if (item.identifier() == -1) { if (originalId == 362) { // base/colorless shulker box rawId = 0xe50000; // purple shulker box } else { if (Via.getConfig().logOtherConversionWarnings()) { protocol.getLogger().warning("Failed to get new item for " + originalId); } rawId = 0x10000; } } else { // Use the found custom mapping // Take the newly added tag if (tag == null) { tag = item.tag(); } rawId = itemIdToRaw(item.identifier(), item, tag); } } item.setIdentifier(rawId >> 16); item.setData((short) (rawId & 0xFFFF)); // NBT changes if (tag != null) { if (isDamageable(item.identifier())) { Tag damageTag = tag.remove("Damage"); if (!gotRawIdFromTag && damageTag instanceof NumberTag) { item.setData(((NumberTag) damageTag).asShort()); } } if (item.identifier() == 358) { // map Tag mapTag = tag.remove("map"); if (!gotRawIdFromTag && mapTag instanceof NumberTag) { item.setData(((NumberTag) mapTag).asShort()); } } // Shield and banner invertShieldAndBannerId(item, tag); // Display Name now uses JSON CompoundTag display = tag.getCompoundTag("display"); if (display != null) { StringTag name = display.getStringTag("Name"); if (name != null) { display.putString(extraNbtTag + "|Name", name.getValue()); name.setValue(protocol.jsonToLegacy(connection, name.getValue())); } } // ench is now Enchantments and now uses identifiers rewriteEnchantmentsToClient(tag, false); rewriteEnchantmentsToClient(tag, true); rewriteCanPlaceToClient(tag, "CanPlaceOn"); rewriteCanPlaceToClient(tag, "CanDestroy"); } return item; } private int itemIdToRaw(int oldId, Item item, CompoundTag tag) { Optional eggEntityId = SpawnEggMappings1_13.getEntityId(oldId); if (eggEntityId.isPresent()) { if (tag == null) { item.setTag(tag = new CompoundTag()); } if (!tag.contains("EntityTag")) { CompoundTag entityTag = new CompoundTag(); entityTag.putString("id", eggEntityId.get()); tag.put("EntityTag", entityTag); } return 0x17f0000; // 383 << 16; } return (oldId >> 4) << 16 | oldId & 0xF; } private void rewriteCanPlaceToClient(CompoundTag tag, String tagName) { // The tag was manually created incorrectly so ignore rewriting it ListTag blockTag = tag.getListTag(tagName); if (blockTag == null) return; ListTag newCanPlaceOn = new ListTag<>(StringTag.class); tag.put(extraNbtTag + "|" + tagName, blockTag.copy()); for (Tag oldTag : blockTag) { Object value = oldTag.getValue(); String[] newValues = value instanceof String ? BlockIdData.fallbackReverseMapping.get(Key.stripMinecraftNamespace((String) value)) : null; if (newValues != null) { for (String newValue : newValues) { newCanPlaceOn.add(new StringTag(newValue)); } } else { newCanPlaceOn.add(new StringTag(oldTag.getValue().toString())); } } tag.put(tagName, newCanPlaceOn); } //TODO un-ugly all of this private void rewriteEnchantmentsToClient(CompoundTag tag, boolean storedEnch) { String key = storedEnch ? "StoredEnchantments" : "Enchantments"; ListTag enchantments = tag.getListTag(key, CompoundTag.class); if (enchantments == null) return; ListTag noMapped = new ListTag<>(CompoundTag.class); ListTag newEnchantments = new ListTag<>(CompoundTag.class); List lore = new ArrayList<>(); boolean hasValidEnchants = false; for (CompoundTag enchantmentEntry : enchantments.copy()) { StringTag idTag = enchantmentEntry.getStringTag("id"); if (idTag == null) { continue; } String newId = idTag.getValue(); NumberTag levelTag = enchantmentEntry.getNumberTag("lvl"); if (levelTag == null) { continue; } int levelValue = levelTag.asInt(); short level = levelValue < Short.MAX_VALUE ? (short) levelValue : Short.MAX_VALUE; String mappedEnchantmentId = enchantmentMappings.get(newId); if (mappedEnchantmentId != null) { lore.add(new StringTag(mappedEnchantmentId + " " + EnchantmentRewriter.getRomanNumber(level))); noMapped.add(enchantmentEntry); } else if (!newId.isEmpty()) { Short oldId = Protocol1_12_2To1_13.MAPPINGS.getOldEnchantmentsIds().inverse().get(Key.stripMinecraftNamespace(newId)); if (oldId == null) { if (!newId.startsWith("viaversion:legacy/")) { // Custom enchant (?) noMapped.add(enchantmentEntry); // Some custom-enchant plugins write it into the lore manually, which would double its entry if (ViaBackwards.getConfig().addCustomEnchantsToLore()) { String name = newId; int index = name.indexOf(':') + 1; if (index != 0 && index != name.length()) { name = name.substring(index); } name = "§7" + Character.toUpperCase(name.charAt(0)) + name.substring(1).toLowerCase(Locale.ENGLISH); lore.add(new StringTag(name + " " + EnchantmentRewriter.getRomanNumber(level))); } if (Via.getManager().isDebug()) { protocol.getLogger().warning("Found unknown enchant: " + newId); } continue; } else { oldId = Short.valueOf(newId.substring(18)); } } if (level != 0) { hasValidEnchants = true; } CompoundTag newEntry = new CompoundTag(); newEntry.putShort("id", oldId); newEntry.putShort("lvl", level); newEnchantments.add(newEntry); } } // Put here to hide empty enchantment from 1.14 rewrites if (!storedEnch && !hasValidEnchants) { NumberTag hideFlags = tag.getNumberTag("HideFlags"); if (hideFlags == null) { hideFlags = new IntTag(); tag.put(extraNbtTag + "|DummyEnchant", new ByteTag(false)); } else { tag.putInt(extraNbtTag + "|OldHideFlags", hideFlags.asByte()); } if (newEnchantments.isEmpty()) { CompoundTag enchEntry = new CompoundTag(); enchEntry.putShort("id", (short) 0); enchEntry.putShort("lvl", (short) 0); newEnchantments.add(enchEntry); } int value = hideFlags.asByte() | 1; tag.putInt("HideFlags", value); } if (!noMapped.isEmpty()) { tag.put(extraNbtTag + "|" + key, noMapped); if (!lore.isEmpty()) { CompoundTag display = tag.getCompoundTag("display"); if (display == null) { tag.put("display", display = new CompoundTag()); } ListTag loreTag = display.getListTag("Lore", StringTag.class); if (loreTag == null) { display.put("Lore", loreTag = new ListTag<>(StringTag.class)); tag.put(extraNbtTag + "|DummyLore", new ByteTag(false)); } else if (!loreTag.isEmpty()) { ListTag oldLore = new ListTag<>(StringTag.class); for (StringTag value : loreTag) { oldLore.add(value.copy()); } tag.put(extraNbtTag + "|OldLore", oldLore); lore.addAll(loreTag.getValue()); } loreTag.setValue(lore); } } tag.remove("Enchantments"); tag.put(storedEnch ? key : "ench", newEnchantments); } @Override public Item handleItemToServer(UserConnection connection, Item item) { if (item == null) return null; CompoundTag tag = item.tag(); // Save original id int originalId = (item.identifier() << 16 | item.data() & 0xFFFF); int rawId = IdAndData.toRawData(item.identifier(), item.data()); // NBT Additions if (isDamageable(item.identifier())) { if (tag == null) item.setTag(tag = new CompoundTag()); tag.putInt("Damage", item.data()); } if (item.identifier() == 358) { // map if (tag == null) item.setTag(tag = new CompoundTag()); tag.putInt("map", item.data()); } // NBT Changes if (tag != null) { // Shield and banner invertShieldAndBannerId(item, tag); // Display Name now uses JSON CompoundTag display = tag.getCompoundTag("display"); if (display != null) { StringTag name = display.getStringTag("Name"); if (name != null) { Tag via = display.remove(extraNbtTag + "|Name"); name.setValue(via instanceof StringTag ? ((StringTag) via).getValue() : ComponentUtil.legacyToJsonString(name.getValue())); } } // ench is now Enchantments and now uses identifiers rewriteEnchantmentsToServer(tag, false); rewriteEnchantmentsToServer(tag, true); rewriteCanPlaceToServer(tag, "CanPlaceOn"); rewriteCanPlaceToServer(tag, "CanDestroy"); // Handle SpawnEggs if (item.identifier() == 383) { CompoundTag entityTag = tag.getCompoundTag("EntityTag"); StringTag identifier; if (entityTag != null && (identifier = entityTag.getStringTag("id")) != null) { rawId = SpawnEggMappings1_13.getSpawnEggId(identifier.getValue()); if (rawId == -1) { rawId = 25100288; // Bat fallback } else { entityTag.remove("id"); if (entityTag.isEmpty()) { tag.remove("EntityTag"); } } } else { // Fallback to bat rawId = 25100288; } } if (tag.isEmpty()) { item.setTag(tag = null); } } // Handle custom mappings int identifier = item.identifier(); item.setIdentifier(rawId); item = super.handleItemToServer(connection, item); // Mapped with original data, we can return here if (item.identifier() != rawId && item.identifier() != -1) return item; // Set to legacy id again item.setIdentifier(identifier); int newId = -1; if (protocol.getMappingData().getItemMappings().inverse().getNewId(rawId) == -1) { if (!isDamageable(item.identifier()) && item.identifier() != 358) { // Map if (tag == null) { item.setTag(tag = new CompoundTag()); } tag.putInt(extraNbtTag, originalId); // Data will be lost, saving original id } if (item.identifier() == 229) { // purple shulker box newId = 362; // directly set the new id -> base/colorless shulker box } else if (item.identifier() == 31 && item.data() == 0) { // Shrub was removed rawId = IdAndData.toRawData(32); // Dead Bush } else if (protocol.getMappingData().getItemMappings().inverse().getNewId(rawId & ~0xF) != -1) { rawId &= ~0xF; // Remove data } else { if (Via.getConfig().logOtherConversionWarnings()) { protocol.getLogger().warning("Failed to get old item for " + item.identifier()); } rawId = 16; // Stone } } if (newId == -1) { newId = protocol.getMappingData().getItemMappings().inverse().getNewId(rawId); } item.setIdentifier(newId); item.setData((short) 0); return item; } private void rewriteCanPlaceToServer(CompoundTag tag, String tagName) { if (tag.getListTag(tagName) == null) return; ListTag blockTag = tag.getListTag(extraNbtTag + "|" + tagName); if (blockTag != null) { tag.remove(extraNbtTag + "|" + tagName); tag.put(tagName, blockTag.copy()); } else if ((blockTag = tag.getListTag(tagName)) != null) { ListTag newCanPlaceOn = new ListTag<>(StringTag.class); for (Tag oldTag : blockTag) { Object value = oldTag.getValue(); String oldId = Key.stripMinecraftNamespace(value.toString()); int key = Ints.tryParse(oldId); String numberConverted = BlockIdData.numberIdToString.get(key); if (numberConverted != null) { oldId = numberConverted; } String lowerCaseId = oldId.toLowerCase(Locale.ROOT); String[] newValues = BlockIdData.blockIdMapping.get(lowerCaseId); if (newValues != null) { for (String newValue : newValues) { newCanPlaceOn.add(new StringTag(newValue)); } } else { newCanPlaceOn.add(new StringTag(lowerCaseId)); } } tag.put(tagName, newCanPlaceOn); } } private void rewriteEnchantmentsToServer(CompoundTag tag, boolean storedEnch) { String key = storedEnch ? "StoredEnchantments" : "Enchantments"; ListTag enchantments = tag.getListTag(storedEnch ? key : "ench", CompoundTag.class); if (enchantments == null) return; ListTag newEnchantments = new ListTag<>(CompoundTag.class); boolean dummyEnchant = false; if (!storedEnch) { Tag hideFlags = tag.remove(extraNbtTag + "|OldHideFlags"); if (hideFlags instanceof IntTag) { tag.putInt("HideFlags", ((NumberTag) hideFlags).asByte()); dummyEnchant = true; } else if (tag.remove(extraNbtTag + "|DummyEnchant") != null) { tag.remove("HideFlags"); dummyEnchant = true; } } for (CompoundTag entryTag : enchantments) { NumberTag idTag = entryTag.getNumberTag("id"); NumberTag levelTag = entryTag.getNumberTag("lvl"); CompoundTag enchantmentEntry = new CompoundTag(); short oldId = idTag != null ? idTag.asShort() : 0; short level = levelTag != null ? levelTag.asShort() : 0; if (dummyEnchant && oldId == 0 && level == 0) { continue; // Skip dummy enchatment } String newId = Protocol1_12_2To1_13.MAPPINGS.getOldEnchantmentsIds().get(oldId); if (newId == null) { newId = "viaversion:legacy/" + oldId; } enchantmentEntry.putString("id", newId); enchantmentEntry.putShort("lvl", level); newEnchantments.add(enchantmentEntry); } ListTag noMapped = tag.getListTag(extraNbtTag + "|Enchantments", CompoundTag.class); if (noMapped != null) { for (CompoundTag value : noMapped) { newEnchantments.add(value); } tag.remove(extraNbtTag + "|Enchantments"); } CompoundTag display = tag.getCompoundTag("display"); if (display == null) { tag.put("display", display = new CompoundTag()); } ListTag oldLore = tag.getListTag(extraNbtTag + "|OldLore", StringTag.class); if (oldLore != null) { ListTag lore = display.getListTag("Lore", StringTag.class); if (lore == null) { tag.put("Lore", lore = new ListTag<>(StringTag.class)); } lore.setValue(oldLore.getValue()); tag.remove(extraNbtTag + "|OldLore"); } else if (tag.remove(extraNbtTag + "|DummyLore") != null) { display.remove("Lore"); if (display.isEmpty()) { tag.remove("display"); } } if (!storedEnch) { tag.remove("ench"); } tag.put(key, newEnchantments); } private void invertShieldAndBannerId(Item item, CompoundTag tag) { if (item.identifier() != 442 && item.identifier() != 425) return; CompoundTag blockEntityTag = tag.getCompoundTag("BlockEntityTag"); if (blockEntityTag == null) return; NumberTag base = blockEntityTag.getNumberTag("Base"); if (base != null) { blockEntityTag.putInt("Base", 15 - base.asInt()); // Invert color id } ListTag patterns = blockEntityTag.getListTag("Patterns", CompoundTag.class); if (patterns != null) { for (CompoundTag pattern : patterns) { NumberTag colorTag = pattern.getNumberTag("Color"); pattern.putInt("Color", 15 - colorTag.asInt()); // Invert color id } } } // TODO find a less hacky way to do this (https://bugs.mojang.com/browse/MC-74231) private static void flowerPotSpecialTreatment(UserConnection user, int blockState, BlockPosition position) { if (FlowerPotHandler.isFlowah(blockState)) { BackwardsBlockEntityProvider beProvider = Via.getManager().getProviders().get(BackwardsBlockEntityProvider.class); CompoundTag nbt = beProvider.transform(user, position, "minecraft:flower_pot"); // Remove the flowerpot PacketWrapper blockUpdateRemove = PacketWrapper.create(ClientboundPackets1_12_1.BLOCK_UPDATE, user); blockUpdateRemove.write(Types.BLOCK_POSITION1_8, position); blockUpdateRemove.write(Types.VAR_INT, 0); blockUpdateRemove.scheduleSend(Protocol1_13To1_12_2.class); // Create the flowerpot PacketWrapper blockCreate = PacketWrapper.create(ClientboundPackets1_12_1.BLOCK_UPDATE, user); blockCreate.write(Types.BLOCK_POSITION1_8, position); blockCreate.write(Types.VAR_INT, Protocol1_13To1_12_2.MAPPINGS.getNewBlockStateId(blockState)); blockCreate.scheduleSend(Protocol1_13To1_12_2.class); // Send a block entity update PacketWrapper wrapper = PacketWrapper.create(ClientboundPackets1_12_1.BLOCK_ENTITY_DATA, user); wrapper.write(Types.BLOCK_POSITION1_8, position); wrapper.write(Types.UNSIGNED_BYTE, (short) 5); wrapper.write(Types.NAMED_COMPOUND_TAG, nbt); wrapper.scheduleSend(Protocol1_13To1_12_2.class); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/rewriter/EntityPacketRewriter1_13.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.rewriter; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.entities.storage.EntityPositionHandler; import com.viaversion.viabackwards.api.rewriters.LegacyEntityRewriter; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.Protocol1_13To1_12_2; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.data.EntityIdMappings1_12_2; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.data.PaintingNames1_13; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.data.ParticleIdMappings1_12_2; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage.BackwardsBlockStorage; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage.NoteBlockStorage; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage.PlayerPositionStorage1_13; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_13; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_12; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_13; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12to1_12_1.packet.ClientboundPackets1_12_1; import com.viaversion.viaversion.protocols.v1_12to1_12_1.packet.ServerboundPackets1_12_1; public class EntityPacketRewriter1_13 extends LegacyEntityRewriter { public EntityPacketRewriter1_13(Protocol1_13To1_12_2 protocol) { super(protocol); } @Override protected void registerPackets() { protocol.registerClientbound(ClientboundPackets1_13.PLAYER_POSITION, new PacketHandlers() { @Override public void register() { map(Types.DOUBLE); map(Types.DOUBLE); map(Types.DOUBLE); map(Types.FLOAT); map(Types.FLOAT); map(Types.BYTE); handler(wrapper -> { if (!ViaBackwards.getConfig().isFix1_13FacePlayer()) return; PlayerPositionStorage1_13 playerStorage = wrapper.user().get(PlayerPositionStorage1_13.class); byte bitField = wrapper.get(Types.BYTE, 0); playerStorage.setX(toSet(bitField, 0, playerStorage.x(), wrapper.get(Types.DOUBLE, 0))); playerStorage.setY(toSet(bitField, 1, playerStorage.y(), wrapper.get(Types.DOUBLE, 1))); playerStorage.setZ(toSet(bitField, 2, playerStorage.z(), wrapper.get(Types.DOUBLE, 2))); }); } }); protocol.registerClientbound(ClientboundPackets1_13.ADD_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.UUID); map(Types.BYTE); map(Types.DOUBLE); map(Types.DOUBLE); map(Types.DOUBLE); map(Types.BYTE); map(Types.BYTE); map(Types.INT); handler(getObjectTrackerHandler()); handler(wrapper -> { int id = wrapper.get(Types.BYTE, 0); int data = wrapper.get(Types.INT, 0); EntityTypes1_13.ObjectType type = EntityTypes1_13.ObjectType.findById(id, data); if (type == EntityTypes1_13.ObjectType.FALLING_BLOCK) { int combined = Protocol1_13To1_12_2.MAPPINGS.getNewBlockStateId(data); combined = ((combined >> 4) & 0xFFF) | ((combined & 0xF) << 12); wrapper.set(Types.INT, 0, combined); } else if (type == EntityTypes1_13.ObjectType.ITEM_FRAME) { data = switch (data) { case 3 -> 0; case 4 -> 1; case 5 -> 3; default -> data; }; wrapper.set(Types.INT, 0, data); } else if (type == EntityTypes1_13.ObjectType.TRIDENT) { wrapper.set(Types.BYTE, 0, (byte) EntityTypes1_13.ObjectType.TIPPED_ARROW.getId()); } }); } }); registerTracker(ClientboundPackets1_13.ADD_EXPERIENCE_ORB, EntityTypes1_13.EntityType.EXPERIENCE_ORB); registerTracker(ClientboundPackets1_13.ADD_GLOBAL_ENTITY, EntityTypes1_13.EntityType.LIGHTNING_BOLT); protocol.registerClientbound(ClientboundPackets1_13.ADD_MOB, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.UUID); map(Types.VAR_INT); map(Types.DOUBLE); map(Types.DOUBLE); map(Types.DOUBLE); map(Types.BYTE); map(Types.BYTE); map(Types.BYTE); map(Types.SHORT); map(Types.SHORT); map(Types.SHORT); map(Types1_13.ENTITY_DATA_LIST, Types.ENTITY_DATA_LIST1_12); handler(wrapper -> { int type = wrapper.get(Types.VAR_INT, 1); EntityType entityType = EntityTypes1_13.EntityType.findById(type); if (entityType == null) { return; } tracker(wrapper.user()).addEntity(wrapper.get(Types.VAR_INT, 0), entityType); int oldId = EntityIdMappings1_12_2.getOldId(type); if (oldId == -1) { if (!hasData(entityType)) { protocol.getLogger().warning("Could not find entity type mapping " + type + "/" + entityType); } } else { wrapper.set(Types.VAR_INT, 1, oldId); } }); // Rewrite entity type / ddata handler(getMobSpawnRewriter1_11(Types.ENTITY_DATA_LIST1_12)); } }); protocol.registerClientbound(ClientboundPackets1_13.ADD_PLAYER, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.UUID); map(Types.DOUBLE); map(Types.DOUBLE); map(Types.DOUBLE); map(Types.BYTE); map(Types.BYTE); map(Types1_13.ENTITY_DATA_LIST, Types.ENTITY_DATA_LIST1_12); handler(getTrackerAndDataHandler(Types.ENTITY_DATA_LIST1_12, EntityTypes1_13.EntityType.PLAYER)); } }); protocol.registerClientbound(ClientboundPackets1_13.ADD_PAINTING, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.UUID); handler(getTrackerHandler(EntityTypes1_13.EntityType.PAINTING)); handler(wrapper -> { int motive = wrapper.read(Types.VAR_INT); String title = PaintingNames1_13.getStringId(motive); wrapper.write(Types.STRING, title); }); } }); registerJoinGame(ClientboundPackets1_13.LOGIN, EntityTypes1_13.EntityType.PLAYER); protocol.registerClientbound(ClientboundPackets1_13.RESPAWN, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Dimension ID handler(wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_13To1_12_2.class); int dimensionId = wrapper.get(Types.INT, 0); if (clientWorld.setEnvironment(dimensionId)) { tracker(wrapper.user()).clearEntities(); wrapper.user().get(BackwardsBlockStorage.class).clear(); wrapper.user().get(NoteBlockStorage.class).clear(); } }); } }); registerSetEntityData(ClientboundPackets1_13.SET_ENTITY_DATA, Types1_13.ENTITY_DATA_LIST, Types.ENTITY_DATA_LIST1_12); // Face Player (new packet) protocol.registerClientbound(ClientboundPackets1_13.PLAYER_LOOK_AT, null, wrapper -> { wrapper.cancel(); if (!ViaBackwards.getConfig().isFix1_13FacePlayer()) return; // We will just accept a possible, very minor mismatch between server and client position, // and will take the server's one in both cases, else we would have to cache all entities' positions. final int anchor = wrapper.read(Types.VAR_INT); // feet/eyes enum final double x = wrapper.read(Types.DOUBLE); final double y = wrapper.read(Types.DOUBLE); final double z = wrapper.read(Types.DOUBLE); PlayerPositionStorage1_13 positionStorage = wrapper.user().get(PlayerPositionStorage1_13.class); // Send teleport packet to client PacketWrapper positionAndLook = wrapper.create(ClientboundPackets1_12_1.PLAYER_POSITION); positionAndLook.write(Types.DOUBLE, 0D); positionAndLook.write(Types.DOUBLE, 0D); positionAndLook.write(Types.DOUBLE, 0D); //TODO properly cache and calculate head position? EntityPositionHandler.writeFacingDegrees(positionAndLook, positionStorage.x(), anchor == 1 ? positionStorage.y() + 1.62 : positionStorage.y(), positionStorage.z(), x, y, z); positionAndLook.write(Types.BYTE, (byte) 7); // bitfield, 0=absolute, 1=relative - x,y,z relative, yaw,pitch absolute positionAndLook.write(Types.VAR_INT, -1); positionAndLook.send(Protocol1_13To1_12_2.class); }); PacketHandler movementRemapper = wrapper -> { if (!ViaBackwards.getConfig().isFix1_13FacePlayer()) { return; } final double x = wrapper.passthrough(Types.DOUBLE); final double y = wrapper.passthrough(Types.DOUBLE); final double z = wrapper.passthrough(Types.DOUBLE); wrapper.user().get(PlayerPositionStorage1_13.class).setPosition(x, y, z); }; protocol.registerServerbound(ServerboundPackets1_12_1.MOVE_PLAYER_POS, movementRemapper); // Player Position protocol.registerServerbound(ServerboundPackets1_12_1.MOVE_PLAYER_POS_ROT, movementRemapper); // Player Position And Look (serverbound) protocol.registerServerbound(ServerboundPackets1_12_1.MOVE_VEHICLE, movementRemapper); // Vehicle Move (serverbound) } @Override protected void registerRewrites() { // Rewrite new Entity 'drowned' mapEntityTypeWithData(EntityTypes1_13.EntityType.DROWNED, EntityTypes1_13.EntityType.ZOMBIE_VILLAGER).plainName(); // Fishy mapEntityTypeWithData(EntityTypes1_13.EntityType.COD, EntityTypes1_13.EntityType.SQUID).plainName(); mapEntityTypeWithData(EntityTypes1_13.EntityType.SALMON, EntityTypes1_13.EntityType.SQUID).plainName(); mapEntityTypeWithData(EntityTypes1_13.EntityType.PUFFERFISH, EntityTypes1_13.EntityType.SQUID).plainName(); mapEntityTypeWithData(EntityTypes1_13.EntityType.TROPICAL_FISH, EntityTypes1_13.EntityType.SQUID).plainName(); // Phantom mapEntityTypeWithData(EntityTypes1_13.EntityType.PHANTOM, EntityTypes1_13.EntityType.PARROT).plainName().spawnEntityData(storage -> { // The phantom is grey/blue so let's do yellow/blue storage.add(new EntityData(15, EntityDataTypes1_12.VAR_INT, 3)); }); // Dolphin mapEntityTypeWithData(EntityTypes1_13.EntityType.DOLPHIN, EntityTypes1_13.EntityType.SQUID).plainName(); // Turtle mapEntityTypeWithData(EntityTypes1_13.EntityType.TURTLE, EntityTypes1_13.EntityType.OCELOT).plainName(); // Rewrite Data types filter().handler((event, data) -> { int typeId = data.dataType().typeId(); if (typeId == 4) { JsonElement element = data.value(); protocol.translatableRewriter().processText(event.user(), element); data.setDataType(EntityDataTypes1_12.COMPONENT); } else if (typeId == 5) { // Rewrite optional chat to string JsonElement element = data.value(); data.setTypeAndValue(EntityDataTypes1_12.STRING, protocol.jsonToLegacy(event.user(), element)); } else if (typeId == 6) { Item item = (Item) data.getValue(); data.setTypeAndValue(EntityDataTypes1_12.ITEM, protocol.getItemRewriter().handleItemToClient(event.user(), item)); } else if (typeId == 15) { // Discontinue particles event.cancel(); } else { data.setDataType(EntityDataTypes1_12.byId(typeId > 5 ? typeId - 1 : typeId)); } }); // Handle zombie entity data filter().type(EntityTypes1_13.EntityType.ZOMBIE).removeIndex(15); // Handle turtle entity data (Remove them all for now) filter().type(EntityTypes1_13.EntityType.TURTLE).cancel(13); // Home pos filter().type(EntityTypes1_13.EntityType.TURTLE).cancel(14); // Has egg filter().type(EntityTypes1_13.EntityType.TURTLE).cancel(15); // Laying egg filter().type(EntityTypes1_13.EntityType.TURTLE).cancel(16); // Travel pos filter().type(EntityTypes1_13.EntityType.TURTLE).cancel(17); // Going home filter().type(EntityTypes1_13.EntityType.TURTLE).cancel(18); // Traveling // Remove additional fish data filter().type(EntityTypes1_13.EntityType.ABSTRACT_FISH).cancel(12); filter().type(EntityTypes1_13.EntityType.ABSTRACT_FISH).cancel(13); // Remove phantom size filter().type(EntityTypes1_13.EntityType.PHANTOM).cancel(12); // Remove boat splash timer filter().type(EntityTypes1_13.EntityType.BOAT).cancel(12); // Remove Trident special loyalty level filter().type(EntityTypes1_13.EntityType.TRIDENT).cancel(7); // Handle new wolf colors filter().type(EntityTypes1_13.EntityType.WOLF).index(17).handler((event, data) -> { data.setValue(15 - (int) data.getValue()); }); // Rewrite AreaEffectCloud filter().type(EntityTypes1_13.EntityType.AREA_EFFECT_CLOUD).index(9).handler((event, data) -> { Particle particle = (Particle) data.getValue(); ParticleIdMappings1_12_2.ParticleData particleData = ParticleIdMappings1_12_2.getMapping(particle.id()); int firstArg = 0; int secondArg = 0; int[] particleArgs = particleData.rewriteMeta(protocol, particle.getArguments()); if (particleArgs != null && particleArgs.length != 0) { if (particleData.getHandler().isBlockHandler() && particleArgs[0] == 0) { // Air doesn't have a break particle for sub 1.13 clients -> glass pane particleArgs[0] = 102; } firstArg = particleArgs[0]; secondArg = particleArgs.length == 2 ? particleArgs[1] : 0; } event.createExtraData(new EntityData(9, EntityDataTypes1_12.VAR_INT, particleData.getHistoryId())); event.createExtraData(new EntityData(10, EntityDataTypes1_12.VAR_INT, firstArg)); event.createExtraData(new EntityData(11, EntityDataTypes1_12.VAR_INT, secondArg)); event.cancel(); }); } @Override public EntityType typeFromId(int typeId) { return EntityTypes1_13.EntityType.findById(typeId); } @Override public EntityType objectTypeFromId(int typeId, int data) { return EntityTypes1_13.ObjectType.getEntityType(typeId, data); } @Override public int newEntityId(final int newId) { return EntityIdMappings1_12_2.getOldId(newId); } private static double toSet(int field, int bitIndex, double origin, double packetValue) { // If bit is set, coordinate is relative return (field & (1 << bitIndex)) != 0 ? origin + packetValue : packetValue; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/rewriter/PlayerPacketRewriter1_13.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.rewriter; import com.google.common.base.Joiner; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.Protocol1_13To1_12_2; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.data.ParticleIdMappings1_12_2; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage.TabCompleteStorage; import com.viaversion.viabackwards.utils.ChatUtil; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.packet.State; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.rewriter.RewriterBase; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets; import com.viaversion.viaversion.protocols.base.ServerboundLoginPackets; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ServerboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.rewriter.ItemPacketRewriter1_13; import com.viaversion.viaversion.protocols.v1_12to1_12_1.packet.ClientboundPackets1_12_1; import com.viaversion.viaversion.protocols.v1_12to1_12_1.packet.ServerboundPackets1_12_1; import com.viaversion.viaversion.rewriter.CommandRewriter; import com.viaversion.viaversion.util.Key; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; public class PlayerPacketRewriter1_13 extends RewriterBase { private final CommandRewriter commandRewriter = new CommandRewriter<>(protocol); public PlayerPacketRewriter1_13(Protocol1_13To1_12_2 protocol) { super(protocol); } @Override protected void registerPackets() { // Login Plugin Request protocol.registerClientbound(State.LOGIN, ClientboundLoginPackets.CUSTOM_QUERY.getId(), -1, new PacketHandlers() { @Override public void register() { handler(packetWrapper -> { packetWrapper.cancel(); // Plugin response packetWrapper.create(ServerboundLoginPackets.CUSTOM_QUERY_ANSWER.getId(), wrapper -> { wrapper.write(Types.VAR_INT, packetWrapper.read(Types.VAR_INT)); // Packet id wrapper.write(Types.BOOLEAN, false); // Success }).sendToServer(Protocol1_13To1_12_2.class); }); } }); protocol.registerClientbound(ClientboundPackets1_13.CUSTOM_PAYLOAD, wrapper -> { String channel = wrapper.read(Types.STRING); if (channel.equals("minecraft:trader_list")) { wrapper.write(Types.STRING, "MC|TrList"); protocol.getItemRewriter().handleTradeList(wrapper); } else { String oldChannel = ItemPacketRewriter1_13.getOldPluginChannelId(channel); if (oldChannel == null) { if (Via.getConfig().logOtherConversionWarnings()) { protocol.getLogger().warning("Ignoring clientbound plugin message with channel: " + channel); } wrapper.cancel(); return; } wrapper.write(Types.STRING, oldChannel); if (oldChannel.equals("REGISTER") || oldChannel.equals("UNREGISTER")) { String[] channels = new String(wrapper.read(Types.REMAINING_BYTES), StandardCharsets.UTF_8).split("\0"); List rewrittenChannels = new ArrayList<>(); for (String s : channels) { String rewritten = ItemPacketRewriter1_13.getOldPluginChannelId(s); if (rewritten != null) { rewrittenChannels.add(rewritten); } else if (Via.getConfig().logOtherConversionWarnings()) { protocol.getLogger().warning("Ignoring plugin channel in clientbound " + oldChannel + ": " + s); } } wrapper.write(Types.REMAINING_BYTES, Joiner.on('\0').join(rewrittenChannels).getBytes(StandardCharsets.UTF_8)); } } }); protocol.registerClientbound(ClientboundPackets1_13.LEVEL_PARTICLES, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Particle ID map(Types.BOOLEAN); // 1 - Long Distance map(Types.FLOAT); // 2 - X map(Types.FLOAT); // 3 - Y map(Types.FLOAT); // 4 - Z map(Types.FLOAT); // 5 - Offset X map(Types.FLOAT); // 6 - Offset Y map(Types.FLOAT); // 7 - Offset Z map(Types.FLOAT); // 8 - Particle Data map(Types.INT); // 9 - Particle Count handler(wrapper -> { ParticleIdMappings1_12_2.ParticleData old = ParticleIdMappings1_12_2.getMapping(wrapper.get(Types.INT, 0)); wrapper.set(Types.INT, 0, old.getHistoryId()); int[] data = old.rewriteData(protocol, wrapper); if (data != null) { if (old.getHandler().isBlockHandler() && data[0] == 0) { // Cancel air block particles wrapper.cancel(); return; } for (int i : data) { wrapper.write(Types.VAR_INT, i); } } }); } }); protocol.registerClientbound(ClientboundPackets1_13.PLAYER_INFO, new PacketHandlers() { @Override public void register() { handler(packetWrapper -> { TabCompleteStorage storage = packetWrapper.user().get(TabCompleteStorage.class); int action = packetWrapper.passthrough(Types.VAR_INT); int nPlayers = packetWrapper.passthrough(Types.VAR_INT); for (int i = 0; i < nPlayers; i++) { UUID uuid = packetWrapper.passthrough(Types.UUID); if (action == 0) { // Add String name = packetWrapper.passthrough(Types.STRING); storage.usernames().put(uuid, name); packetWrapper.passthrough(Types.PROFILE_PROPERTY_ARRAY); packetWrapper.passthrough(Types.VAR_INT); packetWrapper.passthrough(Types.VAR_INT); packetWrapper.passthrough(Types.OPTIONAL_COMPONENT); } else if (action == 1) { // Update Game Mode packetWrapper.passthrough(Types.VAR_INT); } else if (action == 2) { // Update Ping packetWrapper.passthrough(Types.VAR_INT); } else if (action == 3) { // Update Display Name packetWrapper.passthrough(Types.OPTIONAL_COMPONENT); } else if (action == 4) { // Remove Player storage.usernames().remove(uuid); } } }); } }); protocol.registerClientbound(ClientboundPackets1_13.SET_OBJECTIVE, new PacketHandlers() { @Override public void register() { map(Types.STRING); map(Types.BYTE); handler(wrapper -> { byte mode = wrapper.get(Types.BYTE, 0); if (mode == 0 || mode == 2) { JsonElement value = wrapper.read(Types.COMPONENT); String legacyValue = protocol.jsonToLegacy(wrapper.user(), value); wrapper.write(Types.STRING, ChatUtil.fromLegacy(legacyValue, 'f', 32)); int type = wrapper.read(Types.VAR_INT); wrapper.write(Types.STRING, type == 1 ? "hearts" : "integer"); } }); } }); protocol.registerClientbound(ClientboundPackets1_13.SET_PLAYER_TEAM, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Name map(Types.BYTE); // Action handler(wrapper -> { byte action = wrapper.get(Types.BYTE, 0); if (action == 0 || action == 2) { JsonElement displayName = wrapper.read(Types.COMPONENT); String legacyTextDisplayName = protocol.jsonToLegacy(wrapper.user(), displayName); wrapper.write(Types.STRING, ChatUtil.fromLegacy(legacyTextDisplayName, 'f', 32)); byte flags = wrapper.read(Types.BYTE); String nameTagVisibility = wrapper.read(Types.STRING); String collisionRule = wrapper.read(Types.STRING); int colour = wrapper.read(Types.VAR_INT); if (colour == 21) { colour = -1; } JsonElement prefixComponent = wrapper.read(Types.COMPONENT); JsonElement suffixComponent = wrapper.read(Types.COMPONENT); String prefix = protocol.jsonToLegacy(wrapper.user(), prefixComponent); if (ViaBackwards.getConfig().addTeamColorTo1_13Prefix()) { prefix += "§" + (colour > -1 && colour <= 15 ? Integer.toHexString(colour) : "r"); } String suffix = protocol.jsonToLegacy(wrapper.user(), suffixComponent); wrapper.write(Types.STRING, ChatUtil.fromLegacyPrefix(prefix, 'f', 16)); wrapper.write(Types.STRING, ChatUtil.fromLegacy(suffix, '\0', 16)); wrapper.write(Types.BYTE, flags); wrapper.write(Types.STRING, nameTagVisibility); wrapper.write(Types.STRING, collisionRule); wrapper.write(Types.BYTE, (byte) colour); } if (action == 0 || action == 3 || action == 4) { wrapper.passthrough(Types.STRING_ARRAY); //Entities } }); } }); protocol.registerClientbound(ClientboundPackets1_13.COMMANDS, null, wrapper -> { wrapper.cancel(); TabCompleteStorage storage = wrapper.user().get(TabCompleteStorage.class); if (!storage.commands().isEmpty()) { storage.commands().clear(); } int size = wrapper.read(Types.VAR_INT); boolean initialNodes = true; for (int i = 0; i < size; i++) { byte flags = wrapper.read(Types.BYTE); wrapper.read(Types.VAR_INT_ARRAY_PRIMITIVE); // Children indices if ((flags & 0x08) != 0) { wrapper.read(Types.VAR_INT); // Redirect node index } byte nodeType = (byte) (flags & 0x03); if (initialNodes && nodeType == 2) { initialNodes = false; } if (nodeType == 1 || nodeType == 2) { // Literal/argument node String name = wrapper.read(Types.STRING); if (nodeType == 1 && initialNodes) { storage.commands().add('/' + name); } } if (nodeType == 2) { // Argument node commandRewriter.handleArgument(wrapper, wrapper.read(Types.STRING)); } if ((flags & 0x10) != 0) { wrapper.read(Types.STRING); // Suggestion type } } }); protocol.registerClientbound(ClientboundPackets1_13.COMMAND_SUGGESTIONS, wrapper -> { TabCompleteStorage storage = wrapper.user().get(TabCompleteStorage.class); if (storage.lastRequest() == null) { wrapper.cancel(); return; } if (storage.lastId() != wrapper.read(Types.VAR_INT)) wrapper.cancel(); int start = wrapper.read(Types.VAR_INT); int length = wrapper.read(Types.VAR_INT); int lastRequestPartIndex = storage.lastRequest().lastIndexOf(' ') + 1; if (lastRequestPartIndex != start) wrapper.cancel(); // Client only replaces after space if (length != storage.lastRequest().length() - lastRequestPartIndex) { wrapper.cancel(); // We can't set the length in previous versions } int count = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < count; i++) { String match = wrapper.read(Types.STRING); wrapper.write(Types.STRING, (start == 0 && !storage.isLastAssumeCommand() ? "/" : "") + match); wrapper.read(Types.OPTIONAL_COMPONENT); // Remove tooltip } }); protocol.registerServerbound(ServerboundPackets1_12_1.COMMAND_SUGGESTION, wrapper -> { TabCompleteStorage storage = wrapper.user().get(TabCompleteStorage.class); List suggestions = new ArrayList<>(); String command = wrapper.read(Types.STRING); boolean assumeCommand = wrapper.read(Types.BOOLEAN); wrapper.read(Types.OPTIONAL_POSITION1_8); if (!assumeCommand && !command.startsWith("/")) { // Complete usernames for non-commands String buffer = command.substring(command.lastIndexOf(' ') + 1); for (String value : storage.usernames().values()) { if (startsWithIgnoreCase(value, buffer)) { suggestions.add(value); } } } else if (!storage.commands().isEmpty() && !command.contains(" ")) { // Complete commands names with values from 'Declare Commands' packet for (String value : storage.commands()) { if (startsWithIgnoreCase(value, command)) { suggestions.add(value); } } } if (!suggestions.isEmpty()) { wrapper.cancel(); PacketWrapper response = wrapper.create(ClientboundPackets1_12_1.COMMAND_SUGGESTIONS); response.write(Types.VAR_INT, suggestions.size()); for (String value : suggestions) { response.write(Types.STRING, value); } response.scheduleSend(Protocol1_13To1_12_2.class); storage.setLastRequest(null); return; } if (!assumeCommand && command.startsWith("/")) { command = command.substring(1); } int id = ThreadLocalRandom.current().nextInt(); wrapper.write(Types.VAR_INT, id); wrapper.write(Types.STRING, command); storage.setLastId(id); storage.setLastAssumeCommand(assumeCommand); storage.setLastRequest(command); }); protocol.registerServerbound(ServerboundPackets1_12_1.CUSTOM_PAYLOAD, wrapper -> { String channel = wrapper.read(Types.STRING); switch (channel) { case "MC|BSign", "MC|BEdit" -> { wrapper.setPacketType(ServerboundPackets1_13.EDIT_BOOK); Item book = wrapper.read(Types.ITEM1_8); wrapper.write(Types.ITEM1_13, protocol.getItemRewriter().handleItemToServer(wrapper.user(), book)); boolean signing = channel.equals("MC|BSign"); wrapper.write(Types.BOOLEAN, signing); } case "MC|ItemName" -> wrapper.setPacketType(ServerboundPackets1_13.RENAME_ITEM); case "MC|AdvCmd" -> { byte type = wrapper.read(Types.BYTE); if (type == 0) { //Information from https://wiki.vg/index.php?title=Plugin_channels&oldid=14089 (see web archive) //The Vanilla client only uses this for command block minecarts and uses MC|AutoCmd for blocks, but the server still accepts it for either. //Maybe older versions used this and we need to implement this? The issue is that we would have to save the command block types wrapper.setPacketType(ServerboundPackets1_13.SET_COMMAND_BLOCK); wrapper.cancel(); if (Via.getConfig().logOtherConversionWarnings()) { protocol.getLogger().warning("Client send MC|AdvCmd custom payload to update command block, weird!"); } } else if (type == 1) { wrapper.setPacketType(ServerboundPackets1_13.SET_COMMAND_MINECART); wrapper.write(Types.VAR_INT, wrapper.read(Types.INT)); //Entity Id wrapper.passthrough(Types.STRING); //Command wrapper.passthrough(Types.BOOLEAN); //Track Output } else { wrapper.cancel(); } } case "MC|AutoCmd" -> { wrapper.setPacketType(ServerboundPackets1_13.SET_COMMAND_BLOCK); int x = wrapper.read(Types.INT); int y = wrapper.read(Types.INT); int z = wrapper.read(Types.INT); wrapper.write(Types.BLOCK_POSITION1_8, new BlockPosition(x, (short) y, z)); wrapper.passthrough(Types.STRING); //Command byte flags = 0; if (wrapper.read(Types.BOOLEAN)) flags |= 0x01; //Track Output String mode = wrapper.read(Types.STRING); int modeId = mode.equals("SEQUENCE") ? 0 : mode.equals("AUTO") ? 1 : 2; wrapper.write(Types.VAR_INT, modeId); if (wrapper.read(Types.BOOLEAN)) flags |= 0x02; //Is conditional if (wrapper.read(Types.BOOLEAN)) flags |= 0x04; //Automatic wrapper.write(Types.BYTE, flags); } case "MC|Struct" -> { wrapper.setPacketType(ServerboundPackets1_13.SET_STRUCTURE_BLOCK); int x = wrapper.read(Types.INT); int y = wrapper.read(Types.INT); int z = wrapper.read(Types.INT); wrapper.write(Types.BLOCK_POSITION1_8, new BlockPosition(x, (short) y, z)); wrapper.write(Types.VAR_INT, wrapper.read(Types.BYTE) - 1); String mode = wrapper.read(Types.STRING); int modeId = mode.equals("SAVE") ? 0 : mode.equals("LOAD") ? 1 : mode.equals("CORNER") ? 2 : 3; wrapper.write(Types.VAR_INT, modeId); wrapper.passthrough(Types.STRING); //Name wrapper.write(Types.BYTE, wrapper.read(Types.INT).byteValue()); //Offset X wrapper.write(Types.BYTE, wrapper.read(Types.INT).byteValue()); //Offset Y wrapper.write(Types.BYTE, wrapper.read(Types.INT).byteValue()); //Offset Z wrapper.write(Types.BYTE, wrapper.read(Types.INT).byteValue()); //Size X wrapper.write(Types.BYTE, wrapper.read(Types.INT).byteValue()); //Size Y wrapper.write(Types.BYTE, wrapper.read(Types.INT).byteValue()); //Size Z String mirror = wrapper.read(Types.STRING); int mirrorId = mode.equals("NONE") ? 0 : mode.equals("LEFT_RIGHT") ? 1 : 2; String rotation = wrapper.read(Types.STRING); int rotationId = mode.equals("NONE") ? 0 : mode.equals("CLOCKWISE_90") ? 1 : mode.equals("CLOCKWISE_180") ? 2 : 3; wrapper.passthrough(Types.STRING); //Metadata byte flags = 0; if (wrapper.read(Types.BOOLEAN)) flags |= 0x01; //Ignore entities if (wrapper.read(Types.BOOLEAN)) flags |= 0x02; //Show air if (wrapper.read(Types.BOOLEAN)) flags |= 0x04; //Show bounding box wrapper.passthrough(Types.FLOAT); //Integrity wrapper.passthrough(Types.VAR_LONG); //Seed wrapper.write(Types.BYTE, flags); } case "MC|Beacon" -> { wrapper.setPacketType(ServerboundPackets1_13.SET_BEACON); wrapper.write(Types.VAR_INT, wrapper.read(Types.INT)); //Primary Effect wrapper.write(Types.VAR_INT, wrapper.read(Types.INT)); //Secondary Effect } case "MC|TrSel" -> { wrapper.setPacketType(ServerboundPackets1_13.SELECT_TRADE); wrapper.write(Types.VAR_INT, wrapper.read(Types.INT)); //Slot } case "MC|PickItem" -> wrapper.setPacketType(ServerboundPackets1_13.PICK_ITEM); default -> { String newChannel = ItemPacketRewriter1_13.getNewPluginChannelId(channel); if (newChannel == null) { if (Via.getConfig().logOtherConversionWarnings()) { protocol.getLogger().warning("Ignoring serverbound plugin message with channel: " + channel); } wrapper.cancel(); return; } wrapper.write(Types.STRING, newChannel); if (newChannel.equals("minecraft:register") || newChannel.equals("minecraft:unregister")) { String[] channels = new String(wrapper.read(Types.SERVERBOUND_CUSTOM_PAYLOAD_DATA), StandardCharsets.UTF_8).split("\0"); List rewrittenChannels = new ArrayList<>(); for (String s : channels) { String rewritten = ItemPacketRewriter1_13.getNewPluginChannelId(s); if (rewritten != null) { rewrittenChannels.add(rewritten); } else if (Via.getConfig().logOtherConversionWarnings()) { protocol.getLogger().warning("Ignoring plugin channel in serverbound " + Key.stripMinecraftNamespace(newChannel).toUpperCase(Locale.ROOT) + ": " + s); } } if (!rewrittenChannels.isEmpty()) { wrapper.write(Types.SERVERBOUND_CUSTOM_PAYLOAD_DATA, Joiner.on('\0').join(rewrittenChannels).getBytes(StandardCharsets.UTF_8)); } else { wrapper.cancel(); } } } } }); protocol.replaceClientbound(ClientboundPackets1_13.AWARD_STATS, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); handler(wrapper -> { int size = wrapper.get(Types.VAR_INT, 0); int newSize = size; for (int i = 0; i < size; i++) { int categoryId = wrapper.read(Types.VAR_INT); int statisticId = wrapper.read(Types.VAR_INT); String name = ""; // categories 0-7 (items, blocks, entities) - probably not feasible switch (categoryId) { case 0, 1, 2, 3, 4, 5, 6, 7 -> { wrapper.read(Types.VAR_INT); // remove value newSize--; continue; } case 8 -> { name = protocol.getMappingData().getStatisticMappings().get(statisticId); if (name == null) { wrapper.read(Types.VAR_INT); newSize--; continue; } } } wrapper.write(Types.STRING, name); // string id wrapper.passthrough(Types.VAR_INT); // value } if (newSize != size) { wrapper.set(Types.VAR_INT, 0, newSize); } }); } }); } private static boolean startsWithIgnoreCase(String string, String prefix) { if (string.length() < prefix.length()) { return false; } return string.regionMatches(true, 0, prefix, 0, prefix.length()); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/rewriter/SoundPacketRewriter1_13.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.rewriter; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.Protocol1_13To1_12_2; import com.viaversion.viabackwards.protocol.v1_13to1_12_2.data.NamedSoundMappings1_12_2; import com.viaversion.viaversion.api.rewriter.RewriterBase; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12to1_12_1.packet.ClientboundPackets1_12_1; public class SoundPacketRewriter1_13 extends RewriterBase { private static final String[] SOUND_SOURCES = {"master", "music", "record", "weather", "block", "hostile", "neutral", "player", "ambient", "voice"}; public SoundPacketRewriter1_13(Protocol1_13To1_12_2 protocol) { super(protocol); } @Override protected void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_13.CUSTOM_SOUND, wrapper -> { String sound = wrapper.read(Types.STRING); String mappedSound = NamedSoundMappings1_12_2.getOldId(sound); if (mappedSound != null || (mappedSound = protocol.getMappingData().getMappedNamedSound(sound)) != null) { wrapper.write(Types.STRING, mappedSound); } else { wrapper.write(Types.STRING, sound); } }); // Stop Sound -> Plugin Message protocol.registerClientbound(ClientboundPackets1_13.STOP_SOUND, ClientboundPackets1_12_1.CUSTOM_PAYLOAD, wrapper -> { wrapper.write(Types.STRING, "MC|StopSound"); byte flags = wrapper.read(Types.BYTE); String source; if ((flags & 0x01) != 0) { source = SOUND_SOURCES[wrapper.read(Types.VAR_INT)]; } else { source = ""; } String sound; if ((flags & 0x02) != 0) { String newSound = wrapper.read(Types.STRING); sound = protocol.getMappingData().getMappedNamedSound(newSound); if (sound == null) { sound = ""; } } else { sound = ""; } wrapper.write(Types.STRING, source); wrapper.write(Types.STRING, sound); }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/storage/BackwardsBlockStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.libs.fastutil.ints.IntOpenHashSet; import com.viaversion.viaversion.libs.fastutil.ints.IntSet; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.checkerframework.checker.nullness.qual.Nullable; public class BackwardsBlockStorage implements StorableObject { // This BlockStorage is very exclusive (; private static final IntSet WHITELIST = new IntOpenHashSet(779); static { // Flower pots for (int i = 5265; i <= 5286; i++) { WHITELIST.add(i); } // Add those beds for (int i = 0; i < (16 * 16); i++) { WHITELIST.add(748 + i); } // Add the banners for (int i = 6854; i <= 7173; i++) { WHITELIST.add(i); } // Spawner WHITELIST.add(1647); // Skulls for (int i = 5447; i <= 5566; i++) { WHITELIST.add(i); } // pistons for (int i = 1028; i <= 1039; i++) { WHITELIST.add(i); } for (int i = 1047; i <= 1082; i++) { WHITELIST.add(i); } for (int i = 1099; i <= 1110; i++) { WHITELIST.add(i); } } private final Map blocks = new ConcurrentHashMap<>(); public void checkAndStore(BlockPosition position, int block) { if (!WHITELIST.contains(block)) { // Remove if not whitelisted blocks.remove(position); return; } blocks.put(position, block); } public @Nullable Integer get(BlockPosition position) { return blocks.get(position); } public int remove(BlockPosition position) { return blocks.remove(position); } public void clear() { blocks.clear(); } public Map getBlocks() { return blocks; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/storage/NoteBlockStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.libs.fastutil.objects.Object2IntMap; import com.viaversion.viaversion.libs.fastutil.objects.Object2IntOpenHashMap; import com.viaversion.viaversion.util.Pair; public class NoteBlockStorage implements StorableObject { private static final int MAX_NOTE_ID = 24; private final Object2IntMap noteBlockUpdates = new Object2IntOpenHashMap<>(); public void storeNoteBlockUpdate(final BlockPosition position, final int blockStateId) { noteBlockUpdates.put(position, blockStateId); } public Pair getNoteBlockUpdate(final BlockPosition position) { if (!noteBlockUpdates.containsKey(position)) { return null; } int relativeBlockState = noteBlockUpdates.removeInt(position) - 249; relativeBlockState = relativeBlockState / 2; // Get rid of powered state return new Pair<>(relativeBlockState / MAX_NOTE_ID + 1, relativeBlockState % MAX_NOTE_ID + 1); } public void clear() { noteBlockUpdates.clear(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/storage/PlayerPositionStorage1_13.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage; import com.viaversion.viabackwards.api.entities.storage.PlayerPositionStorage; public class PlayerPositionStorage1_13 extends PlayerPositionStorage { } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/storage/TabCompleteStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_13to1_12_2.storage; import com.viaversion.viaversion.api.connection.StorableObject; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; public class TabCompleteStorage implements StorableObject { private final Map usernames = new HashMap<>(); private final Set commands = new HashSet<>(); private int lastId; private String lastRequest; private boolean lastAssumeCommand; public Map usernames() { return usernames; } public Set commands() { return commands; } public int lastId() { return lastId; } public void setLastId(final int lastId) { this.lastId = lastId; } public String lastRequest() { return lastRequest; } public void setLastRequest(String lastRequest) { this.lastRequest = lastRequest; } public boolean isLastAssumeCommand() { return lastAssumeCommand; } public void setLastAssumeCommand(boolean lastAssumeCommand) { this.lastAssumeCommand = lastAssumeCommand; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14_1to1_14/Protocol1_14_1To1_14.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14_1to1_14; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.protocol.v1_14_1to1_14.rewriter.EntityPacketRewriter1_14_1; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_15; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ClientboundPackets1_14; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ServerboundPackets1_14; public class Protocol1_14_1To1_14 extends BackwardsProtocol { private final EntityPacketRewriter1_14_1 entityRewriter = new EntityPacketRewriter1_14_1(this); public Protocol1_14_1To1_14() { super(ClientboundPackets1_14.class, ClientboundPackets1_14.class, ServerboundPackets1_14.class, ServerboundPackets1_14.class); } @Override protected void registerPackets() { entityRewriter.register(); } @Override public void init(UserConnection user) { user.addEntityTracker(this.getClass(), new EntityTrackerBase(user, EntityTypes1_15.PLAYER)); user.addClientWorld(this.getClass(), new ClientWorld()); } @Override public EntityPacketRewriter1_14_1 getEntityRewriter() { return entityRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14_1to1_14/rewriter/EntityPacketRewriter1_14_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14_1to1_14.rewriter; import com.viaversion.viabackwards.api.rewriters.LegacyEntityRewriter; import com.viaversion.viabackwards.protocol.v1_14_1to1_14.Protocol1_14_1To1_14; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_14; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_14; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ClientboundPackets1_14; import java.util.List; public class EntityPacketRewriter1_14_1 extends LegacyEntityRewriter { public EntityPacketRewriter1_14_1(Protocol1_14_1To1_14 protocol) { super(protocol); } @Override protected void registerPackets() { registerTracker(ClientboundPackets1_14.ADD_EXPERIENCE_ORB, EntityTypes1_14.EXPERIENCE_ORB); registerTracker(ClientboundPackets1_14.ADD_GLOBAL_ENTITY, EntityTypes1_14.LIGHTNING_BOLT); registerTracker(ClientboundPackets1_14.ADD_PAINTING, EntityTypes1_14.PAINTING); registerTracker(ClientboundPackets1_14.ADD_PLAYER, EntityTypes1_14.PLAYER); registerJoinGame(ClientboundPackets1_14.LOGIN, EntityTypes1_14.PLAYER); registerRespawn(ClientboundPackets1_14.RESPAWN); protocol.registerClientbound(ClientboundPackets1_14.ADD_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.UUID); // 1 - UUID map(Types.VAR_INT); // 2 - Type handler(getTrackerHandler()); } }); protocol.registerClientbound(ClientboundPackets1_14.ADD_MOB, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.UUID); // 1 - Entity UUID map(Types.VAR_INT); // 2 - Entity Type map(Types.DOUBLE); // 3 - X map(Types.DOUBLE); // 4 - Y map(Types.DOUBLE); // 5 - Z map(Types.BYTE); // 6 - Yaw map(Types.BYTE); // 7 - Pitch map(Types.BYTE); // 8 - Head Pitch map(Types.SHORT); // 9 - Velocity X map(Types.SHORT); // 10 - Velocity Y map(Types.SHORT); // 11 - Velocity Z map(Types1_14.ENTITY_DATA_LIST); // 12 - Entity data handler(wrapper -> { int entityId = wrapper.get(Types.VAR_INT, 0); int type = wrapper.get(Types.VAR_INT, 1); // Register Type ID tracker(wrapper.user()).addEntity(entityId, EntityTypes1_14.getTypeFromId(type)); List entityDataList = wrapper.get(Types1_14.ENTITY_DATA_LIST, 0); handleEntityData(entityId, entityDataList, wrapper.user()); }); } }); // Entity data registerSetEntityData(ClientboundPackets1_14.SET_ENTITY_DATA, Types1_14.ENTITY_DATA_LIST); } @Override protected void registerRewrites() { filter().type(EntityTypes1_14.VILLAGER).cancel(15); filter().type(EntityTypes1_14.VILLAGER).index(16).toIndex(15); filter().type(EntityTypes1_14.WANDERING_TRADER).cancel(15); } @Override public EntityType typeFromId(int typeId) { return EntityTypes1_14.getTypeFromId(typeId); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14_2to1_14_1/Protocol1_14_2To1_14_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14_2to1_14_1; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ClientboundPackets1_14; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ServerboundPackets1_14; public class Protocol1_14_2To1_14_1 extends BackwardsProtocol { public Protocol1_14_2To1_14_1() { super(ClientboundPackets1_14.class, ClientboundPackets1_14.class, ServerboundPackets1_14.class, ServerboundPackets1_14.class); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14_3to1_14_2/Protocol1_14_3To1_14_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14_3to1_14_2; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ClientboundPackets1_14; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ServerboundPackets1_14; import com.viaversion.viaversion.rewriter.RecipeRewriter; import com.viaversion.viaversion.util.Key; public class Protocol1_14_3To1_14_2 extends BackwardsProtocol { public Protocol1_14_3To1_14_2() { super(ClientboundPackets1_14.class, ClientboundPackets1_14.class, ServerboundPackets1_14.class, ServerboundPackets1_14.class); } @Override protected void registerPackets() { registerClientbound(ClientboundPackets1_14.MERCHANT_OFFERS, wrapper -> { wrapper.passthrough(Types.VAR_INT); int size = wrapper.passthrough(Types.UNSIGNED_BYTE); for (int i = 0; i < size; i++) { wrapper.passthrough(Types.ITEM1_13_2); wrapper.passthrough(Types.ITEM1_13_2); if (wrapper.passthrough(Types.BOOLEAN)) { wrapper.passthrough(Types.ITEM1_13_2); } wrapper.passthrough(Types.BOOLEAN); wrapper.passthrough(Types.INT); wrapper.passthrough(Types.INT); wrapper.passthrough(Types.INT); wrapper.passthrough(Types.INT); wrapper.passthrough(Types.FLOAT); } wrapper.passthrough(Types.VAR_INT); wrapper.passthrough(Types.VAR_INT); wrapper.passthrough(Types.BOOLEAN); wrapper.read(Types.BOOLEAN); }); RecipeRewriter recipeHandler = new RecipeRewriter<>(this); registerClientbound(ClientboundPackets1_14.UPDATE_RECIPES, wrapper -> { int size = wrapper.passthrough(Types.VAR_INT); int deleted = 0; for (int i = 0; i < size; i++) { String fullType = wrapper.read(Types.STRING); String type = Key.stripMinecraftNamespace(fullType); String id = wrapper.read(Types.STRING); // id if (type.equals("crafting_special_repairitem")) { deleted++; continue; } wrapper.write(Types.STRING, fullType); wrapper.write(Types.STRING, id); recipeHandler.handleRecipeType(wrapper, type); } wrapper.set(Types.VAR_INT, 0, size - deleted); }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14_4to1_14_3/Protocol1_14_4To1_14_3.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14_4to1_14_3; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ClientboundPackets1_14; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ServerboundPackets1_14; import com.viaversion.viaversion.protocols.v1_14_3to1_14_4.packet.ClientboundPackets1_14_4; public class Protocol1_14_4To1_14_3 extends BackwardsProtocol { public Protocol1_14_4To1_14_3() { super(ClientboundPackets1_14_4.class, ClientboundPackets1_14.class, ServerboundPackets1_14.class, ServerboundPackets1_14.class); } @Override protected void registerPackets() { // Acknowledge Player Digging - added in pre4 registerClientbound(ClientboundPackets1_14_4.BLOCK_BREAK_ACK, ClientboundPackets1_14.BLOCK_UPDATE, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_14); map(Types.VAR_INT); handler(wrapper -> { int status = wrapper.read(Types.VAR_INT); boolean allGood = wrapper.read(Types.BOOLEAN); if (allGood && status == 0) { wrapper.cancel(); } }); } }); registerClientbound(ClientboundPackets1_14_4.MERCHANT_OFFERS, wrapper -> { wrapper.passthrough(Types.VAR_INT); int size = wrapper.passthrough(Types.UNSIGNED_BYTE); for (int i = 0; i < size; i++) { wrapper.passthrough(Types.ITEM1_13_2); wrapper.passthrough(Types.ITEM1_13_2); if (wrapper.passthrough(Types.BOOLEAN)) { wrapper.passthrough(Types.ITEM1_13_2); } wrapper.passthrough(Types.BOOLEAN); wrapper.passthrough(Types.INT); wrapper.passthrough(Types.INT); wrapper.passthrough(Types.INT); wrapper.passthrough(Types.INT); wrapper.passthrough(Types.FLOAT); wrapper.read(Types.INT); // demand value added in pre-5 } }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14to1_13_2/Protocol1_14To1_13_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14to1_13_2; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.data.BackwardsMappingData1_14; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.rewriter.BlockItemPacketRewriter1_14; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.rewriter.CommandRewriter1_14; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.rewriter.EntityPacketRewriter1_14; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.rewriter.PlayerPacketRewriter1_14; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.rewriter.SoundPacketRewriter1_14; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.storage.ChunkLightStorage; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.storage.DifficultyStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_14; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ServerboundPackets1_13; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ClientboundPackets1_14; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ServerboundPackets1_14; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; public class Protocol1_14To1_13_2 extends BackwardsProtocol { public static final BackwardsMappingData1_14 MAPPINGS = new BackwardsMappingData1_14(); private final EntityPacketRewriter1_14 entityRewriter = new EntityPacketRewriter1_14(this); private final BlockItemPacketRewriter1_14 itemRewriter = new BlockItemPacketRewriter1_14(this); private final BlockRewriter blockRewriter = BlockRewriter.legacy(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final JsonNBTComponentRewriter translatableRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); public Protocol1_14To1_13_2() { super(ClientboundPackets1_14.class, ClientboundPackets1_13.class, ServerboundPackets1_14.class, ServerboundPackets1_13.class); } @Override protected void registerPackets() { super.registerPackets(); new CommandRewriter1_14(this).registerDeclareCommands(ClientboundPackets1_14.COMMANDS); new PlayerPacketRewriter1_14(this).register(); new SoundPacketRewriter1_14(this).register(); cancelClientbound(ClientboundPackets1_14.SET_CHUNK_CACHE_CENTER); cancelClientbound(ClientboundPackets1_14.SET_CHUNK_CACHE_RADIUS); registerClientbound(ClientboundPackets1_14.UPDATE_TAGS, wrapper -> { int blockTagsSize = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < blockTagsSize; i++) { wrapper.passthrough(Types.STRING); int[] blockIds = wrapper.passthrough(Types.VAR_INT_ARRAY_PRIMITIVE); for (int j = 0; j < blockIds.length; j++) { int id = blockIds[j]; // Ignore new blocktags int blockId = MAPPINGS.getNewBlockId(id); blockIds[j] = blockId; } } int itemTagsSize = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < itemTagsSize; i++) { wrapper.passthrough(Types.STRING); int[] itemIds = wrapper.passthrough(Types.VAR_INT_ARRAY_PRIMITIVE); for (int j = 0; j < itemIds.length; j++) { int itemId = itemIds[j]; // Ignore new itemtags int oldId = MAPPINGS.getItemMappings().getNewId(itemId); itemIds[j] = oldId; } } int fluidTagsSize = wrapper.passthrough(Types.VAR_INT); // fluid tags for (int i = 0; i < fluidTagsSize; i++) { wrapper.passthrough(Types.STRING); wrapper.passthrough(Types.VAR_INT_ARRAY_PRIMITIVE); } // Eat entity tags int entityTagsSize = wrapper.read(Types.VAR_INT); for (int i = 0; i < entityTagsSize; i++) { wrapper.read(Types.STRING); wrapper.read(Types.VAR_INT_ARRAY_PRIMITIVE); } }); registerClientbound(ClientboundPackets1_14.LIGHT_UPDATE, null, wrapper -> { int x = wrapper.read(Types.VAR_INT); int z = wrapper.read(Types.VAR_INT); int skyLightMask = wrapper.read(Types.VAR_INT); int blockLightMask = wrapper.read(Types.VAR_INT); int emptySkyLightMask = wrapper.read(Types.VAR_INT); int emptyBlockLightMask = wrapper.read(Types.VAR_INT); byte[][] skyLight = new byte[16][]; // we don't need void and +256 light if (isSet(skyLightMask, 0)) { wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); } for (int i = 0; i < 16; i++) { if (isSet(skyLightMask, i + 1)) { skyLight[i] = wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); } else if (isSet(emptySkyLightMask, i + 1)) { skyLight[i] = ChunkLightStorage.EMPTY_LIGHT; } } if (isSet(skyLightMask, 17)) { wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); } byte[][] blockLight = new byte[16][]; if (isSet(blockLightMask, 0)) { wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); } for (int i = 0; i < 16; i++) { if (isSet(blockLightMask, i + 1)) { blockLight[i] = wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); } else if (isSet(emptyBlockLightMask, i + 1)) { blockLight[i] = ChunkLightStorage.EMPTY_LIGHT; } } if (isSet(blockLightMask, 17)) { wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); } //TODO Soft memory leak: Don't store light if chunk is already loaded wrapper.user().get(ChunkLightStorage.class).setStoredLight(skyLight, blockLight, x, z); wrapper.cancel(); }); } private static boolean isSet(int mask, int i) { return (mask & (1 << i)) != 0; } @Override public void init(UserConnection user) { user.addEntityTracker(this.getClass(), new EntityTrackerBase(user, EntityTypes1_14.PLAYER)); user.addClientWorld(this.getClass(), new ClientWorld()); if (!user.has(ChunkLightStorage.class)) { user.put(new ChunkLightStorage()); } user.put(new DifficultyStorage()); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_14 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_14 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14to1_13_2/data/BackwardsMappingData1_14.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14to1_13_2.data; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viaversion.protocols.v1_13_2to1_14.Protocol1_13_2To1_14; public final class BackwardsMappingData1_14 extends BackwardsMappingData { public BackwardsMappingData1_14() { super("1.14", "1.13.2", Protocol1_13_2To1_14.class); } @Override protected void loadExtras(final CompoundTag data) { super.loadExtras(data); if (ViaBackwards.getConfig().scaffoldingToWater()) { for (int i = 11099; i <= 11130; i++) { blockStateMappings.setNewId(i, 49); } } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14to1_13_2/rewriter/BlockItemPacketRewriter1_14.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14to1_13_2.rewriter; import com.google.common.collect.ImmutableSet; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.api.data.TranslatableMappings; import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.api.rewriters.EnchantmentRewriter; import com.viaversion.viabackwards.item.DataItemWithExtras; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.Protocol1_14To1_13_2; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.storage.ChunkLightStorage; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.Environment; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSectionLight; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSectionLightImpl; import com.viaversion.viaversion.api.minecraft.chunks.DataPalette; import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_14; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.item.DataItem; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_13; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_14; import com.viaversion.viaversion.api.type.types.version.Types1_13; import com.viaversion.viaversion.api.type.types.version.Types1_13_2; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.libs.gson.JsonObject; import com.viaversion.viaversion.libs.gson.JsonParseException; import com.viaversion.viaversion.libs.mcstructs.text.utils.TextUtils; import com.viaversion.viaversion.protocols.v1_12_2to1_13.Protocol1_12_2To1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ServerboundPackets1_13; import com.viaversion.viaversion.protocols.v1_13_2to1_14.Protocol1_13_2To1_14; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ClientboundPackets1_14; import com.viaversion.viaversion.rewriter.RecipeRewriter; import com.viaversion.viaversion.util.ComponentUtil; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.SerializerVersion; import java.util.ArrayList; import java.util.List; import java.util.Set; public class BlockItemPacketRewriter1_14 extends BackwardsItemRewriter { private EnchantmentRewriter enchantmentRewriter; public BlockItemPacketRewriter1_14(Protocol1_14To1_13_2 protocol) { super(protocol, Types.ITEM1_13_2, Types.ITEM1_13_2_SHORT_ARRAY); } @Override protected void registerPackets() { protocol.registerServerbound(ServerboundPackets1_13.EDIT_BOOK, wrapper -> handleItemToServer(wrapper.user(), wrapper.passthrough(Types.ITEM1_13_2))); protocol.replaceClientbound(ClientboundPackets1_14.OPEN_SCREEN, wrapper -> { int windowId = wrapper.read(Types.VAR_INT); wrapper.write(Types.UNSIGNED_BYTE, (short) windowId); int type = wrapper.read(Types.VAR_INT); String stringType = null; String containerTitle = null; int slotSize = 0; if (type < 6) { if (type == 2) containerTitle = "Barrel"; stringType = "minecraft:container"; slotSize = (type + 1) * 9; } else { switch (type) { case 11 -> stringType = "minecraft:crafting_table"; case 9, 20, 13, 14 -> { if (type == 9) containerTitle = "Blast Furnace"; else if (type == 20) containerTitle = "Smoker"; else if (type == 14) containerTitle = "Grindstone"; stringType = "minecraft:furnace"; slotSize = 3; } case 6 -> { stringType = "minecraft:dropper"; slotSize = 9; } case 12 -> stringType = "minecraft:enchanting_table"; case 10 -> { stringType = "minecraft:brewing_stand"; slotSize = 5; } case 18 -> stringType = "minecraft:villager"; case 8 -> { stringType = "minecraft:beacon"; slotSize = 1; } case 21, 7 -> { if (type == 21) containerTitle = "Cartography Table"; stringType = "minecraft:anvil"; } case 15 -> { stringType = "minecraft:hopper"; slotSize = 5; } case 19 -> { stringType = "minecraft:shulker_box"; slotSize = 27; } } } if (stringType == null) { protocol.getLogger().warning("Can't open inventory for player! Type: " + type); wrapper.cancel(); return; } wrapper.write(Types.STRING, stringType); JsonElement title = wrapper.read(Types.COMPONENT); if (containerTitle != null) { // Don't rewrite renamed, only translatable titles JsonObject object; if (title.isJsonObject() && (object = title.getAsJsonObject()).has("translate")) { // Don't rewrite other 9x3 translatable containers if (type != 2 || object.getAsJsonPrimitive("translate").getAsString().equals("container.barrel")) { title = ComponentUtil.legacyToJson(containerTitle); } } } wrapper.write(Types.COMPONENT, title); wrapper.write(Types.UNSIGNED_BYTE, (short) slotSize); }); // Horse window -> Open Window protocol.registerClientbound(ClientboundPackets1_14.HORSE_SCREEN_OPEN, ClientboundPackets1_13.OPEN_SCREEN, wrapper -> { wrapper.passthrough(Types.UNSIGNED_BYTE); // Window id wrapper.write(Types.STRING, "EntityHorse"); // Type JsonObject object = new JsonObject(); object.addProperty("translate", "minecraft.horse"); wrapper.write(Types.COMPONENT, object); // Title wrapper.write(Types.UNSIGNED_BYTE, wrapper.read(Types.VAR_INT).shortValue()); // Number of slots wrapper.passthrough(Types.INT); // Entity id }); // Trade List -> Plugin Message protocol.registerClientbound(ClientboundPackets1_14.MERCHANT_OFFERS, ClientboundPackets1_13.CUSTOM_PAYLOAD, wrapper -> { wrapper.write(Types.STRING, "minecraft:trader_list"); int windowId = wrapper.read(Types.VAR_INT); wrapper.write(Types.INT, windowId); int size = wrapper.passthrough(Types.UNSIGNED_BYTE); for (int i = 0; i < size; i++) { // Input Item Item input = wrapper.read(Types.ITEM1_13_2); input = handleItemToClient(wrapper.user(), input); wrapper.write(Types.ITEM1_13_2, input); // Output Item Item output = wrapper.read(Types.ITEM1_13_2); output = handleItemToClient(wrapper.user(), output); wrapper.write(Types.ITEM1_13_2, output); boolean secondItem = wrapper.passthrough(Types.BOOLEAN); // Has second item if (secondItem) { // Second Item Item second = wrapper.read(Types.ITEM1_13_2); second = handleItemToClient(wrapper.user(), second); wrapper.write(Types.ITEM1_13_2, second); } wrapper.passthrough(Types.BOOLEAN); // Trade disabled wrapper.passthrough(Types.INT); // Number of tools uses wrapper.passthrough(Types.INT); // Maximum number of trade uses wrapper.read(Types.INT); wrapper.read(Types.INT); wrapper.read(Types.FLOAT); } wrapper.read(Types.VAR_INT); wrapper.read(Types.VAR_INT); wrapper.read(Types.BOOLEAN); }, true); // Open Book -> Plugin Message protocol.registerClientbound(ClientboundPackets1_14.OPEN_BOOK, ClientboundPackets1_13.CUSTOM_PAYLOAD, wrapper -> { wrapper.write(Types.STRING, "minecraft:book_open"); wrapper.passthrough(Types.VAR_INT); }); protocol.replaceClientbound(ClientboundPackets1_14.SET_EQUIPPED_ITEM, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.VAR_INT); // 1 - Slot ID handler(wrapper -> passthroughClientboundItem(wrapper)); handler(wrapper -> { int entityId = wrapper.get(Types.VAR_INT, 0); EntityType entityType = wrapper.user().getEntityTracker(Protocol1_14To1_13_2.class).entityType(entityId); if (entityType == null) return; if (entityType.isOrHasParent(EntityTypes1_14.ABSTRACT_HORSE)) { wrapper.setPacketType(ClientboundPackets1_13.SET_ENTITY_DATA); wrapper.resetReader(); wrapper.passthrough(Types.VAR_INT); wrapper.read(Types.VAR_INT); Item item = wrapper.read(Types.ITEM1_13_2); int armorType = item == null || item.identifier() == 0 ? 0 : item.identifier() - 726; if (armorType < 0 || armorType > 3) { wrapper.cancel(); return; } List entityDataList = new ArrayList<>(); entityDataList.add(new EntityData(16, Types1_13_2.ENTITY_DATA_TYPES.varIntType, armorType)); wrapper.write(Types1_13.ENTITY_DATA_LIST, entityDataList); } }); } }); RecipeRewriter recipeHandler = new RecipeRewriter<>(protocol); final Set removedTypes = ImmutableSet.of("crafting_special_suspiciousstew", "blasting", "smoking", "campfire_cooking", "stonecutting"); protocol.registerClientbound(ClientboundPackets1_14.UPDATE_RECIPES, wrapper -> { int size = wrapper.passthrough(Types.VAR_INT); int deleted = 0; for (int i = 0; i < size; i++) { String type = wrapper.read(Types.STRING); String id = wrapper.read(Types.STRING); // Recipe Identifier type = Key.stripMinecraftNamespace(type); if (removedTypes.contains(type)) { switch (type) { case "blasting", "smoking", "campfire_cooking" -> { wrapper.read(Types.STRING); // Group wrapper.read(Types.ITEM1_13_2_ARRAY); // Ingredients wrapper.read(Types.ITEM1_13_2); wrapper.read(Types.FLOAT); // EXP wrapper.read(Types.VAR_INT); // Cooking time } case "stonecutting" -> { wrapper.read(Types.STRING); // Group? wrapper.read(Types.ITEM1_13_2_ARRAY); // Ingredients wrapper.read(Types.ITEM1_13_2); // Result } } deleted++; continue; } wrapper.write(Types.STRING, id); wrapper.write(Types.STRING, type); // Handle the rest of the types recipeHandler.handleRecipeType(wrapper, type); } wrapper.set(Types.VAR_INT, 0, size - deleted); }); protocol.registerClientbound(ClientboundPackets1_14.BLOCK_DESTRUCTION, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.BLOCK_POSITION1_14, Types.BLOCK_POSITION1_8); map(Types.BYTE); } }); protocol.registerClientbound(ClientboundPackets1_14.BLOCK_ENTITY_DATA, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_14, Types.BLOCK_POSITION1_8); } }); protocol.replaceClientbound(ClientboundPackets1_14.BLOCK_EVENT, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_14, Types.BLOCK_POSITION1_8); // Location map(Types.UNSIGNED_BYTE); // Action id map(Types.UNSIGNED_BYTE); // Action param map(Types.VAR_INT); // Block id - /!\ NOT BLOCK STATE handler(wrapper -> { int mappedId = protocol.getMappingData().getNewBlockId(wrapper.get(Types.VAR_INT, 0)); if (mappedId == -1) { wrapper.cancel(); return; } wrapper.set(Types.VAR_INT, 0, mappedId); }); } }); protocol.replaceClientbound(ClientboundPackets1_14.BLOCK_UPDATE, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_14, Types.BLOCK_POSITION1_8); map(Types.VAR_INT); handler(wrapper -> { int id = wrapper.get(Types.VAR_INT, 0); wrapper.set(Types.VAR_INT, 0, protocol.getMappingData().getNewBlockStateId(id)); }); } }); protocol.registerClientbound(ClientboundPackets1_14.EXPLODE, new PacketHandlers() { @Override public void register() { map(Types.FLOAT); // X map(Types.FLOAT); // Y map(Types.FLOAT); // Z map(Types.FLOAT); // Radius handler(wrapper -> { for (int i = 0; i < 3; i++) { float coord = wrapper.get(Types.FLOAT, i); if (coord < 0f) { coord = (float) Math.floor(coord); wrapper.set(Types.FLOAT, i, coord); } } }); } }); protocol.registerClientbound(ClientboundPackets1_14.LEVEL_CHUNK, wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_14To1_13_2.class); Chunk chunk = wrapper.read(ChunkType1_14.TYPE); wrapper.write(ChunkType1_13.forEnvironment(clientWorld.getEnvironment()), chunk); ChunkLightStorage.ChunkLight chunkLight = wrapper.user().get(ChunkLightStorage.class).getStoredLight(chunk.getX(), chunk.getZ()); for (int i = 0; i < chunk.getSections().length; i++) { ChunkSection section = chunk.getSections()[i]; if (section == null) continue; ChunkSectionLight sectionLight = ChunkSectionLightImpl.createWithBlockLight(); section.setLight(sectionLight); if (chunkLight == null) { sectionLight.setBlockLight(ChunkLightStorage.FULL_LIGHT); if (clientWorld.getEnvironment() == Environment.NORMAL) { sectionLight.setSkyLight(ChunkLightStorage.FULL_LIGHT); } } else { byte[] blockLight = chunkLight.blockLight()[i]; sectionLight.setBlockLight(blockLight != null ? blockLight : ChunkLightStorage.FULL_LIGHT); if (clientWorld.getEnvironment() == Environment.NORMAL) { byte[] skyLight = chunkLight.skyLight()[i]; sectionLight.setSkyLight(skyLight != null ? skyLight : ChunkLightStorage.FULL_LIGHT); } } DataPalette palette = section.palette(PaletteType.BLOCKS); if (Via.getConfig().isNonFullBlockLightFix() && section.getNonAirBlocksCount() != 0 && sectionLight.hasBlockLight()) { for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { int id = palette.idAt(x, y, z); if (Protocol1_13_2To1_14.MAPPINGS.getNonFullBlocks().contains(id)) { sectionLight.getBlockLightNibbleArray().set(x, y, z, 0); } } } } } for (int j = 0; j < palette.size(); j++) { int mappedBlockStateId = protocol.getMappingData().getNewBlockStateId(palette.idByIndex(j)); palette.setIdByIndex(j, mappedBlockStateId); } } }); protocol.registerClientbound(ClientboundPackets1_14.FORGET_LEVEL_CHUNK, wrapper -> { int x = wrapper.passthrough(Types.INT); int z = wrapper.passthrough(Types.INT); wrapper.user().get(ChunkLightStorage.class).unloadChunk(x, z); }); protocol.replaceClientbound(ClientboundPackets1_14.LEVEL_EVENT, new PacketHandlers() { @Override public void register() { map(Types.INT); // Effect Id map(Types.BLOCK_POSITION1_14, Types.BLOCK_POSITION1_8); // Location map(Types.INT); // Data handler(wrapper -> { int id = wrapper.get(Types.INT, 0); int data = wrapper.get(Types.INT, 1); if (id == 1010) { // Play record wrapper.set(Types.INT, 1, protocol.getMappingData().getNewItemId(data)); } else if (id == 2001) { // Block break + block break sound wrapper.set(Types.INT, 1, protocol.getMappingData().getNewBlockStateId(data)); } }); } }); protocol.registerClientbound(ClientboundPackets1_14.MAP_ITEM_DATA, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.BYTE); map(Types.BOOLEAN); read(Types.BOOLEAN); // Locked } }); protocol.registerClientbound(ClientboundPackets1_14.SET_DEFAULT_SPAWN_POSITION, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_14, Types.BLOCK_POSITION1_8); } }); } @Override protected void registerRewrites() { enchantmentRewriter = new EnchantmentRewriter(this, false); enchantmentRewriter.registerEnchantment("minecraft:multishot", "§7Multishot"); enchantmentRewriter.registerEnchantment("minecraft:quick_charge", "§7Quick Charge"); enchantmentRewriter.registerEnchantment("minecraft:piercing", "§7Piercing"); } @Override public Item handleItemToClient(UserConnection connection, Item item) { if (item == null) return null; item = super.handleItemToClient(connection, item); // Lore now uses JSON final CompoundTag display = item.tag() != null ? item.tag().getCompoundTag("display") : null; if (display != null && item instanceof DataItemWithExtras fullItem && fullItem.lore() != null) { final List lore = fullItem.lore(); final ListTag loreTag = fullItem.rawLore(); saveListTag(display, loreTag, "Lore"); try { for (int i = 0; i < lore.size(); i++) { final JsonElement loreEntry = lore.get(i); final var component = SerializerVersion.V1_12.toComponent(loreEntry); if (component == null) { lore.remove(i); loreTag.remove(i); i--; continue; } TextUtils.setTranslator(component, s -> Protocol1_12_2To1_13.MAPPINGS.getMojangTranslation() .getOrDefault(s, TranslatableMappings.getTranslatableMappings("1.14").get(s))); loreTag.get(i).setValue(component.asLegacyFormatString()); } } catch (final JsonParseException e) { display.remove("Lore"); } } if (item instanceof DataItemWithExtras) { item = new DataItem(item.identifier(), (byte) item.amount(), item.data(), item.tag()); // back to a normal DataItem } enchantmentRewriter.handleToClient(item); return item; } @Override public Item handleItemToServer(UserConnection connection, Item item) { if (item == null) return null; // Lore now uses JSON CompoundTag tag = item.tag(); CompoundTag display; if (tag != null && (display = tag.getCompoundTag("display")) != null) { // Transform to json if no backup tag is found (else process that in the super method) ListTag lore = display.getListTag("Lore", StringTag.class); if (lore != null && !hasBackupTag(display, "Lore")) { for (StringTag loreEntry : lore) { loreEntry.setValue(ComponentUtil.legacyToJsonString(loreEntry.getValue())); } } } enchantmentRewriter.handleToServer(item); // Call this last to check for the backup lore above item = super.handleItemToServer(connection, item); return item; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14to1_13_2/rewriter/CommandRewriter1_14.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14to1_13_2.rewriter; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.Protocol1_14To1_13_2; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ClientboundPackets1_14; import com.viaversion.viaversion.rewriter.CommandRewriter; import org.checkerframework.checker.nullness.qual.Nullable; public class CommandRewriter1_14 extends CommandRewriter { public CommandRewriter1_14(Protocol1_14To1_13_2 protocol) { super(protocol); this.parserHandlers.put("minecraft:nbt_tag", wrapper -> wrapper.write(Types.VAR_INT, 2)); // Greedy phrase this.parserHandlers.put("minecraft:time", wrapper -> { wrapper.write(Types.BYTE, (byte) (0x01)); // Flags wrapper.write(Types.INT, 0); // Min value }); } @Override public @Nullable String handleArgumentType(String argumentType) { return switch (argumentType) { case "minecraft:nbt_compound_tag" -> "minecraft:nbt"; case "minecraft:nbt_tag" -> "brigadier:string"; case "minecraft:time" -> "brigadier:integer"; default -> super.handleArgumentType(argumentType); }; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14to1_13_2/rewriter/EntityPacketRewriter1_14.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14to1_13_2.rewriter; import com.viaversion.viabackwards.api.entities.storage.EntityPositionHandler; import com.viaversion.viabackwards.api.entities.storage.EntityReplacement; import com.viaversion.viabackwards.api.rewriters.LegacyEntityRewriter; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.Protocol1_14To1_13_2; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.storage.ChunkLightStorage; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.storage.DifficultyStorage; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.storage.EntityPositionStorage1_14; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.data.entity.StoredEntityData; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.VillagerData; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_13; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_14; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_13_2; import com.viaversion.viaversion.api.type.types.version.Types1_14; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ServerboundPackets1_13; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ClientboundPackets1_14; import com.viaversion.viaversion.rewriter.entitydata.EntityDataHandler; public class EntityPacketRewriter1_14 extends LegacyEntityRewriter { private EntityPositionHandler positionHandler; public EntityPacketRewriter1_14(Protocol1_14To1_13_2 protocol) { super(protocol, Types1_13_2.ENTITY_DATA_TYPES.optionalComponentType, Types1_13_2.ENTITY_DATA_TYPES.booleanType); } @Override protected void registerPackets() { positionHandler = new EntityPositionHandler(this, EntityPositionStorage1_14.class, EntityPositionStorage1_14::new); protocol.registerClientbound(ClientboundPackets1_14.ENTITY_EVENT, wrapper -> { int entityId = wrapper.passthrough(Types.INT); byte status = wrapper.passthrough(Types.BYTE); // Check for death status if (status != 3) return; EntityTracker tracker = tracker(wrapper.user()); EntityType entityType = tracker.entityType(entityId); if (entityType != EntityTypes1_14.PLAYER) return; // Remove equipment, else the client will see ghost items for (int i = 0; i <= 5; i++) { PacketWrapper equipmentPacket = wrapper.create(ClientboundPackets1_13.SET_EQUIPPED_ITEM); equipmentPacket.write(Types.VAR_INT, entityId); equipmentPacket.write(Types.VAR_INT, i); equipmentPacket.write(Types.ITEM1_13_2, null); equipmentPacket.send(Protocol1_14To1_13_2.class); } }); protocol.registerClientbound(ClientboundPackets1_14.TELEPORT_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.DOUBLE); map(Types.DOUBLE); map(Types.DOUBLE); handler(wrapper -> positionHandler.cacheEntityPosition(wrapper, false, false)); } }); PacketHandlers relativeMoveHandler = new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.SHORT); map(Types.SHORT); map(Types.SHORT); handler(wrapper -> { double x = wrapper.get(Types.SHORT, 0) / EntityPositionHandler.RELATIVE_MOVE_FACTOR; double y = wrapper.get(Types.SHORT, 1) / EntityPositionHandler.RELATIVE_MOVE_FACTOR; double z = wrapper.get(Types.SHORT, 2) / EntityPositionHandler.RELATIVE_MOVE_FACTOR; positionHandler.cacheEntityPosition(wrapper, x, y, z, false, true); }); } }; protocol.registerClientbound(ClientboundPackets1_14.MOVE_ENTITY_POS, relativeMoveHandler); protocol.registerClientbound(ClientboundPackets1_14.MOVE_ENTITY_POS_ROT, relativeMoveHandler); protocol.registerClientbound(ClientboundPackets1_14.ADD_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.UUID); // 1 - UUID map(Types.VAR_INT, Types.BYTE); // 2 - Type map(Types.DOUBLE); // 3 - X map(Types.DOUBLE); // 4 - Y map(Types.DOUBLE); // 5 - Z map(Types.BYTE); // 6 - Pitch map(Types.BYTE); // 7 - Yaw map(Types.INT); // 8 - Data map(Types.SHORT); // 9 - Velocity X map(Types.SHORT); // 10 - Velocity Y map(Types.SHORT); // 11 - Velocity Z handler(wrapper -> { final int type = wrapper.get(Types.BYTE, 0); final int data = wrapper.get(Types.INT, 0); final EntityType entityType = objectTypeFromId(type, data); if (entityType == null) { return; } trackAndCacheEntityPosition(wrapper, entityType); }); handler(wrapper -> { int id = wrapper.get(Types.BYTE, 0); int mappedId = newEntityId(id); EntityTypes1_13.EntityType entityType = EntityTypes1_13.EntityType.findById(mappedId); if (entityType == null) { // Would be EntityType.PIG on a 1.14 client, but later discarded anyway since not an object type return; } EntityTypes1_13.ObjectType objectType = null; if (entityType.isOrHasParent(EntityTypes1_13.EntityType.ABSTRACT_MINECART)) { objectType = EntityTypes1_13.ObjectType.MINECART; int data = switch (entityType) { case CHEST_MINECART -> 1; case FURNACE_MINECART -> 2; case TNT_MINECART -> 3; case SPAWNER_MINECART -> 4; case HOPPER_MINECART -> 5; case COMMAND_BLOCK_MINECART -> 6; default -> 0; }; if (data != 0) wrapper.set(Types.INT, 0, data); } else if (entityType.is(EntityTypes1_13.EntityType.EXPERIENCE_ORB)) { // Newer clients can spawn experience orbs via add entity, map to add experience orb and override values via multiple packets wrapper.cancel(); final int entityId = wrapper.get(Types.VAR_INT, 0); // Shrug about uuid or rotations final PacketWrapper addExperienceOrb = PacketWrapper.create(ClientboundPackets1_13.ADD_EXPERIENCE_ORB, wrapper.user()); addExperienceOrb.write(Types.VAR_INT, entityId); // Entity id addExperienceOrb.write(Types.DOUBLE, wrapper.get(Types.DOUBLE, 0)); // X addExperienceOrb.write(Types.DOUBLE, wrapper.get(Types.DOUBLE, 1)); // Y addExperienceOrb.write(Types.DOUBLE, wrapper.get(Types.DOUBLE, 2)); // Z addExperienceOrb.write(Types.SHORT, (short) 0); // Experience count addExperienceOrb.send(Protocol1_14To1_13_2.class); final PacketWrapper setEntityMotion = PacketWrapper.create(ClientboundPackets1_13.SET_ENTITY_MOTION, wrapper.user()); setEntityMotion.write(Types.VAR_INT, entityId); // Entity id setEntityMotion.write(Types.SHORT, wrapper.get(Types.SHORT, 0)); setEntityMotion.write(Types.SHORT, wrapper.get(Types.SHORT, 1)); setEntityMotion.write(Types.SHORT, wrapper.get(Types.SHORT, 2)); setEntityMotion.send(Protocol1_14To1_13_2.class); return; } else { for (final EntityTypes1_13.ObjectType type : EntityTypes1_13.ObjectType.values()) { if (type.getType() == entityType) { objectType = type; break; } } } if (objectType == null) return; wrapper.set(Types.BYTE, 0, (byte) objectType.getId()); int data = wrapper.get(Types.INT, 0); if (objectType == EntityTypes1_13.ObjectType.FALLING_BLOCK) { int blockState = wrapper.get(Types.INT, 0); int combined = protocol.getMappingData().getNewBlockStateId(blockState); wrapper.set(Types.INT, 0, combined); } else if (entityType.isOrHasParent(EntityTypes1_13.EntityType.ABSTRACT_ARROW)) { wrapper.set(Types.INT, 0, data + 1); } }); } }); protocol.registerClientbound(ClientboundPackets1_14.ADD_MOB, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.UUID); // 1 - Entity UUID map(Types.VAR_INT); // 2 - Entity Type map(Types.DOUBLE); // 3 - X map(Types.DOUBLE); // 4 - Y map(Types.DOUBLE); // 5 - Z map(Types.BYTE); // 6 - Yaw map(Types.BYTE); // 7 - Pitch map(Types.BYTE); // 8 - Head Pitch map(Types.SHORT); // 9 - Velocity X map(Types.SHORT); // 10 - Velocity Y map(Types.SHORT); // 11 - Velocity Z map(Types1_14.ENTITY_DATA_LIST, Types1_13_2.ENTITY_DATA_LIST); // 12 - Entity data handler(wrapper -> { int type = wrapper.get(Types.VAR_INT, 1); EntityType entityType = EntityTypes1_14.getTypeFromId(type); trackAndCacheEntityPosition(wrapper, entityType); int oldId = newEntityId(type); if (oldId == -1) { EntityReplacement entityReplacement = entityDataForType(entityType); if (entityReplacement == null) { protocol.getLogger().warning("Could not find entity type mapping " + type + "/" + entityType); wrapper.cancel(); } else { wrapper.set(Types.VAR_INT, 1, entityReplacement.replacementId()); } } else { wrapper.set(Types.VAR_INT, 1, oldId); } }); // Handle entity type & data handler(getMobSpawnRewriter1_11(Types1_13_2.ENTITY_DATA_LIST)); } }); protocol.registerClientbound(ClientboundPackets1_14.ADD_EXPERIENCE_ORB, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.DOUBLE); // Needs to be mapped for the position cache map(Types.DOUBLE); map(Types.DOUBLE); handler(wrapper -> trackAndCacheEntityPosition(wrapper, EntityTypes1_14.EXPERIENCE_ORB)); } }); protocol.registerClientbound(ClientboundPackets1_14.ADD_GLOBAL_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.BYTE); map(Types.DOUBLE); // Needs to be mapped for the position cache map(Types.DOUBLE); map(Types.DOUBLE); handler(wrapper -> trackAndCacheEntityPosition(wrapper, EntityTypes1_14.LIGHTNING_BOLT)); } }); protocol.registerClientbound(ClientboundPackets1_14.ADD_PAINTING, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.UUID); map(Types.VAR_INT); map(Types.BLOCK_POSITION1_14, Types.BLOCK_POSITION1_8); map(Types.BYTE); // Track entity handler(wrapper -> trackAndCacheEntityPosition(wrapper, EntityTypes1_14.PAINTING)); } }); protocol.registerClientbound(ClientboundPackets1_14.ADD_PLAYER, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.UUID); // 1 - Player UUID map(Types.DOUBLE); // 2 - X map(Types.DOUBLE); // 3 - Y map(Types.DOUBLE); // 4 - Z map(Types.BYTE); // 5 - Yaw map(Types.BYTE); // 6 - Pitch map(Types1_14.ENTITY_DATA_LIST, Types1_13_2.ENTITY_DATA_LIST); // 7 - Entity data handler(getTrackerAndDataHandler(Types1_13_2.ENTITY_DATA_LIST, EntityTypes1_14.PLAYER)); handler(wrapper -> positionHandler.cacheEntityPosition(wrapper, true, false)); } }); registerSetEntityData(ClientboundPackets1_14.SET_ENTITY_DATA, Types1_14.ENTITY_DATA_LIST, Types1_13_2.ENTITY_DATA_LIST); protocol.registerClientbound(ClientboundPackets1_14.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Entity ID map(Types.UNSIGNED_BYTE); // 1 - Gamemode map(Types.INT); // 2 - Dimension handler(getDimensionHandler(1)); handler(getPlayerTrackerHandler()); handler(wrapper -> { short difficulty = wrapper.user().get(DifficultyStorage.class).getDifficulty(); wrapper.write(Types.UNSIGNED_BYTE, difficulty); wrapper.passthrough(Types.UNSIGNED_BYTE); // Max Players wrapper.passthrough(Types.STRING); // Level Type wrapper.read(Types.VAR_INT); // Read View Distance final int entityId = wrapper.get(Types.INT, 0); final StoredEntityData storedEntity = tracker(wrapper.user()).entityData(entityId); storedEntity.put(new EntityPositionStorage1_14()); }); } }); protocol.registerClientbound(ClientboundPackets1_14.RESPAWN, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Dimension ID handler(wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_14To1_13_2.class); int dimensionId = wrapper.get(Types.INT, 0); if (clientWorld.setEnvironment(dimensionId)) { EntityTracker tracker = tracker(wrapper.user()); tracker.clearEntities(); wrapper.user().get(ChunkLightStorage.class).clear(); tracker.entityData(tracker.clientEntityId()).put(new EntityPositionStorage1_14()); } short difficulty = wrapper.user().get(DifficultyStorage.class).getDifficulty(); wrapper.write(Types.UNSIGNED_BYTE, difficulty); }); } }); PacketHandler absoluteMoveHandler = wrapper -> { final double x = wrapper.passthrough(Types.DOUBLE); final double y = wrapper.passthrough(Types.DOUBLE); final double z = wrapper.passthrough(Types.DOUBLE); positionHandler.cacheEntityPosition(wrapper, tracker(wrapper.user()).clientEntityId(), x, y, z, false, false); }; protocol.registerServerbound(ServerboundPackets1_13.MOVE_PLAYER_POS, absoluteMoveHandler); protocol.registerServerbound(ServerboundPackets1_13.MOVE_PLAYER_POS_ROT, absoluteMoveHandler); } private void trackAndCacheEntityPosition(PacketWrapper wrapper, EntityType type) { // Tracks the entity + cache the position for the entity tracker(wrapper.user()).addEntity(wrapper.get(Types.VAR_INT, 0), type); if (type == EntityTypes1_14.PAINTING) { final BlockPosition position = wrapper.get(Types.BLOCK_POSITION1_8, 0); positionHandler.cacheEntityPosition(wrapper, position.x(), position.y(), position.z(), true, false); } else { positionHandler.cacheEntityPosition(wrapper, true, false); } } @Override protected void registerRewrites() { filter().handler((event, data) -> { int typeId = data.dataType().typeId(); if (typeId <= 15) { data.setDataType(Types1_13_2.ENTITY_DATA_TYPES.byId(typeId)); } }); registerEntityDataTypeHandler(Types1_13_2.ENTITY_DATA_TYPES.itemType, null, Types1_13_2.ENTITY_DATA_TYPES.optionalBlockStateType, null, Types1_13_2.ENTITY_DATA_TYPES.componentType, Types1_13_2.ENTITY_DATA_TYPES.optionalComponentType); filter().type(EntityTypes1_14.PILLAGER).cancel(15); filter().type(EntityTypes1_14.FOX).cancel(15); filter().type(EntityTypes1_14.FOX).cancel(16); filter().type(EntityTypes1_14.FOX).cancel(17); filter().type(EntityTypes1_14.FOX).cancel(18); filter().type(EntityTypes1_14.PANDA).cancel(15); filter().type(EntityTypes1_14.PANDA).cancel(16); filter().type(EntityTypes1_14.PANDA).cancel(17); filter().type(EntityTypes1_14.PANDA).cancel(18); filter().type(EntityTypes1_14.PANDA).cancel(19); filter().type(EntityTypes1_14.PANDA).cancel(20); filter().type(EntityTypes1_14.CAT).cancel(18); filter().type(EntityTypes1_14.CAT).cancel(19); filter().type(EntityTypes1_14.CAT).cancel(20); filter().type(EntityTypes1_14.ABSTRACT_RAIDER).removeIndex(14); // Celebrating filter().type(EntityTypes1_14.AREA_EFFECT_CLOUD).index(10).handler((event, data) -> { protocol.getParticleRewriter().rewriteParticle(event.user(), (Particle) data.getValue()); }); filter().type(EntityTypes1_14.FIREWORK_ROCKET).index(8).handler((event, data) -> { data.setDataType(Types1_13_2.ENTITY_DATA_TYPES.varIntType); Integer value = (Integer) data.getValue(); if (value == null) { data.setValue(0); } }); filter().type(EntityTypes1_14.ABSTRACT_ARROW).removeIndex(9); filter().type(EntityTypes1_14.VILLAGER).cancel(15); // Head shake timer EntityDataHandler villagerDataHandler = (event, data) -> { VillagerData villagerData = (VillagerData) data.getValue(); data.setTypeAndValue(Types1_13_2.ENTITY_DATA_TYPES.varIntType, villagerDataToProfession(villagerData)); if (data.id() == 16) { event.setIndex(15); // decreased by 2 again in one of the following handlers } }; filter().type(EntityTypes1_14.ZOMBIE_VILLAGER).index(18).handler(villagerDataHandler); filter().type(EntityTypes1_14.VILLAGER).index(16).handler(villagerDataHandler); // Holding arms up - from bitfield into own boolean filter().type(EntityTypes1_14.ABSTRACT_SKELETON).index(13).handler((event, data) -> { byte value = (byte) data.getValue(); if ((value & 4) != 0) { event.createExtraData(new EntityData(14, Types1_13_2.ENTITY_DATA_TYPES.booleanType, true)); } }); filter().type(EntityTypes1_14.ZOMBIE).index(13).handler((event, data) -> { byte value = (byte) data.getValue(); if ((value & 4) != 0) { event.createExtraData(new EntityData(16, Types1_13_2.ENTITY_DATA_TYPES.booleanType, true)); } }); filter().type(EntityTypes1_14.ZOMBIE).addIndex(16); // Remove bed location filter().type(EntityTypes1_14.LIVING_ENTITY).handler((event, data) -> { int index = event.index(); if (index == 12) { BlockPosition position = (BlockPosition) data.getValue(); if (position != null) { // Use bed PacketWrapper wrapper = PacketWrapper.create(ClientboundPackets1_13.PLAYER_SLEEP, null, event.user()); wrapper.write(Types.VAR_INT, event.entityId()); wrapper.write(Types.BLOCK_POSITION1_8, position); try { wrapper.scheduleSend(Protocol1_14To1_13_2.class); } catch (Exception ex) { ex.printStackTrace(); } } event.cancel(); } else if (index > 12) { event.setIndex(index - 1); } }); // Pose filter().removeIndex(6); filter().type(EntityTypes1_14.OCELOT).index(13).handler((event, data) -> { event.setIndex(15); data.setTypeAndValue(Types1_13_2.ENTITY_DATA_TYPES.varIntType, 0); }); filter().type(EntityTypes1_14.CAT).handler((event, data) -> { if (event.index() == 15) { data.setValue(1); } else if (event.index() == 13) { data.setValue((byte) ((byte) data.getValue() & 0x4)); } }); filter().handler((event, data) -> { if (data.dataType().typeId() > 15) { event.cancel(); // Cancel bad data (generally from plugins sending data for despawned entities) } }); } public int villagerDataToProfession(VillagerData data) { switch (data.profession()) { case 1: // Armorer case 10: // Mason case 13: // Toolsmith case 14: // Weaponsmith return 3; // Blacksmith case 2: // Butcher case 8: // Leatherworker return 4; // Butcher case 3: // Cartographer case 9: // Librarian return 1; // Librarian case 4: // Cleric return 2; // Priest case 5: // Farmer case 6: // Fisherman case 7: // Fletcher case 12: // Shepherd return 0; // Farmer case 0: // None case 11: // Nitwit default: return 5; // Nitwit } } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); mapEntityTypeWithData(EntityTypes1_14.CAT, EntityTypes1_14.OCELOT).jsonName(); mapEntityTypeWithData(EntityTypes1_14.TRADER_LLAMA, EntityTypes1_14.LLAMA).jsonName(); mapEntityTypeWithData(EntityTypes1_14.FOX, EntityTypes1_14.WOLF).jsonName(); mapEntityTypeWithData(EntityTypes1_14.PANDA, EntityTypes1_14.POLAR_BEAR).jsonName(); mapEntityTypeWithData(EntityTypes1_14.PILLAGER, EntityTypes1_14.VILLAGER).jsonName(); mapEntityTypeWithData(EntityTypes1_14.WANDERING_TRADER, EntityTypes1_14.VILLAGER).jsonName(); mapEntityTypeWithData(EntityTypes1_14.RAVAGER, EntityTypes1_14.COW).jsonName(); } @Override public EntityType typeFromId(int typeId) { return EntityTypes1_14.getTypeFromId(typeId); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14to1_13_2/rewriter/PlayerPacketRewriter1_14.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14to1_13_2.rewriter; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.Protocol1_14To1_13_2; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.storage.DifficultyStorage; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.rewriter.RewriterBase; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ServerboundPackets1_13; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ClientboundPackets1_14; public class PlayerPacketRewriter1_14 extends RewriterBase { public PlayerPacketRewriter1_14(Protocol1_14To1_13_2 protocol) { super(protocol); } @Override protected void registerPackets() { protocol.registerClientbound(ClientboundPackets1_14.CHANGE_DIFFICULTY, new PacketHandlers() { @Override public void register() { map(Types.UNSIGNED_BYTE); read(Types.BOOLEAN); // Locked handler(wrapper -> { byte difficulty = wrapper.get(Types.UNSIGNED_BYTE, 0).byteValue(); wrapper.user().get(DifficultyStorage.class).setDifficulty(difficulty); }); } }); protocol.registerClientbound(ClientboundPackets1_14.OPEN_SIGN_EDITOR, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_14, Types.BLOCK_POSITION1_8); } }); protocol.registerServerbound(ServerboundPackets1_13.BLOCK_ENTITY_TAG_QUERY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.BLOCK_POSITION1_8, Types.BLOCK_POSITION1_14); } }); protocol.registerServerbound(ServerboundPackets1_13.PLAYER_ACTION, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Action map(Types.BLOCK_POSITION1_8, Types.BLOCK_POSITION1_14); // Position } }); protocol.registerServerbound(ServerboundPackets1_13.RECIPE_BOOK_UPDATE, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); handler(wrapper -> { int type = wrapper.get(Types.VAR_INT, 0); if (type == 0) { wrapper.passthrough(Types.STRING); } else if (type == 1) { wrapper.passthrough(Types.BOOLEAN); // Crafting Recipe Book Open wrapper.passthrough(Types.BOOLEAN); // Crafting Recipe Filter Active wrapper.passthrough(Types.BOOLEAN); // Smelting Recipe Book Open wrapper.passthrough(Types.BOOLEAN); // Smelting Recipe Filter Active // Blast furnace/smoker data wrapper.write(Types.BOOLEAN, false); wrapper.write(Types.BOOLEAN, false); wrapper.write(Types.BOOLEAN, false); wrapper.write(Types.BOOLEAN, false); } }); } }); protocol.registerServerbound(ServerboundPackets1_13.SET_COMMAND_BLOCK, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_8, Types.BLOCK_POSITION1_14); } }); protocol.registerServerbound(ServerboundPackets1_13.SET_STRUCTURE_BLOCK, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_8, Types.BLOCK_POSITION1_14); } }); protocol.registerServerbound(ServerboundPackets1_13.SIGN_UPDATE, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_8, Types.BLOCK_POSITION1_14); } }); protocol.registerServerbound(ServerboundPackets1_13.USE_ITEM_ON, wrapper -> { BlockPosition position = wrapper.read(Types.BLOCK_POSITION1_8); int face = wrapper.read(Types.VAR_INT); int hand = wrapper.read(Types.VAR_INT); float x = wrapper.read(Types.FLOAT); float y = wrapper.read(Types.FLOAT); float z = wrapper.read(Types.FLOAT); wrapper.write(Types.VAR_INT, hand); wrapper.write(Types.BLOCK_POSITION1_14, position); wrapper.write(Types.VAR_INT, face); wrapper.write(Types.FLOAT, x); wrapper.write(Types.FLOAT, y); wrapper.write(Types.FLOAT, z); wrapper.write(Types.BOOLEAN, false); // Inside block }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14to1_13_2/rewriter/SoundPacketRewriter1_14.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14to1_13_2.rewriter; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.Protocol1_14To1_13_2; import com.viaversion.viabackwards.protocol.v1_14to1_13_2.storage.EntityPositionStorage1_14; import com.viaversion.viaversion.api.data.entity.StoredEntityData; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.rewriter.RewriterBase; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ClientboundPackets1_14; public class SoundPacketRewriter1_14 extends RewriterBase { public SoundPacketRewriter1_14(Protocol1_14To1_13_2 protocol) { super(protocol); } @Override protected void registerPackets() { // Entity Sound Effect protocol.registerClientbound(ClientboundPackets1_14.SOUND_ENTITY, null, wrapper -> { wrapper.cancel(); int soundId = wrapper.read(Types.VAR_INT); int newId = protocol.getMappingData().getSoundMappings().getNewId(soundId); if (newId == -1) return; int category = wrapper.read(Types.VAR_INT); int entityId = wrapper.read(Types.VAR_INT); StoredEntityData storedEntity = wrapper.user().getEntityTracker(protocol.getClass()).entityData(entityId); EntityPositionStorage1_14 entityStorage; if (storedEntity == null || (entityStorage = storedEntity.get(EntityPositionStorage1_14.class)) == null) { protocol.getLogger().warning("Untracked entity with id " + entityId); return; } float volume = wrapper.read(Types.FLOAT); float pitch = wrapper.read(Types.FLOAT); int x = (int) (entityStorage.x() * 8D); int y = (int) (entityStorage.y() * 8D); int z = (int) (entityStorage.z() * 8D); PacketWrapper soundPacket = wrapper.create(ClientboundPackets1_13.SOUND); soundPacket.write(Types.VAR_INT, newId); soundPacket.write(Types.VAR_INT, category); soundPacket.write(Types.INT, x); soundPacket.write(Types.INT, y); soundPacket.write(Types.INT, z); soundPacket.write(Types.FLOAT, volume); soundPacket.write(Types.FLOAT, pitch); soundPacket.send(Protocol1_14To1_13_2.class); }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14to1_13_2/storage/ChunkLightStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14to1_13_2.storage; import com.viaversion.viaversion.api.connection.StorableObject; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; public class ChunkLightStorage implements StorableObject { public static final byte[] FULL_LIGHT = new byte[2048]; public static final byte[] EMPTY_LIGHT = new byte[2048]; private static Constructor fastUtilLongObjectHashMap; private final Map storedLight = createLongObjectMap(); static { Arrays.fill(FULL_LIGHT, (byte) 0xFF); Arrays.fill(EMPTY_LIGHT, (byte) 0x0); try { fastUtilLongObjectHashMap = Class.forName("it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap").getConstructor(); } catch (ClassNotFoundException | NoSuchMethodException ignored) { } } public void setStoredLight(byte[][] skyLight, byte[][] blockLight, int x, int z) { storedLight.put(getChunkSectionIndex(x, z), new ChunkLight(skyLight, blockLight)); } public ChunkLight getStoredLight(int x, int z) { return storedLight.get(getChunkSectionIndex(x, z)); } public void clear() { storedLight.clear(); } public void unloadChunk(int x, int z) { storedLight.remove(getChunkSectionIndex(x, z)); } private long getChunkSectionIndex(int x, int z) { return ((x & 0x3FFFFFFL) << 38) | (z & 0x3FFFFFFL); } private Map createLongObjectMap() { if (fastUtilLongObjectHashMap != null) { try { return (Map) fastUtilLongObjectHashMap.newInstance(); } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { e.printStackTrace(); } } return new HashMap<>(); } public record ChunkLight(byte[][] skyLight, byte[][] blockLight) { @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final ChunkLight that = (ChunkLight) o; if (!Arrays.deepEquals(skyLight, that.skyLight)) return false; return Arrays.deepEquals(blockLight, that.blockLight); } @Override public int hashCode() { int result = Arrays.deepHashCode(skyLight); result = 31 * result + Arrays.deepHashCode(blockLight); return result; } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14to1_13_2/storage/DifficultyStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14to1_13_2.storage; import com.viaversion.viaversion.api.connection.StorableObject; public class DifficultyStorage implements StorableObject { private byte difficulty; public byte getDifficulty() { return difficulty; } public void setDifficulty(byte difficulty) { this.difficulty = difficulty; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_14to1_13_2/storage/EntityPositionStorage1_14.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_14to1_13_2.storage; import com.viaversion.viabackwards.api.entities.storage.EntityPositionStorage; public class EntityPositionStorage1_14 extends EntityPositionStorage { } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_15_1to1_15/Protocol1_15_1To1_15.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_15_1to1_15; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ServerboundPackets1_14; import com.viaversion.viaversion.protocols.v1_14_4to1_15.packet.ClientboundPackets1_15; public class Protocol1_15_1To1_15 extends BackwardsProtocol { public Protocol1_15_1To1_15() { super(ClientboundPackets1_15.class, ClientboundPackets1_15.class, ServerboundPackets1_14.class, ServerboundPackets1_14.class); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_15_2to1_15_1/Protocol1_15_2To1_15_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_15_2to1_15_1; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ServerboundPackets1_14; import com.viaversion.viaversion.protocols.v1_14_4to1_15.packet.ClientboundPackets1_15; public class Protocol1_15_2To1_15_1 extends BackwardsProtocol { public Protocol1_15_2To1_15_1() { super(ClientboundPackets1_15.class, ClientboundPackets1_15.class, ServerboundPackets1_14.class, ServerboundPackets1_14.class); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_15to1_14_4/Protocol1_15To1_14_4.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_15to1_14_4; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_15to1_14_4.rewriter.BlockItemPacketRewriter1_15; import com.viaversion.viabackwards.protocol.v1_15to1_14_4.rewriter.EntityPacketRewriter1_15; import com.viaversion.viabackwards.protocol.v1_15to1_14_4.storage.ImmediateRespawnStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_15; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ServerboundPackets1_14; import com.viaversion.viaversion.protocols.v1_14_3to1_14_4.packet.ClientboundPackets1_14_4; import com.viaversion.viaversion.protocols.v1_14_4to1_15.Protocol1_14_4To1_15; import com.viaversion.viaversion.protocols.v1_14_4to1_15.packet.ClientboundPackets1_15; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; public class Protocol1_15To1_14_4 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.15", "1.14", Protocol1_14_4To1_15.class); private final EntityPacketRewriter1_15 entityRewriter = new EntityPacketRewriter1_15(this); private final BlockItemPacketRewriter1_15 blockItemPackets = new BlockItemPacketRewriter1_15(this); private final BlockRewriter blockRewriter = BlockRewriter.for1_14(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final JsonNBTComponentRewriter translatableRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); private final TagRewriter tagRewriter = new TagRewriter<>(this); public Protocol1_15To1_14_4() { super(ClientboundPackets1_15.class, ClientboundPackets1_14_4.class, ServerboundPackets1_14.class, ServerboundPackets1_14.class); } @Override protected void registerPackets() { super.registerPackets(); // Explosion - manually send an explosion sound registerClientbound(ClientboundPackets1_15.EXPLODE, new PacketHandlers() { @Override public void register() { map(Types.FLOAT); // x map(Types.FLOAT); // y map(Types.FLOAT); // z handler(wrapper -> { PacketWrapper soundPacket = wrapper.create(ClientboundPackets1_14_4.SOUND); soundPacket.write(Types.VAR_INT, 243); // entity.generic.explode soundPacket.write(Types.VAR_INT, 4); // blocks category soundPacket.write(Types.INT, toEffectCoordinate(wrapper.get(Types.FLOAT, 0))); // x soundPacket.write(Types.INT, toEffectCoordinate(wrapper.get(Types.FLOAT, 1))); // y soundPacket.write(Types.INT, toEffectCoordinate(wrapper.get(Types.FLOAT, 2))); // z soundPacket.write(Types.FLOAT, 4F); // volume soundPacket.write(Types.FLOAT, 1F); // pitch - usually semi randomized by the server, but we don't really have to care about that soundPacket.send(Protocol1_15To1_14_4.class); }); } private int toEffectCoordinate(float coordinate) { return (int) (coordinate * 8); } }); tagRewriter.register(ClientboundPackets1_15.UPDATE_TAGS, RegistryType.ENTITY); } @Override public void init(UserConnection user) { user.addEntityTracker(getClass(), new EntityTrackerBase(user, EntityTypes1_15.PLAYER)); user.addClientWorld(getClass(), new ClientWorld()); user.put(new ImmediateRespawnStorage()); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_15 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_15 getItemRewriter() { return blockItemPackets; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_15to1_14_4/rewriter/BlockItemPacketRewriter1_15.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_15to1_14_4.rewriter; import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.protocol.v1_15to1_14_4.Protocol1_15To1_14_4; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_14; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_15; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ServerboundPackets1_14; import com.viaversion.viaversion.protocols.v1_14_4to1_15.packet.ClientboundPackets1_15; import com.viaversion.viaversion.rewriter.RecipeRewriter; public class BlockItemPacketRewriter1_15 extends BackwardsItemRewriter { public BlockItemPacketRewriter1_15(Protocol1_15To1_14_4 protocol) { super(protocol, Types.ITEM1_13_2, Types.ITEM1_13_2_SHORT_ARRAY); } @Override protected void registerPackets() { new RecipeRewriter<>(protocol).register(ClientboundPackets1_15.UPDATE_RECIPES); protocol.registerServerbound(ServerboundPackets1_14.EDIT_BOOK, wrapper -> handleItemToServer(wrapper.user(), wrapper.passthrough(Types.ITEM1_13_2))); protocol.registerClientbound(ClientboundPackets1_15.LEVEL_CHUNK, wrapper -> { Chunk chunk = wrapper.read(ChunkType1_15.TYPE); wrapper.write(ChunkType1_14.TYPE, chunk); if (chunk.isFullChunk()) { int[] biomeData = chunk.getBiomeData(); int[] newBiomeData = new int[256]; for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { int x = j << 2; int z = i << 2; int newIndex = z << 4 | x; int oldIndex = i << 2 | j; int biome = biomeData[oldIndex]; for (int k = 0; k < 4; k++) { int offX = newIndex + (k << 4); for (int l = 0; l < 4; l++) { newBiomeData[offX + l] = biome; } } } } chunk.setBiomeData(newBiomeData); } protocol.getBlockRewriter().handleChunk(chunk); }); protocol.replaceClientbound(ClientboundPackets1_15.LEVEL_PARTICLES, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Particle ID map(Types.BOOLEAN); // 1 - Long Distance map(Types.DOUBLE, Types.FLOAT); // 2 - X map(Types.DOUBLE, Types.FLOAT); // 3 - Y map(Types.DOUBLE, Types.FLOAT); // 4 - Z map(Types.FLOAT); // 5 - Offset X map(Types.FLOAT); // 6 - Offset Y map(Types.FLOAT); // 7 - Offset Z map(Types.FLOAT); // 8 - Particle Data map(Types.INT); // 9 - Particle Count handler(wrapper -> { int id = wrapper.get(Types.INT, 0); if (id == 3 || id == 23) { int data = wrapper.passthrough(Types.VAR_INT); wrapper.set(Types.VAR_INT, 0, protocol.getMappingData().getNewBlockStateId(data)); } else if (id == 32) { Item item = handleItemToClient(wrapper.user(), wrapper.read(Types.ITEM1_13_2)); wrapper.write(Types.ITEM1_13_2, item); } int mappedId = protocol.getMappingData().getNewParticleId(id); if (id != mappedId) { wrapper.set(Types.INT, 0, mappedId); } }); } }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_15to1_14_4/rewriter/EntityPacketRewriter1_15.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_15to1_14_4.rewriter; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_15to1_14_4.Protocol1_15To1_14_4; import com.viaversion.viabackwards.protocol.v1_15to1_14_4.storage.ImmediateRespawnStorage; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_15; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_14; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ServerboundPackets1_14; import com.viaversion.viaversion.protocols.v1_14_4to1_15.packet.ClientboundPackets1_15; import java.util.ArrayList; public class EntityPacketRewriter1_15 extends EntityRewriter { public EntityPacketRewriter1_15(Protocol1_15To1_14_4 protocol) { super(protocol); } @Override protected void registerPackets() { protocol.registerClientbound(ClientboundPackets1_15.SET_HEALTH, wrapper -> { float health = wrapper.passthrough(Types.FLOAT); if (health > 0) return; if (!wrapper.user().get(ImmediateRespawnStorage.class).isImmediateRespawn()) return; // Instantly request respawn when 1.15 gamerule is set PacketWrapper statusPacket = wrapper.create(ServerboundPackets1_14.CLIENT_COMMAND); statusPacket.write(Types.VAR_INT, 0); statusPacket.sendToServer(Protocol1_15To1_14_4.class); }); protocol.registerClientbound(ClientboundPackets1_15.GAME_EVENT, new PacketHandlers() { @Override public void register() { map(Types.UNSIGNED_BYTE); map(Types.FLOAT); handler(wrapper -> { if (wrapper.get(Types.UNSIGNED_BYTE, 0) == 11) { wrapper.user().get(ImmediateRespawnStorage.class).setImmediateRespawn(wrapper.get(Types.FLOAT, 0) == 1); } }); } }); protocol.replaceClientbound(ClientboundPackets1_15.ADD_MOB, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.UUID); // 1 - Entity UUID map(Types.VAR_INT); // 2 - Entity Type map(Types.DOUBLE); // 3 - X map(Types.DOUBLE); // 4 - Y map(Types.DOUBLE); // 5 - Z map(Types.BYTE); // 6 - Yaw map(Types.BYTE); // 7 - Pitch map(Types.BYTE); // 8 - Head Pitch map(Types.SHORT); // 9 - Velocity X map(Types.SHORT); // 10 - Velocity Y map(Types.SHORT); // 11 - Velocity Z handler(wrapper -> wrapper.write(Types1_14.ENTITY_DATA_LIST, new ArrayList<>())); // Entity data is no longer sent in 1.15, so we have to send an empty one handler(wrapper -> { int type = wrapper.get(Types.VAR_INT, 1); EntityType entityType = EntityTypes1_15.getTypeFromId(type); tracker(wrapper.user()).addEntity(wrapper.get(Types.VAR_INT, 0), entityType); wrapper.set(Types.VAR_INT, 1, newEntityId(type)); }); } }); protocol.registerClientbound(ClientboundPackets1_15.RESPAWN, new PacketHandlers() { @Override public void register() { map(Types.INT); read(Types.LONG); // Seed handler(getDimensionHandler(0)); } }); protocol.registerClientbound(ClientboundPackets1_15.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Entity ID map(Types.UNSIGNED_BYTE); // 1 - Gamemode map(Types.INT); // 2 - Dimension handler(getDimensionHandler(1)); read(Types.LONG); // Seed map(Types.UNSIGNED_BYTE); // 3 - Max Players map(Types.STRING); // 4 - Level Type map(Types.VAR_INT); // 5 - View Distance map(Types.BOOLEAN); // 6 - Reduce Debug Info handler(getPlayerTrackerHandler()); handler(wrapper -> { boolean immediateRespawn = !wrapper.read(Types.BOOLEAN); // Inverted wrapper.user().get(ImmediateRespawnStorage.class).setImmediateRespawn(immediateRespawn); }); } }); registerTracker(ClientboundPackets1_15.ADD_EXPERIENCE_ORB, EntityTypes1_15.EXPERIENCE_ORB); registerTracker(ClientboundPackets1_15.ADD_GLOBAL_ENTITY, EntityTypes1_15.LIGHTNING_BOLT); registerTracker(ClientboundPackets1_15.ADD_PAINTING, EntityTypes1_15.PAINTING); protocol.registerClientbound(ClientboundPackets1_15.ADD_PLAYER, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID map(Types.UUID); // 1 - Player UUID map(Types.DOUBLE); // 2 - X map(Types.DOUBLE); // 3 - Y map(Types.DOUBLE); // 4 - Z map(Types.BYTE); // 5 - Yaw map(Types.BYTE); // 6 - Pitch handler(wrapper -> wrapper.write(Types1_14.ENTITY_DATA_LIST, new ArrayList<>())); // Entity data is no longer sent in 1.15, so we have to send an empty one handler(getTrackerHandler(EntityTypes1_15.PLAYER)); } }); registerSetEntityData(ClientboundPackets1_15.SET_ENTITY_DATA, Types1_14.ENTITY_DATA_LIST); // Attributes (get rid of generic.flyingSpeed for the Bee remap) protocol.registerClientbound(ClientboundPackets1_15.UPDATE_ATTRIBUTES, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.INT); handler(wrapper -> { int entityId = wrapper.get(Types.VAR_INT, 0); EntityType entityType = tracker(wrapper.user()).entityType(entityId); if (entityType != EntityTypes1_15.BEE) return; int size = wrapper.get(Types.INT, 0); int newSize = size; for (int i = 0; i < size; i++) { String key = wrapper.read(Types.STRING); if (key.equals("generic.flyingSpeed")) { newSize--; wrapper.read(Types.DOUBLE); int modSize = wrapper.read(Types.VAR_INT); for (int j = 0; j < modSize; j++) { wrapper.read(Types.UUID); wrapper.read(Types.DOUBLE); wrapper.read(Types.BYTE); } } else { wrapper.write(Types.STRING, key); wrapper.passthrough(Types.DOUBLE); int modSize = wrapper.passthrough(Types.VAR_INT); for (int j = 0; j < modSize; j++) { wrapper.passthrough(Types.UUID); wrapper.passthrough(Types.DOUBLE); wrapper.passthrough(Types.BYTE); } } } if (newSize != size) { wrapper.set(Types.INT, 0, newSize); } }); } }); } @Override protected void registerRewrites() { registerEntityDataTypeHandler(Types1_14.ENTITY_DATA_TYPES.itemType, null, Types1_14.ENTITY_DATA_TYPES.optionalBlockStateType, Types1_14.ENTITY_DATA_TYPES.particleType, Types1_14.ENTITY_DATA_TYPES.componentType, Types1_14.ENTITY_DATA_TYPES.optionalComponentType); filter().type(EntityTypes1_15.LIVING_ENTITY).removeIndex(12); filter().type(EntityTypes1_15.BEE).cancel(15); filter().type(EntityTypes1_15.BEE).cancel(16); filter().type(EntityTypes1_15.ENDERMAN).cancel(16); filter().type(EntityTypes1_15.TRIDENT).cancel(10); // Redundant health removed in 1.15 filter().type(EntityTypes1_15.WOLF).addIndex(17); filter().type(EntityTypes1_15.WOLF).index(8).handler((event, data) -> { event.createExtraData(new EntityData(17/*WOLF_HEALTH*/, Types1_14.ENTITY_DATA_TYPES.floatType, event.data().value())); }); } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); mapEntityTypeWithData(EntityTypes1_15.BEE, EntityTypes1_15.PUFFERFISH).jsonName().spawnEntityData(storage -> { storage.add(new EntityData(14, Types1_14.ENTITY_DATA_TYPES.booleanType, false)); storage.add(new EntityData(15, Types1_14.ENTITY_DATA_TYPES.varIntType, 2)); }); } @Override public EntityType typeFromId(int typeId) { return EntityTypes1_15.getTypeFromId(typeId); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_15to1_14_4/storage/ImmediateRespawnStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_15to1_14_4.storage; import com.viaversion.viaversion.api.connection.StorableObject; public class ImmediateRespawnStorage implements StorableObject { private boolean immediateRespawn; public boolean isImmediateRespawn() { return immediateRespawn; } public void setImmediateRespawn(boolean immediateRespawn) { this.immediateRespawn = immediateRespawn; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16_1to1_16/Protocol1_16_1To1_16.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16_1to1_16; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16; import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ServerboundPackets1_16; public class Protocol1_16_1To1_16 extends BackwardsProtocol { public Protocol1_16_1To1_16() { super(ClientboundPackets1_16.class, ClientboundPackets1_16.class, ServerboundPackets1_16.class, ServerboundPackets1_16.class); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16_2to1_16_1/Protocol1_16_2To1_16_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16_2to1_16_1; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.rewriter.BlockItemPacketRewriter1_16_2; import com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.rewriter.CommandRewriter1_16_2; import com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.rewriter.EntityPacketRewriter1_16_2; import com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.storage.BiomeStorage; import com.viaversion.viabackwards.utils.BackwardsProtocolLogger; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_16_2; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16; import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ServerboundPackets1_16; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.Protocol1_16_1To1_16_2; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ClientboundPackets1_16_2; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ServerboundPackets1_16_2; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; import com.viaversion.viaversion.util.ProtocolLogger; public class Protocol1_16_2To1_16_1 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.16.2", "1.16", Protocol1_16_1To1_16_2.class); public static final ProtocolLogger LOGGER = new BackwardsProtocolLogger(Protocol1_16_2To1_16_1.class); private final EntityPacketRewriter1_16_2 entityRewriter = new EntityPacketRewriter1_16_2(this); private final BlockItemPacketRewriter1_16_2 blockItemPackets = new BlockItemPacketRewriter1_16_2(this); private final BlockRewriter blockRewriter = BlockRewriter.for1_14(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final JsonNBTComponentRewriter translatableRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); private final TagRewriter tagRewriter = new TagRewriter<>(this); public Protocol1_16_2To1_16_1() { super(ClientboundPackets1_16_2.class, ClientboundPackets1_16.class, ServerboundPackets1_16_2.class, ServerboundPackets1_16.class); } @Override protected void registerPackets() { super.registerPackets(); new CommandRewriter1_16_2(this).registerDeclareCommands(ClientboundPackets1_16_2.COMMANDS); replaceClientbound(ClientboundPackets1_16_2.CHAT, wrapper -> { JsonElement message = wrapper.passthrough(Types.COMPONENT); translatableRewriter.processText(wrapper.user(), message); byte position = wrapper.passthrough(Types.BYTE); if (position == 2) { // https://bugs.mojang.com/browse/MC-119145 wrapper.clearPacket(); wrapper.setPacketType(ClientboundPackets1_16.SET_TITLES); wrapper.write(Types.VAR_INT, 2); wrapper.write(Types.COMPONENT, message); } }); // Recipe book data has been split into 2 separate packets registerServerbound(ServerboundPackets1_16.RECIPE_BOOK_UPDATE, ServerboundPackets1_16_2.RECIPE_BOOK_CHANGE_SETTINGS, wrapper -> { int type = wrapper.read(Types.VAR_INT); if (type == 0) { // Shown, change to its own packet wrapper.passthrough(Types.STRING); // Recipe wrapper.setPacketType(ServerboundPackets1_16_2.RECIPE_BOOK_SEEN_RECIPE); } else { wrapper.cancel(); // Settings for (int i = 0; i < 3; i++) { sendSeenRecipePacket(i, wrapper); } } }); tagRewriter.register(ClientboundPackets1_16_2.UPDATE_TAGS, RegistryType.ENTITY); } private static void sendSeenRecipePacket(int recipeType, PacketWrapper wrapper) { boolean open = wrapper.read(Types.BOOLEAN); boolean filter = wrapper.read(Types.BOOLEAN); PacketWrapper newPacket = wrapper.create(ServerboundPackets1_16_2.RECIPE_BOOK_CHANGE_SETTINGS); newPacket.write(Types.VAR_INT, recipeType); newPacket.write(Types.BOOLEAN, open); newPacket.write(Types.BOOLEAN, filter); newPacket.sendToServer(Protocol1_16_2To1_16_1.class); } @Override public void init(UserConnection user) { user.put(new BiomeStorage()); user.addEntityTracker(this.getClass(), new EntityTrackerBase(user, EntityTypes1_16_2.PLAYER)); } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public ProtocolLogger getLogger() { return LOGGER; } @Override public EntityPacketRewriter1_16_2 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_16_2 getItemRewriter() { return blockItemPackets; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16_2to1_16_1/data/BiomeMappings1_16_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.data; import com.viaversion.viabackwards.api.data.BackwardsMappingDataLoader; import com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.Protocol1_16_2To1_16_1; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.libs.fastutil.objects.Object2IntMap; import com.viaversion.viaversion.libs.fastutil.objects.Object2IntOpenHashMap; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.libs.gson.JsonObject; import com.viaversion.viaversion.util.Key; import java.util.Map; public final class BiomeMappings1_16_1 { private static final Object2IntMap MODERN_TO_LEGACY_ID = new Object2IntOpenHashMap<>(); private static final Object2IntMap LEGACY_BIOMES = new Object2IntOpenHashMap<>(); static { LEGACY_BIOMES.defaultReturnValue(-1); MODERN_TO_LEGACY_ID.defaultReturnValue(-1); add(0, "ocean"); add(1, "plains"); add(2, "desert"); add(3, "mountains"); add(4, "forest"); add(5, "taiga"); add(6, "swamp"); add(7, "river"); add(8, "nether"); add(9, "the_end"); add(10, "frozen_ocean"); add(11, "frozen_river"); add(12, "snowy_tundra"); add(13, "snowy_mountains"); add(14, "mushroom_fields"); add(15, "mushroom_field_shore"); add(16, "beach"); add(17, "desert_hills"); add(18, "wooded_hills"); add(19, "taiga_hills"); add(20, "mountain_edge"); add(21, "jungle"); add(22, "jungle_hills"); add(23, "jungle_edge"); add(24, "deep_ocean"); add(25, "stone_shore"); add(26, "snowy_beach"); add(27, "birch_forest"); add(28, "birch_forest_hills"); add(29, "dark_forest"); add(30, "snowy_taiga"); add(31, "snowy_taiga_hills"); add(32, "giant_tree_taiga"); add(33, "giant_tree_taiga_hills"); add(34, "wooded_mountains"); add(35, "savanna"); add(36, "savanna_plateau"); add(37, "badlands"); add(38, "wooded_badlands_plateau"); add(39, "badlands_plateau"); add(40, "small_end_islands"); add(41, "end_midlands"); add(42, "end_highlands"); add(43, "end_barrens"); add(44, "warm_ocean"); add(45, "lukewarm_ocean"); add(46, "cold_ocean"); add(47, "deep_warm_ocean"); add(48, "deep_lukewarm_ocean"); add(49, "deep_cold_ocean"); add(50, "deep_frozen_ocean"); add(127, "the_void"); add(129, "sunflower_plains"); add(130, "desert_lakes"); add(131, "gravelly_mountains"); add(132, "flower_forest"); add(133, "taiga_mountains"); add(134, "swamp_hills"); add(140, "ice_spikes"); add(149, "modified_jungle"); add(151, "modified_jungle_edge"); add(155, "tall_birch_forest"); add(156, "tall_birch_hills"); add(157, "dark_forest_hills"); add(158, "snowy_taiga_mountains"); add(160, "giant_spruce_taiga"); add(161, "giant_spruce_taiga_hills"); add(162, "modified_gravelly_mountains"); add(163, "shattered_savanna"); add(164, "shattered_savanna_plateau"); add(165, "eroded_badlands"); add(166, "modified_wooded_badlands_plateau"); add(167, "modified_badlands_plateau"); add(168, "bamboo_jungle"); add(169, "bamboo_jungle_hills"); // Include the legacy biomes themselves for (final Object2IntMap.Entry entry : LEGACY_BIOMES.object2IntEntrySet()) { MODERN_TO_LEGACY_ID.put(entry.getKey(), entry.getIntValue()); } final JsonObject mappings = BackwardsMappingDataLoader.INSTANCE.loadFromDataDir("biome-mappings.json"); for (final Map.Entry entry : mappings.entrySet()) { final int legacyBiome = LEGACY_BIOMES.getInt(entry.getValue().getAsString()); if (legacyBiome == -1) { Protocol1_16_2To1_16_1.LOGGER.warning("Unknown legacy biome: " + entry.getValue().getAsString()); continue; } MODERN_TO_LEGACY_ID.put(entry.getKey(), legacyBiome); } } private static void add(final int id, final String biome) { LEGACY_BIOMES.put(biome, id); } public static int toLegacyBiome(String biome) { final int legacyBiome = MODERN_TO_LEGACY_ID.getInt(Key.stripMinecraftNamespace(biome)); if (legacyBiome == -1) { if (Via.getConfig().logOtherConversionWarnings()) { Protocol1_16_2To1_16_1.LOGGER.warning("Biome with id " + biome + " has no legacy biome mapping (custom datapack?)"); } return 1; // Plains } return legacyBiome; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16_2to1_16_1/rewriter/BlockItemPacketRewriter1_16_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntArrayTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.Protocol1_16_2To1_16_1; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.BlockChangeRecord; import com.viaversion.viaversion.api.minecraft.BlockChangeRecord1_8; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_16; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_16_2; import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16; import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ServerboundPackets1_16; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ClientboundPackets1_16_2; import com.viaversion.viaversion.rewriter.RecipeRewriter; import com.viaversion.viaversion.util.Key; import org.checkerframework.checker.nullness.qual.Nullable; public class BlockItemPacketRewriter1_16_2 extends BackwardsItemRewriter { public BlockItemPacketRewriter1_16_2(Protocol1_16_2To1_16_1 protocol) { super(protocol, Types.ITEM1_13_2, Types.ITEM1_13_2_SHORT_ARRAY); } @Override protected void registerPackets() { new RecipeRewriter<>(protocol).register(ClientboundPackets1_16_2.UPDATE_RECIPES); protocol.registerClientbound(ClientboundPackets1_16_2.RECIPE, wrapper -> { wrapper.passthrough(Types.VAR_INT); wrapper.passthrough(Types.BOOLEAN); // Open wrapper.passthrough(Types.BOOLEAN); // Filter wrapper.passthrough(Types.BOOLEAN); // Furnace Open wrapper.passthrough(Types.BOOLEAN); // Filter furnace // Blast furnace / smoker wrapper.read(Types.BOOLEAN); wrapper.read(Types.BOOLEAN); wrapper.read(Types.BOOLEAN); wrapper.read(Types.BOOLEAN); }); protocol.getBlockRewriter().registerLevelChunk(ClientboundPackets1_16_2.LEVEL_CHUNK, ChunkType1_16_2.TYPE, ChunkType1_16.TYPE, (connection, chunk) -> { chunk.setIgnoreOldLightData(true); for (CompoundTag blockEntity : chunk.getBlockEntities()) { if (blockEntity != null) { handleBlockEntity(blockEntity); } } }); protocol.registerClientbound(ClientboundPackets1_16_2.BLOCK_ENTITY_DATA, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_14); map(Types.UNSIGNED_BYTE); handler(wrapper -> handleBlockEntity(wrapper.passthrough(Types.NAMED_COMPOUND_TAG))); } }); protocol.registerClientbound(ClientboundPackets1_16_2.SECTION_BLOCKS_UPDATE, ClientboundPackets1_16.CHUNK_BLOCKS_UPDATE, wrapper -> { long chunkPosition = wrapper.read(Types.LONG); wrapper.read(Types.BOOLEAN); // Ignore old light data int chunkX = (int) (chunkPosition >> 42); int chunkY = (int) (chunkPosition << 44 >> 44); int chunkZ = (int) (chunkPosition << 22 >> 42); wrapper.write(Types.INT, chunkX); wrapper.write(Types.INT, chunkZ); BlockChangeRecord[] blockChangeRecord = wrapper.read(Types.VAR_LONG_BLOCK_CHANGE_ARRAY); wrapper.write(Types.BLOCK_CHANGE_ARRAY, blockChangeRecord); for (int i = 0; i < blockChangeRecord.length; i++) { BlockChangeRecord record = blockChangeRecord[i]; int blockId = protocol.getMappingData().getNewBlockStateId(record.getBlockId()); // Relative y -> absolute y blockChangeRecord[i] = new BlockChangeRecord1_8(record.getSectionX(), record.getY(chunkY), record.getSectionZ(), blockId); } }); protocol.registerServerbound(ServerboundPackets1_16.EDIT_BOOK, wrapper -> handleItemToServer(wrapper.user(), wrapper.passthrough(Types.ITEM1_13_2))); } @Override public @Nullable Item handleItemToClient(final UserConnection connection, @Nullable final Item item) { if (item != null && item.tag() != null) { addValueHashAsId(item.tag()); } return super.handleItemToClient(connection, item); } private void handleBlockEntity(CompoundTag tag) { String id = tag.getString("id"); if (id != null && Key.stripMinecraftNamespace(id).equals("skull")) { addValueHashAsId(tag); } } private void addValueHashAsId(CompoundTag tag) { // Workaround an old client bug: MC-68487 CompoundTag skullOwnerTag = tag.getCompoundTag("SkullOwner"); if (skullOwnerTag == null) return; if (!skullOwnerTag.contains("Id")) return; CompoundTag properties = skullOwnerTag.getCompoundTag("Properties"); if (properties == null) return; ListTag textures = properties.getListTag("textures", CompoundTag.class); if (textures == null) return; CompoundTag first = !textures.isEmpty() ? textures.get(0) : null; if (first == null) return; // Make the client cache the skinprofile over this uuid int hashCode = first.get("Value").getValue().hashCode(); int[] uuidIntArray = {hashCode, 0, 0, 0}; skullOwnerTag.put("Id", new IntArrayTag(uuidIntArray)); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16_2to1_16_1/rewriter/CommandRewriter1_16_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.rewriter; import com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.Protocol1_16_2To1_16_1; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ClientboundPackets1_16_2; import com.viaversion.viaversion.rewriter.CommandRewriter; import org.checkerframework.checker.nullness.qual.Nullable; public class CommandRewriter1_16_2 extends CommandRewriter { public CommandRewriter1_16_2(Protocol1_16_2To1_16_1 protocol) { super(protocol); this.parserHandlers.put("minecraft:angle", wrapper -> wrapper.write(Types.VAR_INT, 0)); // Single word } @Override public @Nullable String handleArgumentType(String argumentType) { if (argumentType.equals("minecraft:angle")) { return "brigadier:string"; } return super.handleArgumentType(argumentType); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16_2to1_16_1/rewriter/EntityPacketRewriter1_16_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.rewriter; import com.google.common.collect.Sets; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.Protocol1_16_2To1_16_1; import com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.storage.BiomeStorage; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_16_2; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_16; import com.viaversion.viaversion.protocols.v1_15_2to1_16.data.DimensionRegistries1_16; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ClientboundPackets1_16_2; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.TagUtil; import java.util.Set; public class EntityPacketRewriter1_16_2 extends EntityRewriter { private final Set oldDimensions = Sets.newHashSet("minecraft:overworld", "minecraft:the_nether", "minecraft:the_end"); private boolean warned; public EntityPacketRewriter1_16_2(Protocol1_16_2To1_16_1 protocol) { super(protocol); } @Override protected void registerPackets() { registerTracker(ClientboundPackets1_16_2.ADD_EXPERIENCE_ORB, EntityTypes1_16_2.EXPERIENCE_ORB); registerTracker(ClientboundPackets1_16_2.ADD_PAINTING, EntityTypes1_16_2.PAINTING); registerTracker(ClientboundPackets1_16_2.ADD_PLAYER, EntityTypes1_16_2.PLAYER); registerSetEntityData(ClientboundPackets1_16_2.SET_ENTITY_DATA, Types1_16.ENTITY_DATA_LIST); protocol.registerClientbound(ClientboundPackets1_16_2.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // Entity ID handler(wrapper -> { boolean hardcore = wrapper.read(Types.BOOLEAN); short gamemode = wrapper.read(Types.BYTE); if (hardcore) { gamemode |= 0x08; } wrapper.write(Types.UNSIGNED_BYTE, gamemode); }); map(Types.BYTE); // Previous Gamemode map(Types.STRING_ARRAY); // World List handler(wrapper -> { CompoundTag registry = wrapper.read(Types.NAMED_COMPOUND_TAG); if (wrapper.user().getProtocolInfo().protocolVersion().olderThanOrEqualTo(ProtocolVersion.v1_15_2)) { // Store biomes for <1.16 client handling ListTag biomes = TagUtil.getRegistryEntries(registry, "worldgen/biome"); BiomeStorage biomeStorage = wrapper.user().get(BiomeStorage.class); biomeStorage.clear(); for (CompoundTag biome : biomes) { StringTag name = biome.getStringTag("name"); NumberTag id = biome.getNumberTag("id"); biomeStorage.addBiome(name.getValue(), id.asInt()); } } else if (!warned && !ViaBackwards.getConfig().suppressEmulationWarnings()) { warned = true; protocol.getLogger().warning("1.16 and 1.16.1 clients are only partially supported and may have wrong biomes displayed."); } // Just screw the registry and write the defaults for 1.16 and 1.16.1 clients wrapper.write(Types.NAMED_COMPOUND_TAG, DimensionRegistries1_16.getDimensionsTag()); CompoundTag dimensionData = wrapper.read(Types.NAMED_COMPOUND_TAG); wrapper.write(Types.STRING, getDimensionFromData(dimensionData)); }); map(Types.STRING); // Dimension handler(wrapper -> { final String world = wrapper.get(Types.STRING, 1); trackWorld(wrapper.user(), world); }); map(Types.LONG); // Seed handler(wrapper -> { int maxPlayers = wrapper.read(Types.VAR_INT); wrapper.write(Types.UNSIGNED_BYTE, (short) Math.min(maxPlayers, 255)); }); // ... handler(getPlayerTrackerHandler()); } }); protocol.registerClientbound(ClientboundPackets1_16_2.RESPAWN, wrapper -> { CompoundTag dimensionData = wrapper.read(Types.NAMED_COMPOUND_TAG); wrapper.write(Types.STRING, getDimensionFromData(dimensionData)); final String world = wrapper.passthrough(Types.STRING); trackWorld(wrapper.user(), world); }); } private String getDimensionFromData(CompoundTag dimensionData) { // This may technically break other custom dimension settings for 1.16/1.16.1 clients, so those cases are considered semi "unsupported" here StringTag effectsLocation = dimensionData.getStringTag("effects"); return effectsLocation != null && oldDimensions.contains(Key.namespaced(effectsLocation.getValue())) ? effectsLocation.getValue() : "minecraft:overworld"; } @Override protected void registerRewrites() { registerEntityDataTypeHandler(Types1_16.ENTITY_DATA_TYPES.itemType, null, Types1_16.ENTITY_DATA_TYPES.optionalBlockStateType, Types1_16.ENTITY_DATA_TYPES.particleType, Types1_16.ENTITY_DATA_TYPES.componentType, Types1_16.ENTITY_DATA_TYPES.optionalComponentType); filter().type(EntityTypes1_16_2.ABSTRACT_PIGLIN).index(15).toIndex(16); filter().type(EntityTypes1_16_2.ABSTRACT_PIGLIN).index(16).toIndex(15); } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); mapEntityTypeWithData(EntityTypes1_16_2.PIGLIN_BRUTE, EntityTypes1_16_2.PIGLIN).jsonName(); } @Override public EntityType typeFromId(int typeId) { return EntityTypes1_16_2.getTypeFromId(typeId); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16_2to1_16_1/storage/BiomeStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.storage; import com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.data.BiomeMappings1_16_1; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntOpenHashMap; public final class BiomeStorage implements StorableObject { private final Int2IntMap modernToLegacyBiomes = new Int2IntOpenHashMap(); public BiomeStorage() { modernToLegacyBiomes.defaultReturnValue(-1); } public void addBiome(final String biome, final int id) { modernToLegacyBiomes.put(id, BiomeMappings1_16_1.toLegacyBiome(biome)); } public int legacyBiome(final int biome) { return modernToLegacyBiomes.get(biome); } public void clear() { modernToLegacyBiomes.clear(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16_3to1_16_2/Protocol1_16_3To1_16_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16_3to1_16_2; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ClientboundPackets1_16_2; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ServerboundPackets1_16_2; public class Protocol1_16_3To1_16_2 extends BackwardsProtocol { public Protocol1_16_3To1_16_2() { super(ClientboundPackets1_16_2.class, ClientboundPackets1_16_2.class, ServerboundPackets1_16_2.class, ServerboundPackets1_16_2.class); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16_4to1_16_3/Protocol1_16_4To1_16_3.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16_4to1_16_3; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.protocol.v1_16_4to1_16_3.storage.PlayerHandStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ClientboundPackets1_16_2; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ServerboundPackets1_16_2; public class Protocol1_16_4To1_16_3 extends BackwardsProtocol { public Protocol1_16_4To1_16_3() { super(ClientboundPackets1_16_2.class, ClientboundPackets1_16_2.class, ServerboundPackets1_16_2.class, ServerboundPackets1_16_2.class); } @Override protected void registerPackets() { registerServerbound(ServerboundPackets1_16_2.EDIT_BOOK, new PacketHandlers() { @Override public void register() { map(Types.ITEM1_13_2); map(Types.BOOLEAN); handler(wrapper -> { int slot = wrapper.read(Types.VAR_INT); if (slot == 1) { wrapper.write(Types.VAR_INT, 40); // offhand } else { wrapper.write(Types.VAR_INT, wrapper.user().get(PlayerHandStorage.class).getCurrentHand()); } }); } }); registerServerbound(ServerboundPackets1_16_2.SET_CARRIED_ITEM, wrapper -> { short slot = wrapper.passthrough(Types.SHORT); wrapper.user().get(PlayerHandStorage.class).setCurrentHand(slot); }); } @Override public void init(UserConnection user) { user.put(new PlayerHandStorage()); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16_4to1_16_3/storage/PlayerHandStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16_4to1_16_3.storage; import com.viaversion.viaversion.api.connection.StorableObject; public class PlayerHandStorage implements StorableObject { private int currentHand; public int getCurrentHand() { return currentHand; } public void setCurrentHand(int currentHand) { this.currentHand = currentHand; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16to1_15_2/Protocol1_16To1_15_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16to1_15_2; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.data.BackwardsMappingData1_16; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.rewriter.BlockItemPacketRewriter1_16; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.rewriter.CommandRewriter1_16; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.rewriter.EntityPacketRewriter1_16; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.rewriter.TranslatableRewriter1_16; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.storage.PlayerAttributesStorage; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.storage.PlayerSneakStorage; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.storage.WorldNameTracker; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_16; import com.viaversion.viaversion.api.protocol.packet.State; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.libs.gson.JsonObject; import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets; import com.viaversion.viaversion.protocols.base.ClientboundStatusPackets; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ServerboundPackets1_14; import com.viaversion.viaversion.protocols.v1_14_4to1_15.packet.ClientboundPackets1_15; import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16; import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ServerboundPackets1_16; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.util.GsonUtil; import java.util.UUID; public class Protocol1_16To1_15_2 extends BackwardsProtocol { public static final BackwardsMappingData1_16 MAPPINGS = new BackwardsMappingData1_16(); private final EntityPacketRewriter1_16 entityRewriter = new EntityPacketRewriter1_16(this); private final BlockItemPacketRewriter1_16 blockItemPackets = new BlockItemPacketRewriter1_16(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final TranslatableRewriter1_16 translatableRewriter = new TranslatableRewriter1_16(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BlockRewriter blockRewriter = BlockRewriter.for1_14(this); public Protocol1_16To1_15_2() { super(ClientboundPackets1_16.class, ClientboundPackets1_15.class, ServerboundPackets1_16.class, ServerboundPackets1_14.class); } @Override protected void registerPackets() { super.registerPackets(); new CommandRewriter1_16(this).registerDeclareCommands(ClientboundPackets1_16.COMMANDS); registerClientbound(State.STATUS, ClientboundStatusPackets.STATUS_RESPONSE, wrapper -> { String original = wrapper.passthrough(Types.STRING); JsonObject object = GsonUtil.getGson().fromJson(original, JsonObject.class); JsonElement description = object.get("description"); if (description == null) return; translatableRewriter.processText(wrapper.user(), description); wrapper.set(Types.STRING, 0, object.toString()); }); replaceClientbound(ClientboundPackets1_16.CHAT, new PacketHandlers() { @Override public void register() { handler(wrapper -> translatableRewriter.processText(wrapper.user(), wrapper.passthrough(Types.COMPONENT))); map(Types.BYTE); read(Types.UUID); // Sender } }); replaceClientbound(ClientboundPackets1_16.OPEN_SCREEN, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Window Id map(Types.VAR_INT); // Window Type handler(wrapper -> translatableRewriter.processText(wrapper.user(), wrapper.passthrough(Types.COMPONENT))); handler(wrapper -> { int windowType = wrapper.get(Types.VAR_INT, 1); if (windowType == 20) { // Smithing table wrapper.set(Types.VAR_INT, 1, 7); // Open anvil inventory } else if (windowType > 20) { wrapper.set(Types.VAR_INT, 1, --windowType); } }); } }); // Login success registerClientbound(State.LOGIN, ClientboundLoginPackets.LOGIN_FINISHED, wrapper -> { // Transform uuid to plain string UUID uuid = wrapper.read(Types.UUID); wrapper.write(Types.STRING, uuid.toString()); }); tagRewriter.register(ClientboundPackets1_16.UPDATE_TAGS, RegistryType.ENTITY); registerServerbound(ServerboundPackets1_14.PLAYER_COMMAND, wrapper -> { wrapper.passthrough(Types.VAR_INT); // player id int action = wrapper.passthrough(Types.VAR_INT); if (action == 0) { wrapper.user().get(PlayerSneakStorage.class).setSneaking(true); } else if (action == 1) { wrapper.user().get(PlayerSneakStorage.class).setSneaking(false); } }); registerServerbound(ServerboundPackets1_14.INTERACT, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Entity Id int action = wrapper.passthrough(Types.VAR_INT); if (action == 0 || action == 2) { if (action == 2) { // Location wrapper.passthrough(Types.FLOAT); wrapper.passthrough(Types.FLOAT); wrapper.passthrough(Types.FLOAT); } wrapper.passthrough(Types.VAR_INT); // Hand } // New boolean: Whether the client is sneaking wrapper.write(Types.BOOLEAN, wrapper.user().get(PlayerSneakStorage.class).isSneaking()); }); registerServerbound(ServerboundPackets1_14.PLAYER_ABILITIES, wrapper -> { byte flags = wrapper.read(Types.BYTE); flags &= 2; // Only take the isFlying value (everything else has been removed and wasn't used anyways) wrapper.write(Types.BYTE, flags); wrapper.read(Types.FLOAT); wrapper.read(Types.FLOAT); }); cancelServerbound(ServerboundPackets1_14.SET_JIGSAW_BLOCK); } @Override public void init(UserConnection user) { user.addEntityTracker(this.getClass(), new EntityTrackerBase(user, EntityTypes1_16.PLAYER)); user.addClientWorld(this.getClass(), new ClientWorld()); user.put(new PlayerSneakStorage()); user.put(new WorldNameTracker()); user.put(new PlayerAttributesStorage()); } @Override public TranslatableRewriter1_16 getComponentRewriter() { return translatableRewriter; } @Override public BackwardsMappingData1_16 getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_16 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_16 getItemRewriter() { return blockItemPackets; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16to1_15_2/data/BackwardsMappingData1_16.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16to1_15_2.data; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viaversion.protocols.v1_15_2to1_16.Protocol1_15_2To1_16; import com.viaversion.viaversion.protocols.v1_15_2to1_16.data.AttributeMappings1_16; import com.viaversion.viaversion.util.Key; import java.util.HashMap; import java.util.Map; public class BackwardsMappingData1_16 extends BackwardsMappingData { private final Map attributeMappings = new HashMap<>(); public BackwardsMappingData1_16() { super("1.16", "1.15", Protocol1_15_2To1_16.class); } @Override protected void loadExtras(final CompoundTag data) { super.loadExtras(data); for (Map.Entry entry : AttributeMappings1_16.attributeIdentifierMappings().entrySet()) { attributeMappings.put(Key.stripMinecraftNamespace(entry.getValue()), entry.getKey()); } } public String mappedAttributeIdentifier(final String identifier) { return attributeMappings.getOrDefault(identifier, identifier); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16to1_15_2/data/MapColorMappings1_15_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16to1_15_2.data; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntOpenHashMap; public final class MapColorMappings1_15_2 { private static final Int2IntMap MAPPINGS = new Int2IntOpenHashMap(); static { MAPPINGS.put(208, 113); MAPPINGS.put(209, 114); MAPPINGS.put(210, 114); MAPPINGS.put(211, 112); MAPPINGS.put(212, 152); MAPPINGS.put(213, 83); MAPPINGS.put(214, 83); MAPPINGS.put(215, 155); MAPPINGS.put(216, 143); MAPPINGS.put(217, 115); MAPPINGS.put(218, 115); MAPPINGS.put(219, 143); MAPPINGS.put(220, 127); MAPPINGS.put(221, 127); MAPPINGS.put(222, 127); MAPPINGS.put(223, 95); MAPPINGS.put(224, 127); MAPPINGS.put(225, 127); MAPPINGS.put(226, 124); MAPPINGS.put(227, 95); MAPPINGS.put(228, 187); MAPPINGS.put(229, 155); MAPPINGS.put(230, 184); MAPPINGS.put(231, 187); MAPPINGS.put(232, 127); MAPPINGS.put(233, 124); MAPPINGS.put(234, 125); MAPPINGS.put(235, 127); } public static int getMappedColor(int color) { return MAPPINGS.getOrDefault(color, -1); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16to1_15_2/rewriter/BlockItemPacketRewriter1_16.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16to1_15_2.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntArrayTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.LongArrayTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.api.rewriters.EnchantmentRewriter; import com.viaversion.viabackwards.api.rewriters.MapColorRewriter; import com.viaversion.viabackwards.protocol.v1_16_2to1_16_1.storage.BiomeStorage; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.Protocol1_16To1_15_2; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.data.MapColorMappings1_15_2; 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.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_15; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_16; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.v1_13_2to1_14.packet.ServerboundPackets1_14; import com.viaversion.viaversion.protocols.v1_14_4to1_15.packet.ClientboundPackets1_15; import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16; import com.viaversion.viaversion.protocols.v1_15_2to1_16.rewriter.ItemPacketRewriter1_16; import com.viaversion.viaversion.rewriter.RecipeRewriter; import com.viaversion.viaversion.util.CompactArrayUtil; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.UUIDUtil; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; public class BlockItemPacketRewriter1_16 extends BackwardsItemRewriter { private EnchantmentRewriter enchantmentRewriter; public BlockItemPacketRewriter1_16(Protocol1_16To1_15_2 protocol) { super(protocol, Types.ITEM1_13_2, Types.ITEM1_13_2_SHORT_ARRAY); } @Override protected void registerPackets() { RecipeRewriter recipeRewriter = new RecipeRewriter<>(protocol); // Remove new smithing type, only in this handler protocol.registerClientbound(ClientboundPackets1_16.UPDATE_RECIPES, wrapper -> { int size = wrapper.passthrough(Types.VAR_INT); int newSize = size; for (int i = 0; i < size; i++) { String originalType = wrapper.read(Types.STRING); String type = Key.stripMinecraftNamespace(originalType); if (type.equals("smithing")) { newSize--; wrapper.read(Types.STRING); wrapper.read(Types.ITEM1_13_2_ARRAY); wrapper.read(Types.ITEM1_13_2_ARRAY); wrapper.read(Types.ITEM1_13_2); continue; } wrapper.write(Types.STRING, originalType); wrapper.passthrough(Types.STRING); // Recipe Identifier recipeRewriter.handleRecipeType(wrapper, type); } wrapper.set(Types.VAR_INT, 0, newSize); }); protocol.getBlockRewriter().registerLevelChunk(ClientboundPackets1_16.LEVEL_CHUNK, ChunkType1_16.TYPE, ChunkType1_15.TYPE, (connection, chunk) -> { CompoundTag heightMaps = chunk.getHeightMap(); for (Tag heightMapTag : heightMaps.values()) { if (!(heightMapTag instanceof LongArrayTag heightMap)) { continue; } int[] heightMapData = new int[256]; CompactArrayUtil.iterateCompactArrayWithPadding(9, heightMapData.length, heightMap.getValue(), (i, v) -> heightMapData[i] = v); heightMap.setValue(CompactArrayUtil.createCompactArray(9, heightMapData.length, i -> heightMapData[i])); } if (chunk.isBiomeData()) { if (connection.getProtocolInfo().serverProtocolVersion().newerThanOrEqualTo(ProtocolVersion.v1_16_2)) { BiomeStorage biomeStorage = connection.get(BiomeStorage.class); for (int i = 0; i < 1024; i++) { int biome = chunk.getBiomeData()[i]; int legacyBiome = biomeStorage.legacyBiome(biome); if (legacyBiome == -1) { protocol.getLogger().warning("Biome sent that does not exist in the biome registry: " + biome); legacyBiome = 1; } chunk.getBiomeData()[i] = legacyBiome; } } else { for (int i = 0; i < 1024; i++) { int biome = chunk.getBiomeData()[i]; switch (biome) { case 170, 171, 172, 173 -> chunk.getBiomeData()[i] = 8; } } } } if (chunk.getBlockEntities() == null) return; for (CompoundTag blockEntity : chunk.getBlockEntities()) { handleBlockEntity(blockEntity); } }); protocol.registerClientbound(ClientboundPackets1_16.SET_EQUIPMENT, ClientboundPackets1_15.SET_EQUIPPED_ITEM, wrapper -> { int entityId = wrapper.passthrough(Types.VAR_INT); List equipmentData = new ArrayList<>(); byte slot; do { slot = wrapper.read(Types.BYTE); Item item = handleItemToClient(wrapper.user(), wrapper.read(Types.ITEM1_13_2)); int rawSlot = slot & 0x7F; equipmentData.add(new EquipmentData(rawSlot, item)); } while ((slot & 0xFFFFFF80) != 0); // Send first data in the current packet EquipmentData firstData = equipmentData.get(0); wrapper.write(Types.VAR_INT, firstData.slot); wrapper.write(Types.ITEM1_13_2, firstData.item); // If there are more items, send new packets for them for (int i = 1; i < equipmentData.size(); i++) { PacketWrapper equipmentPacket = wrapper.create(ClientboundPackets1_15.SET_EQUIPPED_ITEM); EquipmentData data = equipmentData.get(i); equipmentPacket.write(Types.VAR_INT, entityId); equipmentPacket.write(Types.VAR_INT, data.slot); equipmentPacket.write(Types.ITEM1_13_2, data.item); equipmentPacket.send(Protocol1_16To1_15_2.class); } }); protocol.registerClientbound(ClientboundPackets1_16.LIGHT_UPDATE, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // x map(Types.VAR_INT); // y read(Types.BOOLEAN); } }); protocol.registerClientbound(ClientboundPackets1_16.CONTAINER_SET_DATA, new PacketHandlers() { @Override public void register() { map(Types.UNSIGNED_BYTE); // Window id map(Types.SHORT); // Property map(Types.SHORT); // Value handler(wrapper -> { short property = wrapper.get(Types.SHORT, 0); if (property >= 4 && property <= 6) { // Enchantment id short enchantmentId = wrapper.get(Types.SHORT, 1); if (enchantmentId > 11) { // soul_speed wrapper.set(Types.SHORT, 1, --enchantmentId); } else if (enchantmentId == 11) { wrapper.set(Types.SHORT, 1, (short) 9); } } }); } }); protocol.registerClientbound(ClientboundPackets1_16.MAP_ITEM_DATA, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Map ID map(Types.BYTE); // Scale map(Types.BOOLEAN); // Tracking Position map(Types.BOOLEAN); // Locked handler(MapColorRewriter.getRewriteHandler(MapColorMappings1_15_2::getMappedColor)); } }); protocol.registerClientbound(ClientboundPackets1_16.BLOCK_ENTITY_DATA, wrapper -> { wrapper.passthrough(Types.BLOCK_POSITION1_14); // Position wrapper.passthrough(Types.UNSIGNED_BYTE); // Action CompoundTag tag = wrapper.passthrough(Types.NAMED_COMPOUND_TAG); handleBlockEntity(tag); }); protocol.registerServerbound(ServerboundPackets1_14.EDIT_BOOK, wrapper -> handleItemToServer(wrapper.user(), wrapper.passthrough(Types.ITEM1_13_2))); } private void handleBlockEntity(CompoundTag tag) { String id = tag.getString("id"); if (id == null) return; id = Key.namespaced(id); if (id.equals("minecraft:conduit")) { Tag targetUuidTag = tag.remove("Target"); if (!(targetUuidTag instanceof IntArrayTag)) return; // Target -> target_uuid UUID targetUuid = UUIDUtil.fromIntArray((int[]) targetUuidTag.getValue()); tag.putString("target_uuid", targetUuid.toString()); } else if (id.equals("minecraft:skull")) { if (!(tag.remove("SkullOwner") instanceof CompoundTag skullOwnerTag)) return; if (skullOwnerTag.remove("Id") instanceof IntArrayTag ownerUuidTag) { UUID ownerUuid = UUIDUtil.fromIntArray(ownerUuidTag.getValue()); skullOwnerTag.putString("Id", ownerUuid.toString()); } // SkullOwner -> Owner CompoundTag ownerTag = new CompoundTag(); for (Map.Entry entry : skullOwnerTag) { ownerTag.put(entry.getKey(), entry.getValue()); } tag.put("Owner", ownerTag); } } @Override protected void registerRewrites() { enchantmentRewriter = new EnchantmentRewriter(this); enchantmentRewriter.registerEnchantment("minecraft:soul_speed", "§7Soul Speed"); } @Override public Item handleItemToClient(UserConnection connection, Item item) { if (item == null) return null; item = super.handleItemToClient(connection, item); CompoundTag tag = item.tag(); if (item.identifier() == 771 && tag != null) { CompoundTag ownerTag = tag.getCompoundTag("SkullOwner"); if (ownerTag != null) { IntArrayTag idTag = ownerTag.getIntArrayTag("Id"); if (idTag != null) { UUID ownerUuid = UUIDUtil.fromIntArray(idTag.getValue()); ownerTag.putString("Id", ownerUuid.toString()); } } } // Handle hover event changes in written book pages if (item.identifier() == 759 && tag != null) { ListTag pagesTag = tag.getListTag("pages", StringTag.class); if (pagesTag != null) { for (StringTag page : pagesTag) { JsonElement jsonElement = protocol.getComponentRewriter().processText(connection, page.getValue()); page.setValue(jsonElement.toString()); } } } ItemPacketRewriter1_16.newToOldAttributes(item); enchantmentRewriter.handleToClient(item); return item; } @Override public Item handleItemToServer(UserConnection connection, Item item) { if (item == null) return null; int identifier = item.identifier(); item = super.handleItemToServer(connection, item); CompoundTag tag = item.tag(); if (identifier == 771 && tag != null) { CompoundTag ownerTag = tag.getCompoundTag("SkullOwner"); if (ownerTag != null) { StringTag idTag = ownerTag.getStringTag("Id"); if (idTag != null) { UUID ownerUuid = UUID.fromString(idTag.getValue()); ownerTag.put("Id", new IntArrayTag(UUIDUtil.toIntArray(ownerUuid))); } } } ItemPacketRewriter1_16.oldToNewAttributes(item); enchantmentRewriter.handleToServer(item); return item; } private record EquipmentData(int slot, Item item) { } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16to1_15_2/rewriter/CommandRewriter1_16.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16to1_15_2.rewriter; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.Protocol1_16To1_15_2; import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16; import com.viaversion.viaversion.rewriter.CommandRewriter; import org.checkerframework.checker.nullness.qual.Nullable; public class CommandRewriter1_16 extends CommandRewriter { public CommandRewriter1_16(Protocol1_16To1_15_2 protocol) { super(protocol); } @Override public @Nullable String handleArgumentType(String argumentType) { if (argumentType.equals("minecraft:uuid")) { return "minecraft:game_profile"; } return super.handleArgumentType(argumentType); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16to1_15_2/rewriter/EntityPacketRewriter1_16.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16to1_15_2.rewriter; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.Protocol1_16To1_15_2; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.storage.PlayerAttributesStorage; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.storage.WolfDataMaskStorage; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.storage.WorldNameTracker; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.data.entity.StoredEntityData; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_16; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.entitydata.EntityDataType; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.protocol.remapper.ValueTransformer; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_14; import com.viaversion.viaversion.api.type.types.version.Types1_16; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.v1_14_4to1_15.packet.ClientboundPackets1_15; import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16; import com.viaversion.viaversion.util.Key; import java.util.UUID; public class EntityPacketRewriter1_16 extends EntityRewriter { private final ValueTransformer dimensionTransformer = new ValueTransformer<>(Types.STRING, Types.INT) { @Override public Integer transform(PacketWrapper wrapper, String input) { input = Key.namespaced(input); return switch (input) { case "minecraft:the_nether" -> -1; case "minecraft:the_end" -> 1; default -> 0; // Including overworld }; } }; public EntityPacketRewriter1_16(Protocol1_16To1_15_2 protocol) { super(protocol); } @Override protected void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_16.ADD_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity id map(Types.UUID); // 1 - Entity UUID map(Types.VAR_INT); // 2 - Entity Type map(Types.DOUBLE); // 3 - X map(Types.DOUBLE); // 4 - Y map(Types.DOUBLE); // 5 - Z map(Types.BYTE); // 6 - Pitch map(Types.BYTE); // 7 - Yaw map(Types.INT); // 8 - Data handler(wrapper -> { EntityType entityType = typeFromId(wrapper.get(Types.VAR_INT, 1)); if (entityType == EntityTypes1_16.LIGHTNING_BOLT) { // Map to old weather entity packet wrapper.cancel(); PacketWrapper spawnLightningPacket = wrapper.create(ClientboundPackets1_15.ADD_GLOBAL_ENTITY); spawnLightningPacket.write(Types.VAR_INT, wrapper.get(Types.VAR_INT, 0)); // Entity id spawnLightningPacket.write(Types.BYTE, (byte) 1); // Lightning type spawnLightningPacket.write(Types.DOUBLE, wrapper.get(Types.DOUBLE, 0)); // X spawnLightningPacket.write(Types.DOUBLE, wrapper.get(Types.DOUBLE, 1)); // Y spawnLightningPacket.write(Types.DOUBLE, wrapper.get(Types.DOUBLE, 2)); // Z spawnLightningPacket.send(Protocol1_16To1_15_2.class); } }); handler(getSpawnTrackerWithDataHandler()); } }); protocol.registerClientbound(ClientboundPackets1_16.RESPAWN, new PacketHandlers() { @Override public void register() { map(dimensionTransformer); // Dimension Type handler(wrapper -> { // Grab the tracker for world names WorldNameTracker worldNameTracker = wrapper.user().get(WorldNameTracker.class); String nextWorldName = wrapper.read(Types.STRING); // World Name wrapper.passthrough(Types.LONG); // Seed wrapper.passthrough(Types.UNSIGNED_BYTE); // Gamemode wrapper.read(Types.BYTE); // Previous gamemode // Grab client world ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_16To1_15_2.class); int dimension = wrapper.get(Types.INT, 0); // Send a dummy respawn with a different dimension if the world name was different and the same dimension was used if (clientWorld.getEnvironment() != null && dimension == clientWorld.getEnvironment().id() && (wrapper.user().isClientSide() || Via.getPlatform().isProxy() || wrapper.user().getProtocolInfo().protocolVersion().olderThanOrEqualTo(ProtocolVersion.v1_12_2) // Hotfix for https://github.com/ViaVersion/ViaBackwards/issues/381 || !nextWorldName.equals(worldNameTracker.getWorldName()))) { PacketWrapper packet = wrapper.create(ClientboundPackets1_15.RESPAWN); packet.write(Types.INT, dimension == 0 ? -1 : 0); packet.write(Types.LONG, 0L); packet.write(Types.UNSIGNED_BYTE, (short) 0); packet.write(Types.STRING, "default"); packet.send(Protocol1_16To1_15_2.class); } if (clientWorld.setEnvironment(dimension)) { tracker(wrapper.user()).clearEntities(); } wrapper.write(Types.STRING, "default"); // Level type wrapper.read(Types.BOOLEAN); // Debug if (wrapper.read(Types.BOOLEAN)) { wrapper.set(Types.STRING, 0, "flat"); } final PlayerAttributesStorage attributes = wrapper.user().get(PlayerAttributesStorage.class); final boolean keepPlayerAttributes = wrapper.read(Types.BOOLEAN); if (keepPlayerAttributes) { // Ensure packet order wrapper.send(Protocol1_16To1_15_2.class); wrapper.cancel(); attributes.sendAttributes(wrapper.user(), tracker(wrapper.user()).clientEntityId()); } else { attributes.clearAttributes(); } // Finally update the world name worldNameTracker.setWorldName(nextWorldName); }); } }); protocol.registerClientbound(ClientboundPackets1_16.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // Entity ID map(Types.UNSIGNED_BYTE); // Gamemode read(Types.BYTE); // Previous gamemode read(Types.STRING_ARRAY); // World list read(Types.NAMED_COMPOUND_TAG); // whatever this is map(dimensionTransformer); // Dimension Type handler(wrapper -> { WorldNameTracker worldNameTracker = wrapper.user().get(WorldNameTracker.class); worldNameTracker.setWorldName(wrapper.read(Types.STRING)); // Save the world name }); map(Types.LONG); // Seed map(Types.UNSIGNED_BYTE); // Max players handler(wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_16To1_15_2.class); clientWorld.setEnvironment(wrapper.get(Types.INT, 1)); wrapper.write(Types.STRING, "default"); // Level type wrapper.passthrough(Types.VAR_INT); // View distance wrapper.passthrough(Types.BOOLEAN); // Reduced debug info wrapper.passthrough(Types.BOOLEAN); // Show death screen wrapper.read(Types.BOOLEAN); // Debug if (wrapper.read(Types.BOOLEAN)) { wrapper.set(Types.STRING, 0, "flat"); } }); handler(playerTrackerHandler()); } }); registerTracker(ClientboundPackets1_16.ADD_EXPERIENCE_ORB, EntityTypes1_16.EXPERIENCE_ORB); // F Spawn Global Object, it is no longer with us :( registerTracker(ClientboundPackets1_16.ADD_PAINTING, EntityTypes1_16.PAINTING); registerTracker(ClientboundPackets1_16.ADD_PLAYER, EntityTypes1_16.PLAYER); registerSetEntityData(ClientboundPackets1_16.SET_ENTITY_DATA, Types1_16.ENTITY_DATA_LIST, Types1_14.ENTITY_DATA_LIST); protocol.registerClientbound(ClientboundPackets1_16.UPDATE_ATTRIBUTES, wrapper -> { final PlayerAttributesStorage attributes = wrapper.user().get(PlayerAttributesStorage.class); final int entityId = wrapper.passthrough(Types.VAR_INT); final int size = wrapper.passthrough(Types.INT); for (int i = 0; i < size; i++) { final String identifier = Key.stripMinecraftNamespace(wrapper.read(Types.STRING)); final String mappedIdentifier = protocol.getMappingData().mappedAttributeIdentifier(identifier); wrapper.write(Types.STRING, mappedIdentifier); final double value = wrapper.passthrough(Types.DOUBLE); final int count = wrapper.passthrough(Types.VAR_INT); final var modifiers = new PlayerAttributesStorage.AttributeModifier[count]; for (int j = 0; j < count; j++) { final UUID uuid = wrapper.passthrough(Types.UUID); final double amount = wrapper.passthrough(Types.DOUBLE); final byte operation = wrapper.passthrough(Types.BYTE); modifiers[j] = new PlayerAttributesStorage.AttributeModifier(uuid, amount, operation); } if (entityId == tracker(wrapper.user()).clientEntityId()) { attributes.addAttribute(mappedIdentifier, new PlayerAttributesStorage.Attribute(value, modifiers)); } } }); protocol.registerClientbound(ClientboundPackets1_16.PLAYER_INFO, wrapper -> { int action = wrapper.passthrough(Types.VAR_INT); int playerCount = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < playerCount; i++) { wrapper.passthrough(Types.UUID); if (action == 0) { // Add wrapper.passthrough(Types.STRING); wrapper.passthrough(Types.PROFILE_PROPERTY_ARRAY); wrapper.passthrough(Types.VAR_INT); wrapper.passthrough(Types.VAR_INT); // Display Name protocol.getComponentRewriter().processText(wrapper.user(), wrapper.passthrough(Types.OPTIONAL_COMPONENT)); } else if (action == 1) { // Update Game Mode wrapper.passthrough(Types.VAR_INT); } else if (action == 2) { // Update Ping wrapper.passthrough(Types.VAR_INT); } else if (action == 3) { // Update Display Name // Display name protocol.getComponentRewriter().processText(wrapper.user(), wrapper.passthrough(Types.OPTIONAL_COMPONENT)); } // 4 = Remove Player } }); } @Override protected void registerRewrites() { filter().handler((event, data) -> { data.setDataType(Types1_14.ENTITY_DATA_TYPES.byId(data.dataType().typeId())); EntityDataType type = data.dataType(); if (type == Types1_14.ENTITY_DATA_TYPES.itemType) { data.setValue(protocol.getItemRewriter().handleItemToClient(event.user(), (Item) data.getValue())); } else if (type == Types1_14.ENTITY_DATA_TYPES.optionalBlockStateType) { data.setValue(protocol.getMappingData().getNewBlockStateId((int) data.getValue())); } else if (type == Types1_14.ENTITY_DATA_TYPES.particleType) { protocol.getParticleRewriter().rewriteParticle(event.user(), (Particle) data.getValue()); } else if (type == Types1_14.ENTITY_DATA_TYPES.optionalComponentType) { JsonElement text = data.value(); if (text != null) { protocol.getComponentRewriter().processText(event.user(), text); } } }); filter().type(EntityTypes1_16.ZOGLIN).cancel(16); filter().type(EntityTypes1_16.HOGLIN).cancel(15); filter().type(EntityTypes1_16.PIGLIN).cancel(16); filter().type(EntityTypes1_16.PIGLIN).cancel(17); filter().type(EntityTypes1_16.PIGLIN).cancel(18); filter().type(EntityTypes1_16.STRIDER).index(15).handler((event, data) -> { boolean baby = data.value(); data.setTypeAndValue(Types1_14.ENTITY_DATA_TYPES.varIntType, baby ? 1 : 3); }); filter().type(EntityTypes1_16.STRIDER).cancel(16); filter().type(EntityTypes1_16.STRIDER).cancel(17); filter().type(EntityTypes1_16.STRIDER).cancel(18); filter().type(EntityTypes1_16.FISHING_BOBBER).cancel(8); filter().type(EntityTypes1_16.ABSTRACT_ARROW).cancel(8); filter().type(EntityTypes1_16.ABSTRACT_ARROW).handler((event, data) -> { if (event.index() >= 8) { event.setIndex(event.index() + 1); } }); filter().type(EntityTypes1_16.WOLF).index(16).handler((event, data) -> { byte mask = data.value(); StoredEntityData entityData = tracker(event.user()).entityData(event.entityId()); entityData.put(new WolfDataMaskStorage(mask)); }); filter().type(EntityTypes1_16.WOLF).index(20).handler((event, data) -> { StoredEntityData entityData = tracker(event.user()).entityDataIfPresent(event.entityId()); byte previousMask = 0; if (entityData != null) { WolfDataMaskStorage wolfData = entityData.get(WolfDataMaskStorage.class); if (wolfData != null) { previousMask = wolfData.tameableMask(); } } int angerTime = data.value(); byte tameableMask = (byte) (angerTime > 0 ? previousMask | 2 : previousMask & -3); event.createExtraData(new EntityData(16, Types1_14.ENTITY_DATA_TYPES.byteType, tameableMask)); event.cancel(); }); } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); mapEntityTypeWithData(EntityTypes1_16.HOGLIN, EntityTypes1_16.COW).jsonName(); mapEntityTypeWithData(EntityTypes1_16.ZOGLIN, EntityTypes1_16.COW).jsonName(); mapEntityTypeWithData(EntityTypes1_16.PIGLIN, EntityTypes1_16.ZOMBIFIED_PIGLIN).jsonName(); mapEntityTypeWithData(EntityTypes1_16.STRIDER, EntityTypes1_16.MAGMA_CUBE).jsonName(); } @Override public EntityType typeFromId(int typeId) { return EntityTypes1_16.getTypeFromId(typeId); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16to1_15_2/rewriter/TranslatableRewriter1_16.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16to1_15_2.rewriter; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.Protocol1_16To1_15_2; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.libs.gson.JsonObject; import com.viaversion.viaversion.libs.gson.JsonPrimitive; import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16; import com.viaversion.viaversion.util.ComponentUtil; import com.viaversion.viaversion.util.SerializerVersion; public class TranslatableRewriter1_16 extends JsonNBTComponentRewriter { private static final ChatColor[] COLORS = { new ChatColor("black", 0x000000), new ChatColor("dark_blue", 0x0000aa), new ChatColor("dark_green", 0x00aa00), new ChatColor("dark_aqua", 0x00aaaa), new ChatColor("dark_red", 0xaa0000), new ChatColor("dark_purple", 0xaa00aa), new ChatColor("gold", 0xffaa00), new ChatColor("gray", 0xaaaaaa), new ChatColor("dark_gray", 0x555555), new ChatColor("blue", 0x5555ff), new ChatColor("green", 0x55ff55), new ChatColor("aqua", 0x55ffff), new ChatColor("red", 0xff5555), new ChatColor("light_purple", 0xff55ff), new ChatColor("yellow", 0xffff55), new ChatColor("white", 0xffffff) }; public TranslatableRewriter1_16(Protocol1_16To1_15_2 protocol) { super(protocol, ReadType.JSON); } @Override public void processText(UserConnection connection, JsonElement value) { super.processText(connection, value); if (value == null || !value.isJsonObject()) return; // c o l o r s JsonObject object = value.getAsJsonObject(); JsonPrimitive color = object.getAsJsonPrimitive("color"); if (color != null) { String colorName = color.getAsString(); if (!colorName.isEmpty() && colorName.charAt(0) == '#') { int rgb = Integer.parseInt(colorName.substring(1), 16); String closestChatColor = getClosestChatColor(rgb); object.addProperty("color", closestChatColor); } } JsonObject clickEvent = object.getAsJsonObject("clickEvent"); if (clickEvent != null && clickEvent.has("action")) { String action = clickEvent.get("action").getAsString(); if (action.equals("copy_to_clipboard")) { clickEvent.addProperty("action", "suggest_command"); } } JsonObject hoverEvent = object.getAsJsonObject("hoverEvent"); if (hoverEvent == null || !hoverEvent.has("contents")) { return; } // show_text as chat component json, show_entity and show_item serialized as snbt JsonObject convertedObject = (JsonObject) ComponentUtil.convertJson(object, SerializerVersion.V1_16, SerializerVersion.V1_15); object.add("hoverEvent", convertedObject.getAsJsonObject("hoverEvent")); } private String getClosestChatColor(int rgb) { int r = (rgb >> 16) & 0xFF; int g = (rgb >> 8) & 0xFF; int b = rgb & 0xFF; ChatColor closest = null; int smallestDiff = 0; for (ChatColor color : COLORS) { if (color.rgb == rgb) { return color.colorName; } // Check by the greatest diff of the 3 values int rAverage = (color.r + r) / 2; int rDiff = color.r - r; int gDiff = color.g - g; int bDiff = color.b - b; int diff = ((2 + (rAverage >> 8)) * rDiff * rDiff) + (4 * gDiff * gDiff) + ((2 + ((255 - rAverage) >> 8)) * bDiff * bDiff); if (closest == null || diff < smallestDiff) { closest = color; smallestDiff = diff; } } return closest.colorName; } private static final class ChatColor { private final String colorName; private final int rgb; private final int r; private final int g; private final int b; ChatColor(String colorName, int rgb) { this.colorName = colorName; this.rgb = rgb; r = (rgb >> 16) & 0xFF; g = (rgb >> 8) & 0xFF; b = rgb & 0xFF; } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16to1_15_2/storage/PlayerAttributesStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16to1_15_2.storage; import com.viaversion.viabackwards.protocol.v1_16to1_15_2.Protocol1_16To1_15_2; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_14_4to1_15.packet.ClientboundPackets1_15; import java.util.HashMap; import java.util.Map; import java.util.UUID; public final class PlayerAttributesStorage implements StorableObject { private final Map attributes = new HashMap<>(); public void sendAttributes(final UserConnection connection, final int entityId) { final PacketWrapper updateAttributes = PacketWrapper.create(ClientboundPackets1_15.UPDATE_ATTRIBUTES, connection); updateAttributes.write(Types.VAR_INT, entityId); updateAttributes.write(Types.INT, attributes.size()); for (final Map.Entry attributeEntry : attributes.entrySet()) { final Attribute attribute = attributeEntry.getValue(); updateAttributes.write(Types.STRING, attributeEntry.getKey()); updateAttributes.write(Types.DOUBLE, attribute.value()); updateAttributes.write(Types.VAR_INT, attribute.modifiers().length); for (final AttributeModifier modifier : attribute.modifiers()) { updateAttributes.write(Types.UUID, modifier.uuid()); updateAttributes.write(Types.DOUBLE, modifier.amount()); updateAttributes.write(Types.BYTE, modifier.operation()); } } updateAttributes.send(Protocol1_16To1_15_2.class); } public void clearAttributes() { attributes.clear(); } public void addAttribute(final String key, final Attribute attribute) { attributes.put(key, attribute); } public record Attribute(double value, AttributeModifier[] modifiers) { } public record AttributeModifier(UUID uuid, double amount, byte operation) { } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16to1_15_2/storage/PlayerSneakStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16to1_15_2.storage; import com.viaversion.viaversion.api.connection.StorableObject; public class PlayerSneakStorage implements StorableObject { private boolean sneaking; public boolean isSneaking() { return sneaking; } public void setSneaking(boolean sneaking) { this.sneaking = sneaking; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16to1_15_2/storage/WolfDataMaskStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16to1_15_2.storage; public final class WolfDataMaskStorage { private byte tameableMask; public WolfDataMaskStorage(byte tameableMask) { this.tameableMask = tameableMask; } public void setTameableMask(byte tameableMask) { this.tameableMask = tameableMask; } public byte tameableMask() { return tameableMask; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_16to1_15_2/storage/WorldNameTracker.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_16to1_15_2.storage; import com.viaversion.viaversion.api.connection.StorableObject; public class WorldNameTracker implements StorableObject { private String worldName; public String getWorldName() { return worldName; } public void setWorldName(String worldName) { this.worldName = worldName; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_17_1to1_17/Protocol1_17_1To1_17.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_17_1to1_17; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.protocol.v1_17_1to1_17.storage.InventoryStateIds; import com.viaversion.viabackwards.protocol.v1_17to1_16_4.storage.PlayerLastCursorItem; 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_16_4to1_17.packet.ClientboundPackets1_17; import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ServerboundPackets1_17; import com.viaversion.viaversion.protocols.v1_17to1_17_1.packet.ClientboundPackets1_17_1; public final class Protocol1_17_1To1_17 extends BackwardsProtocol { private static final int MAX_PAGE_LENGTH = 8192; private static final int MAX_TITLE_LENGTH = 128; private static final int MAX_PAGES = 200; // Actually limited to 100 when handling the read packet, but /shrug public Protocol1_17_1To1_17() { super(ClientboundPackets1_17_1.class, ClientboundPackets1_17.class, ServerboundPackets1_17.class, ServerboundPackets1_17.class); } @Override protected void registerPackets() { registerClientbound(ClientboundPackets1_17_1.REMOVE_ENTITIES, null, wrapper -> { int[] entityIds = wrapper.read(Types.VAR_INT_ARRAY_PRIMITIVE); wrapper.cancel(); for (int entityId : entityIds) { // Send individual remove packets PacketWrapper newPacket = wrapper.create(ClientboundPackets1_17.REMOVE_ENTITY); newPacket.write(Types.VAR_INT, entityId); newPacket.send(Protocol1_17_1To1_17.class); } }); registerClientbound(ClientboundPackets1_17_1.CONTAINER_CLOSE, wrapper -> { short containerId = wrapper.passthrough(Types.UNSIGNED_BYTE); wrapper.user().get(InventoryStateIds.class).removeStateId(containerId); }); registerClientbound(ClientboundPackets1_17_1.CONTAINER_SET_SLOT, wrapper -> { byte containerId = wrapper.passthrough(Types.BYTE); int stateId = wrapper.read(Types.VAR_INT); wrapper.user().get(InventoryStateIds.class).setStateId(containerId, stateId); }); registerClientbound(ClientboundPackets1_17_1.CONTAINER_SET_CONTENT, wrapper -> { short containerId = wrapper.passthrough(Types.UNSIGNED_BYTE); int stateId = wrapper.read(Types.VAR_INT); wrapper.user().get(InventoryStateIds.class).setStateId(containerId, stateId); // Length is encoded as a var int in 1.17.1 wrapper.write(Types.ITEM1_13_2_SHORT_ARRAY, wrapper.read(Types.ITEM1_13_2_ARRAY)); // Carried item - forward as CONTAINER_SET_SLOT for <1.17 clients Item carried = wrapper.read(Types.ITEM1_13_2); PlayerLastCursorItem lastCursorItem = wrapper.user().get(PlayerLastCursorItem.class); if (lastCursorItem != null) { // For click drag ghost item fix -- since the state ID is always wrong, // the server always resends the entire window contents after a drag action, // which is useful since we need to update the carried item in preparation // for a subsequent drag lastCursorItem.setLastCursorItem(carried); // In 1.17.1+, the carried/cursor item is part of CONTAINER_SET_CONTENT, // but <1.17 clients don't have this field. Send it as a separate // CONTAINER_SET_SLOT packet so the client's cursor state is properly synced. // This fixes inventory desyncs when the server cancels InventoryClickEvents. PacketWrapper cursorPacket = wrapper.create(ClientboundPackets1_17.CONTAINER_SET_SLOT); cursorPacket.write(Types.BYTE, (byte) -1); // Window ID: -1 for cursor cursorPacket.write(Types.SHORT, (short) -1); // Slot: -1 for cursor cursorPacket.write(Types.ITEM1_13_2, carried); cursorPacket.send(Protocol1_17_1To1_17.class); } }); registerServerbound(ServerboundPackets1_17.CONTAINER_CLOSE, wrapper -> { byte containerId = wrapper.passthrough(Types.BYTE); wrapper.user().get(InventoryStateIds.class).removeStateId(containerId); }); registerServerbound(ServerboundPackets1_17.CONTAINER_CLICK, wrapper -> { byte containerId = wrapper.passthrough(Types.BYTE); int stateId = wrapper.user().get(InventoryStateIds.class).removeStateId(containerId); wrapper.write(Types.VAR_INT, stateId == Integer.MAX_VALUE ? 0 : stateId); }); registerServerbound(ServerboundPackets1_17.EDIT_BOOK, wrapper -> { Item item = wrapper.read(Types.ITEM1_13_2); boolean signing = wrapper.read(Types.BOOLEAN); wrapper.passthrough(Types.VAR_INT); // Slot comes first if (item == null) { wrapper.write(Types.VAR_INT, 0); // Pages length wrapper.write(Types.BOOLEAN, false); // Optional title return; } CompoundTag tag = item.tag(); ListTag pagesTag; StringTag titleTag = null; // Sanity checks if (tag == null || (pagesTag = tag.getListTag("pages", StringTag.class)) == null || (signing && (titleTag = tag.getStringTag("title")) == null)) { wrapper.write(Types.VAR_INT, 0); // Pages length wrapper.write(Types.BOOLEAN, false); // Optional title return; } // Write pages - limit them first if (pagesTag.size() > MAX_PAGES) { pagesTag = new ListTag<>(pagesTag.getValue().subList(0, MAX_PAGES)); } wrapper.write(Types.VAR_INT, pagesTag.size()); for (StringTag pageTag : pagesTag) { String page = pageTag.getValue(); // Limit page length if (page.length() > MAX_PAGE_LENGTH) { page = page.substring(0, MAX_PAGE_LENGTH); } wrapper.write(Types.STRING, page); } // Write optional title wrapper.write(Types.BOOLEAN, signing); if (signing) { // Limit title length String title = titleTag.getValue(); if (title.length() > MAX_TITLE_LENGTH) { title = title.substring(0, MAX_TITLE_LENGTH); } wrapper.write(Types.STRING, title); } }); } @Override public void init(UserConnection connection) { connection.put(new InventoryStateIds()); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_17_1to1_17/storage/InventoryStateIds.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_17_1to1_17.storage; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntOpenHashMap; public final class InventoryStateIds implements StorableObject { private final Int2IntMap ids = new Int2IntOpenHashMap(); public InventoryStateIds() { ids.defaultReturnValue(Integer.MAX_VALUE); } public void setStateId(int containerId, int id) { ids.put(containerId, id); } public int removeStateId(int containerId) { return ids.remove(containerId); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_17to1_16_4/Protocol1_17To1_16_4.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_17to1_16_4; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_17to1_16_4.rewriter.BlockItemPacketRewriter1_17; import com.viaversion.viabackwards.protocol.v1_17to1_16_4.rewriter.EntityPacketRewriter1_17; import com.viaversion.viabackwards.protocol.v1_17to1_16_4.storage.PlayerLastCursorItem; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.TagData; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_17; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.libs.fastutil.ints.IntArrayList; import com.viaversion.viaversion.libs.fastutil.ints.IntList; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ClientboundPackets1_16_2; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ServerboundPackets1_16_2; import com.viaversion.viaversion.protocols.v1_16_4to1_17.Protocol1_16_4To1_17; import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ClientboundPackets1_17; import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ServerboundPackets1_17; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.IdRewriteFunction; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; import com.viaversion.viaversion.util.Key; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public final class Protocol1_17To1_16_4 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.17", "1.16.2", Protocol1_16_4To1_17.class); private static final RegistryType[] TAG_REGISTRY_TYPES = {RegistryType.BLOCK, RegistryType.ITEM, RegistryType.FLUID, RegistryType.ENTITY}; private static final int[] EMPTY_ARRAY = {}; private final EntityPacketRewriter1_17 entityRewriter = new EntityPacketRewriter1_17(this); private final BlockItemPacketRewriter1_17 blockItemPackets = new BlockItemPacketRewriter1_17(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final JsonNBTComponentRewriter translatableRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BlockRewriter blockRewriter = BlockRewriter.for1_14(this); public Protocol1_17To1_16_4() { super(ClientboundPackets1_17.class, ClientboundPackets1_16_2.class, ServerboundPackets1_17.class, ServerboundPackets1_16_2.class); } @Override protected void registerPackets() { super.registerPackets(); registerClientbound(ClientboundPackets1_17.UPDATE_TAGS, wrapper -> { Map> tags = new HashMap<>(); int length = wrapper.read(Types.VAR_INT); for (int i = 0; i < length; i++) { String resourceKey = Key.stripMinecraftNamespace(wrapper.read(Types.STRING)); List tagList = new ArrayList<>(); tags.put(resourceKey, tagList); int tagLength = wrapper.read(Types.VAR_INT); for (int j = 0; j < tagLength; j++) { String identifier = wrapper.read(Types.STRING); int[] entries = wrapper.read(Types.VAR_INT_ARRAY_PRIMITIVE); tagList.add(new TagData(identifier, entries)); } } // Put them into the hardcoded order of Vanilla tags (and only those), rewrite ids for (RegistryType type : TAG_REGISTRY_TYPES) { List tagList = tags.get(type.identifier()); if (tagList == null) { // Higher versions may not send the otherwise expected tags wrapper.write(Types.VAR_INT, 0); continue; } IdRewriteFunction rewriter = tagRewriter.getRewriter(type); wrapper.write(Types.VAR_INT, tagList.size()); for (TagData tagData : tagList) { int[] entries = tagData.entries(); if (rewriter != null) { // Handle id rewriting now IntList idList = new IntArrayList(entries.length); for (int id : entries) { int mappedId = rewriter.rewrite(id); if (mappedId != -1) { idList.add(mappedId); } } entries = idList.toArray(EMPTY_ARRAY); } wrapper.write(Types.STRING, tagData.identifier()); wrapper.write(Types.VAR_INT_ARRAY_PRIMITIVE, entries); } } }); registerClientbound(ClientboundPackets1_17.RESOURCE_PACK, wrapper -> { wrapper.passthrough(Types.STRING); wrapper.passthrough(Types.STRING); wrapper.read(Types.BOOLEAN); // Required wrapper.read(Types.OPTIONAL_COMPONENT); // Prompt message }); registerClientbound(ClientboundPackets1_17.EXPLODE, new PacketHandlers() { @Override public void register() { map(Types.FLOAT); // X map(Types.FLOAT); // Y map(Types.FLOAT); // Z map(Types.FLOAT); // Strength handler(wrapper -> { wrapper.write(Types.INT, wrapper.read(Types.VAR_INT)); // Collection length }); } }); registerClientbound(ClientboundPackets1_17.SET_DEFAULT_SPAWN_POSITION, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_14); handler(wrapper -> { // Angle (which Mojang just forgot to write to the buffer, lol) wrapper.read(Types.FLOAT); }); } }); registerClientbound(ClientboundPackets1_17.PING, null, wrapper -> { wrapper.cancel(); int id = wrapper.read(Types.INT); short shortId = (short) id; if (id == shortId && ViaBackwards.getConfig().handlePingsAsInvAcknowledgements()) { // Send inventory acknowledgement to replace ping packet functionality in the unsigned byte range PacketWrapper acknowledgementPacket = wrapper.create(ClientboundPackets1_16_2.CONTAINER_ACK); acknowledgementPacket.write(Types.UNSIGNED_BYTE, (short) 0); // Inventory id acknowledgementPacket.write(Types.SHORT, shortId); // Confirmation id acknowledgementPacket.write(Types.BOOLEAN, false); // Accepted acknowledgementPacket.send(Protocol1_17To1_16_4.class); return; } // Plugins expecting a real response will have to handle this accordingly themselves PacketWrapper pongPacket = wrapper.create(ServerboundPackets1_17.PONG); pongPacket.write(Types.INT, id); pongPacket.sendToServer(Protocol1_17To1_16_4.class); }); registerServerbound(ServerboundPackets1_16_2.CLIENT_INFORMATION, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Locale map(Types.BYTE); // View distance map(Types.VAR_INT); // Chat mode map(Types.BOOLEAN); // Chat colors map(Types.UNSIGNED_BYTE); // Chat flags map(Types.VAR_INT); // Main hand handler(wrapper -> { wrapper.write(Types.BOOLEAN, false); // Text filtering }); } }); rewriteTitlePacket(ClientboundPackets1_17.SET_TITLE_TEXT, 0); rewriteTitlePacket(ClientboundPackets1_17.SET_SUBTITLE_TEXT, 1); rewriteTitlePacket(ClientboundPackets1_17.SET_ACTION_BAR_TEXT, 2); mergePacket(ClientboundPackets1_17.SET_TITLES_ANIMATION, ClientboundPackets1_16_2.SET_TITLES, 3); registerClientbound(ClientboundPackets1_17.CLEAR_TITLES, ClientboundPackets1_16_2.SET_TITLES, wrapper -> { if (wrapper.read(Types.BOOLEAN)) { wrapper.write(Types.VAR_INT, 5); // Reset times } else { wrapper.write(Types.VAR_INT, 4); // Simple clear } }); cancelClientbound(ClientboundPackets1_17.ADD_VIBRATION_SIGNAL); } @Override public void init(UserConnection user) { addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_17.PLAYER)); user.put(new PlayerLastCursorItem()); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } public void mergePacket(ClientboundPackets1_17 newPacketType, ClientboundPackets1_16_2 oldPacketType, int type) { // A few packets that had different handling based on an initially read enum type were split into different ones registerClientbound(newPacketType, oldPacketType, wrapper -> wrapper.write(Types.VAR_INT, type)); } private void rewriteTitlePacket(ClientboundPackets1_17 newPacketType, int type) { // Also handles translations in the title registerClientbound(newPacketType, ClientboundPackets1_16_2.SET_TITLES, wrapper -> { wrapper.write(Types.VAR_INT, type); translatableRewriter.processText(wrapper.user(), wrapper.passthrough(Types.COMPONENT)); }); } @Override public EntityPacketRewriter1_17 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_17 getItemRewriter() { return blockItemPackets; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_17to1_16_4/data/MapColorMappings1_16_4.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_17to1_16_4.data; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntOpenHashMap; public final class MapColorMappings1_16_4 { private static final Int2IntMap MAPPINGS = new Int2IntOpenHashMap(); static { MAPPINGS.put(236, 85); // (70, 70, 70) -> (65, 65, 65) MAPPINGS.put(237, 27); // (86, 86, 86) -> (88, 88, 88) MAPPINGS.put(238, 45); // (100, 100, 100) -> (96, 96, 96) MAPPINGS.put(239, 84); // (52, 52, 52) -> (53, 53, 53) MAPPINGS.put(240, 144); // (152, 123, 103) -> (147, 124, 113) MAPPINGS.put(241, 145); // (186, 150, 126) -> (180, 152, 138) MAPPINGS.put(242, 146); // (216, 175, 147) -> (209, 177, 161) MAPPINGS.put(243, 147); // (114, 92, 77) -> (110, 93, 85) MAPPINGS.put(244, 127); // (89, 117, 105) -> (48, 115, 112) MAPPINGS.put(245, 226); // (109, 144, 129) -> (58, 142, 140) MAPPINGS.put(246, 124); // (127, 167, 150) -> (64, 154, 150) MAPPINGS.put(247, 227); // (67, 88, 79) -> (30, 75, 74) } public static int getMappedColor(int color) { return MAPPINGS.getOrDefault(color, -1); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_17to1_16_4/rewriter/BlockItemPacketRewriter1_17.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_17to1_16_4.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.LongArrayTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.api.rewriters.MapColorRewriter; import com.viaversion.viabackwards.protocol.v1_17to1_16_4.Protocol1_17To1_16_4; import com.viaversion.viabackwards.protocol.v1_17to1_16_4.data.MapColorMappings1_16_4; import com.viaversion.viabackwards.protocol.v1_17_1to1_17.storage.InventoryStateIds; import com.viaversion.viabackwards.protocol.v1_17to1_16_4.storage.PlayerLastCursorItem; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.minecraft.BlockChangeRecord; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; import com.viaversion.viaversion.api.minecraft.chunks.DataPalette; import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_16_2; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_17; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ClientboundPackets1_16_2; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ServerboundPackets1_16_2; import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ClientboundPackets1_17; import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ServerboundPackets1_17; import com.viaversion.viaversion.rewriter.RecipeRewriter; import com.viaversion.viaversion.util.CompactArrayUtil; import com.viaversion.viaversion.util.MathUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.List; public final class BlockItemPacketRewriter1_17 extends BackwardsItemRewriter { private static final int BEDROCK_BLOCK_STATE = 33; public BlockItemPacketRewriter1_17(Protocol1_17To1_16_4 protocol) { super(protocol, Types.ITEM1_13_2, Types.ITEM1_13_2_SHORT_ARRAY); } @Override protected void registerPackets() { new RecipeRewriter<>(protocol).register(ClientboundPackets1_17.UPDATE_RECIPES); protocol.registerServerbound(ServerboundPackets1_16_2.EDIT_BOOK, wrapper -> handleItemToServer(wrapper.user(), wrapper.passthrough(Types.ITEM1_13_2))); // TODO Since the carried and modified items are typically set incorrectly, the server sends unnecessary // set slot packets after practically every window click, since it thinks the client and server // inventories are desynchronized. Ideally, we want to store a replica of each container state, and update // it appropriately for both serverbound and clientbound window packets, then fill in the carried // and modified items array as appropriate here. That would be a ton of work and replicated vanilla code, // and the hack below mitigates the worst side effects of this issue, which is an incorrect carried item // sent to the client when a right/left click drag is started. It works, at least for now... protocol.replaceServerbound(ServerboundPackets1_16_2.CONTAINER_CLICK, new PacketHandlers() { @Override public void register() { map(Types.BYTE); handler(wrapper -> { short slot = wrapper.passthrough(Types.SHORT); // Slot byte button = wrapper.passthrough(Types.BYTE); // Button wrapper.read(Types.SHORT); // Action id - removed int mode = wrapper.passthrough(Types.VAR_INT); // Mode Item clicked = handleItemToServer(wrapper.user(), wrapper.read(Types.ITEM1_13_2)); // Clicked item // The 1.17 client would check the entire inventory for changes before -> after a click // and send the changed slots here. We include the clicked slot so the server // detects any desync (e.g. cancelled InventoryClickEvent) and sends corrections. if (slot >= 0 && clicked != null) { wrapper.write(Types.VAR_INT, 1); // One modified slot wrapper.write(Types.SHORT, slot); // The clicked slot wrapper.write(Types.ITEM1_13_2, (Item) null); // Predicted: empty (item picked up) } else { wrapper.write(Types.VAR_INT, 0); // Empty array of slot+item } // Non-PICKUP modes (shift-click, swap, throw, drag, etc.) affect multiple slots // that we can't easily predict. Force a state ID mismatch so the server // sends a full inventory resync instead of individual slot corrections. if (mode != 0) { InventoryStateIds stateIds = wrapper.user().get(InventoryStateIds.class); if (stateIds != null) { stateIds.setStateId(wrapper.get(Types.BYTE, 0), -1); } } PlayerLastCursorItem state = wrapper.user().get(PlayerLastCursorItem.class); if (mode == 0 && button == 0 && clicked != null) { // Left click PICKUP // Carried item will (usually) be the entire clicked stack state.setLastCursorItem(clicked); } else if (mode == 0 && button == 1 && clicked != null) { // Right click PICKUP // Carried item will (usually) be half of the clicked stack (rounding up) // if the clicked slot is empty, otherwise it will (usually) be the whole clicked stack if (state.isSet()) { state.setLastCursorItem(clicked); } else { state.setLastCursorItem(clicked, (clicked.amount() + 1) / 2); } } else if (mode == 5 && slot == -999 && (button == 0 || button == 4)) { // Start drag (left or right click) // Preserve guessed carried item and forward to server // This mostly fixes the click and drag ghost item issue // no-op } else { // Carried item unknown (TODO) state.setLastCursorItem(null); } Item carried = state.getLastCursorItem(); if (carried == null) { // Expected is the carried item after clicking, old clients send the clicked one (*mostly* being the same) wrapper.write(Types.ITEM1_13_2, clicked); } else { wrapper.write(Types.ITEM1_13_2, carried); } }); } }); protocol.replaceClientbound(ClientboundPackets1_17.CONTAINER_SET_SLOT, wrapper -> { byte windowId = wrapper.passthrough(Types.BYTE); short slot = wrapper.passthrough(Types.SHORT); Item carried = wrapper.read(Types.ITEM1_13_2); if (carried != null && windowId == -1 && slot == -1) { // This is related to the hack to fix click and drag ghost items above. // After a completed drag, we have no idea how many items remain on the cursor, // and vanilla logic replication would be required to calculate the value. // When the click drag complete packet is sent, we will send an incorrect // carried item, and the server will helpfully send this packet allowing us // to update the internal state. This is necessary for fixing multiple sequential // click drag actions without intermittent pickup actions. wrapper.user().get(PlayerLastCursorItem.class).setLastCursorItem(carried); } wrapper.write(Types.ITEM1_13_2, handleItemToClient(wrapper.user(), carried)); }); protocol.registerServerbound(ServerboundPackets1_16_2.CONTAINER_ACK, null, wrapper -> { wrapper.cancel(); if (!ViaBackwards.getConfig().handlePingsAsInvAcknowledgements()) { return; } // Handle ping packet replacement byte inventoryId = wrapper.read(Types.BYTE); short confirmationId = wrapper.read(Types.SHORT); boolean accepted = wrapper.read(Types.BOOLEAN); if (inventoryId == 0 && accepted) { PacketWrapper pongPacket = wrapper.create(ServerboundPackets1_17.PONG); pongPacket.write(Types.INT, (int) confirmationId); pongPacket.sendToServer(Protocol1_17To1_16_4.class); } }); protocol.replaceClientbound(ClientboundPackets1_17.LEVEL_PARTICLES, new PacketHandlers() { @Override public void register() { map(Types.INT); // Particle id map(Types.BOOLEAN); // Long distance map(Types.DOUBLE); // X map(Types.DOUBLE); // Y map(Types.DOUBLE); // Z map(Types.FLOAT); // Offset X map(Types.FLOAT); // Offset Y map(Types.FLOAT); // Offset Z map(Types.FLOAT); // Particle data map(Types.INT); // Particle count handler(wrapper -> { int id = wrapper.get(Types.INT, 0); if (id == 16) { wrapper.passthrough(Types.FLOAT); // R wrapper.passthrough(Types.FLOAT); // G wrapper.passthrough(Types.FLOAT); // B wrapper.passthrough(Types.FLOAT); // Scale // Dust color transition -> Dust wrapper.read(Types.FLOAT); // R wrapper.read(Types.FLOAT); // G wrapper.read(Types.FLOAT); // B } else if (id == 37) { // Vibration signal - no nice mapping possible without tracking entity positions and doing particle tasks wrapper.set(Types.INT, 0, -1); wrapper.cancel(); } }); handler(protocol.getParticleRewriter().levelParticlesHandler1_13(Types.INT)); } }); protocol.mergePacket(ClientboundPackets1_17.SET_BORDER_SIZE, ClientboundPackets1_16_2.SET_BORDER, 0); protocol.mergePacket(ClientboundPackets1_17.SET_BORDER_LERP_SIZE, ClientboundPackets1_16_2.SET_BORDER, 1); protocol.mergePacket(ClientboundPackets1_17.SET_BORDER_CENTER, ClientboundPackets1_16_2.SET_BORDER, 2); protocol.mergePacket(ClientboundPackets1_17.INITIALIZE_BORDER, ClientboundPackets1_16_2.SET_BORDER, 3); protocol.mergePacket(ClientboundPackets1_17.SET_BORDER_WARNING_DELAY, ClientboundPackets1_16_2.SET_BORDER, 4); protocol.mergePacket(ClientboundPackets1_17.SET_BORDER_WARNING_DISTANCE, ClientboundPackets1_16_2.SET_BORDER, 5); // The Great Shrunkening // Chunk sections *will* be lost ¯\_(ツ)_/¯ protocol.registerClientbound(ClientboundPackets1_17.LIGHT_UPDATE, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // X map(Types.VAR_INT); // Z map(Types.BOOLEAN); // Trust edges handler(wrapper -> { EntityTracker tracker = wrapper.user().getEntityTracker(Protocol1_17To1_16_4.class); int startFromSection = Math.max(0, -(tracker.currentMinY() >> 4)); long[] skyLightMask = wrapper.read(Types.LONG_ARRAY_PRIMITIVE); long[] blockLightMask = wrapper.read(Types.LONG_ARRAY_PRIMITIVE); int cutSkyLightMask = cutLightMask(skyLightMask, startFromSection); int cutBlockLightMask = cutLightMask(blockLightMask, startFromSection); wrapper.write(Types.VAR_INT, cutSkyLightMask); wrapper.write(Types.VAR_INT, cutBlockLightMask); long[] emptySkyLightMask = wrapper.read(Types.LONG_ARRAY_PRIMITIVE); long[] emptyBlockLightMask = wrapper.read(Types.LONG_ARRAY_PRIMITIVE); wrapper.write(Types.VAR_INT, cutLightMask(emptySkyLightMask, startFromSection)); wrapper.write(Types.VAR_INT, cutLightMask(emptyBlockLightMask, startFromSection)); writeLightArrays(wrapper, BitSet.valueOf(skyLightMask), cutSkyLightMask, startFromSection, tracker.currentWorldSectionHeight()); writeLightArrays(wrapper, BitSet.valueOf(blockLightMask), cutBlockLightMask, startFromSection, tracker.currentWorldSectionHeight()); }); } private void writeLightArrays(PacketWrapper wrapper, BitSet bitMask, int cutBitMask, int startFromSection, int sectionHeight) { int packetContentsLength = wrapper.read(Types.VAR_INT); int read = 0; List light = new ArrayList<>(); // Remove lower bounds for (int i = 0; i < startFromSection; i++) { if (bitMask.get(i)) { read++; wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); } } // Add the important 18 sections for (int i = 0; i < 18; i++) { if (isSet(cutBitMask, i)) { read++; light.add(wrapper.read(Types.BYTE_ARRAY_PRIMITIVE)); } } // Remove upper bounds for (int i = startFromSection + 18; i < sectionHeight + 2; i++) { if (bitMask.get(i)) { read++; wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); } } if (read != packetContentsLength) { // Read the rest if the server for some reason sends more than are actually consumed for (int i = read; i < packetContentsLength; i++) { wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); } } // Aaand we're done for (byte[] bytes : light) { wrapper.write(Types.BYTE_ARRAY_PRIMITIVE, bytes); } } private boolean isSet(int mask, int i) { return (mask & (1 << i)) != 0; } }); protocol.replaceClientbound(ClientboundPackets1_17.SECTION_BLOCKS_UPDATE, new PacketHandlers() { @Override public void register() { map(Types.LONG); // Chunk pos map(Types.BOOLEAN); // Suppress light updates handler((wrapper) -> { // Remove sections below y 0 and above 255 long chunkPos = wrapper.get(Types.LONG, 0); int chunkY = (int) (chunkPos << 44 >> 44); if (chunkY < 0 || chunkY > 15) { wrapper.cancel(); return; } BlockChangeRecord[] records = wrapper.passthrough(Types.VAR_LONG_BLOCK_CHANGE_ARRAY); for (BlockChangeRecord record : records) { if (ViaBackwards.getConfig().bedrockAtY0() && chunkY == 0 && record.getSectionY() == 0) { record.setBlockId(BEDROCK_BLOCK_STATE); } else { record.setBlockId(protocol.getMappingData().getNewBlockStateId(record.getBlockId())); } } }); } }); protocol.replaceClientbound(ClientboundPackets1_17.BLOCK_UPDATE, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_14); map(Types.VAR_INT); handler((wrapper) -> { int y = wrapper.get(Types.BLOCK_POSITION1_14, 0).y(); if (y < 0 || y > 255) { wrapper.cancel(); return; } if (ViaBackwards.getConfig().bedrockAtY0() && y == 0) { wrapper.set(Types.VAR_INT, 0, BEDROCK_BLOCK_STATE); } else { wrapper.set(Types.VAR_INT, 0, protocol.getMappingData().getNewBlockStateId(wrapper.get(Types.VAR_INT, 0))); } }); } }); protocol.registerClientbound(ClientboundPackets1_17.LEVEL_CHUNK, wrapper -> { EntityTracker tracker = wrapper.user().getEntityTracker(Protocol1_17To1_16_4.class); int currentWorldSectionHeight = tracker.currentWorldSectionHeight(); Chunk chunk = wrapper.read(new ChunkType1_17(currentWorldSectionHeight)); wrapper.write(ChunkType1_16_2.TYPE, chunk); // Cut sections int startFromSection = Math.max(0, -(tracker.currentMinY() >> 4)); chunk.setBiomeData(Arrays.copyOfRange(chunk.getBiomeData(), startFromSection * 64, (startFromSection * 64) + 1024)); chunk.setBitmask(cutMask(chunk.getChunkMask(), startFromSection, false)); chunk.setChunkMask(null); ChunkSection[] sections = Arrays.copyOfRange(chunk.getSections(), startFromSection, startFromSection + 16); chunk.setSections(sections); CompoundTag heightMaps = chunk.getHeightMap(); for (Tag heightMapTag : heightMaps.values()) { if (!(heightMapTag instanceof final LongArrayTag heightMap)) { continue; // Client can handle bad data } int[] heightMapData = new int[256]; int bitsPerEntry = MathUtil.ceilLog2((currentWorldSectionHeight << 4) + 1); // Shift back to 0 based and clamp to normal height with 9 bits CompactArrayUtil.iterateCompactArrayWithPadding(bitsPerEntry, heightMapData.length, heightMap.getValue(), (i, v) -> heightMapData[i] = MathUtil.clamp(v + tracker.currentMinY(), 0, 255)); heightMap.setValue(CompactArrayUtil.createCompactArrayWithPadding(9, heightMapData.length, i -> heightMapData[i])); } protocol.getBlockRewriter().handleChunk(chunk); if (ViaBackwards.getConfig().bedrockAtY0()) { final ChunkSection lowestSection = chunk.getSections()[0]; if (lowestSection != null) { final DataPalette blocks = lowestSection.palette(PaletteType.BLOCKS); for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { blocks.setIdAt(x, 0, z, BEDROCK_BLOCK_STATE); } } } } chunk.getBlockEntities().removeIf(compound -> { NumberTag tag = compound.getNumberTag("y"); if (tag == null) { return false; } final int y = tag.asInt(); return y < 0 || y > 255 || (ViaBackwards.getConfig().bedrockAtY0() && y == 0); }); }); protocol.registerClientbound(ClientboundPackets1_17.BLOCK_ENTITY_DATA, wrapper -> { int y = wrapper.passthrough(Types.BLOCK_POSITION1_14).y(); if (y < 0 || y > 255 || (ViaBackwards.getConfig().bedrockAtY0() && y == 0)) { wrapper.cancel(); } }); protocol.registerClientbound(ClientboundPackets1_17.BLOCK_DESTRUCTION, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); handler(wrapper -> { int y = wrapper.passthrough(Types.BLOCK_POSITION1_14).y(); if (y < 0 || y > 255 || (ViaBackwards.getConfig().bedrockAtY0() && y == 0)) { wrapper.cancel(); } }); } }); protocol.registerClientbound(ClientboundPackets1_17.MAP_ITEM_DATA, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Map ID map(Types.BYTE); // Scale handler(wrapper -> wrapper.write(Types.BOOLEAN, true)); // Tracking position map(Types.BOOLEAN); // Locked handler(wrapper -> { final int iconCount; final boolean hasMarkers = wrapper.read(Types.BOOLEAN); if (hasMarkers) { iconCount = wrapper.passthrough(Types.VAR_INT); } else { wrapper.write(Types.VAR_INT, 0); // Add icon count iconCount = 0; } MapColorRewriter.rewriteMapColors(wrapper, MapColorMappings1_16_4::getMappedColor, iconCount); }); } }); } private int cutLightMask(long[] mask, int startFromSection) { if (mask.length == 0) return 0; return cutMask(BitSet.valueOf(mask), startFromSection, true); } private int cutMask(BitSet mask, int startFromSection, boolean lightMask) { int cutMask = 0; // Light masks have a section below and above the 16 main sections int to = startFromSection + (lightMask ? 18 : 16); for (int i = startFromSection, j = 0; i < to; i++, j++) { if (mask.get(i)) { cutMask |= (1 << j); } } return cutMask; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_17to1_16_4/rewriter/EntityPacketRewriter1_17.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_17to1_16_4.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_17to1_16_4.Protocol1_17To1_16_4; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_16_2; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_17; import com.viaversion.viaversion.api.minecraft.entitydata.EntityDataType; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_16; import com.viaversion.viaversion.api.type.types.version.Types1_17; import com.viaversion.viaversion.protocols.v1_16_1to1_16_2.packet.ClientboundPackets1_16_2; import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ClientboundPackets1_17; import com.viaversion.viaversion.util.TagUtil; public final class EntityPacketRewriter1_17 extends EntityRewriter { private boolean warned = ViaBackwards.getConfig().bedrockAtY0() || ViaBackwards.getConfig().suppressEmulationWarnings(); public EntityPacketRewriter1_17(Protocol1_17To1_16_4 protocol) { super(protocol); } @Override protected void registerPackets() { registerTracker(ClientboundPackets1_17.ADD_EXPERIENCE_ORB, EntityTypes1_17.EXPERIENCE_ORB); registerTracker(ClientboundPackets1_17.ADD_PAINTING, EntityTypes1_17.PAINTING); registerTracker(ClientboundPackets1_17.ADD_PLAYER, EntityTypes1_17.PLAYER); registerSetEntityData(ClientboundPackets1_17.SET_ENTITY_DATA, Types1_17.ENTITY_DATA_LIST, Types1_16.ENTITY_DATA_LIST); protocol.appendClientbound(ClientboundPackets1_17.ADD_ENTITY, wrapper -> { final int entityType = wrapper.get(Types.VAR_INT, 1); if (entityType != EntityTypes1_16_2.ITEM_FRAME.getId()) { return; } // Older clients will ignore the data field and the server sets the item frame rotation by the yaw/pitch field inside the packet, // newer clients do the opposite and ignore yaw/pitch and use the data field from the packet. final int data = wrapper.get(Types.INT, 0); float pitch = 0F; float yaw = 0F; switch (Math.abs(data % 6)) { case 0 /* down */ -> pitch = 90F; case 1 /* up */ -> pitch = -90F; case 2 /* north */ -> yaw = 180F; case 4 /* west */ -> yaw = 90F; case 5 /* east */ -> yaw = 270; } wrapper.set(Types.BYTE, 0, (byte) (pitch * 256F / 360F)); wrapper.set(Types.BYTE, 1, (byte) (yaw * 256F / 360F)); }); protocol.registerClientbound(ClientboundPackets1_17.REMOVE_ENTITY, ClientboundPackets1_16_2.REMOVE_ENTITIES, wrapper -> { int entityId = wrapper.read(Types.VAR_INT); tracker(wrapper.user()).removeEntity(entityId); // Write into single value array int[] array = {entityId}; wrapper.write(Types.VAR_INT_ARRAY_PRIMITIVE, array); }); protocol.registerClientbound(ClientboundPackets1_17.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // Entity ID map(Types.BOOLEAN); // Hardcore map(Types.BYTE); // Gamemode map(Types.BYTE); // Previous Gamemode map(Types.STRING_ARRAY); // Worlds map(Types.NAMED_COMPOUND_TAG); // Dimension registry map(Types.NAMED_COMPOUND_TAG); // Current dimension data map(Types.STRING); // World handler(wrapper -> { byte previousGamemode = wrapper.get(Types.BYTE, 1); if (previousGamemode == -1) { // "Unset" gamemode removed wrapper.set(Types.BYTE, 1, (byte) 0); } }); handler(getPlayerTrackerHandler()); handler(worldDataTrackerHandler(1)); handler(wrapper -> { CompoundTag registry = wrapper.get(Types.NAMED_COMPOUND_TAG, 0); ListTag biomes = TagUtil.getRegistryEntries(registry, "worldgen/biome"); for (CompoundTag biome : biomes) { CompoundTag biomeCompound = biome.getCompoundTag("element"); StringTag category = biomeCompound.getStringTag("category"); if (category.getValue().equalsIgnoreCase("underground")) { category.setValue("none"); } } ListTag dimensions = TagUtil.getRegistryEntries(registry, "dimension_type"); for (CompoundTag dimension : dimensions) { CompoundTag dimensionCompound = dimension.getCompoundTag("element"); reduceExtendedHeight(dimensionCompound, false); } reduceExtendedHeight(wrapper.get(Types.NAMED_COMPOUND_TAG, 1), true); }); } }); protocol.registerClientbound(ClientboundPackets1_17.RESPAWN, new PacketHandlers() { @Override public void register() { map(Types.NAMED_COMPOUND_TAG); // Dimension data map(Types.STRING); // World handler(worldDataTrackerHandler(0)); handler(wrapper -> reduceExtendedHeight(wrapper.get(Types.NAMED_COMPOUND_TAG, 0), true)); } }); protocol.registerClientbound(ClientboundPackets1_17.PLAYER_POSITION, new PacketHandlers() { @Override public void register() { map(Types.DOUBLE); map(Types.DOUBLE); map(Types.DOUBLE); map(Types.FLOAT); map(Types.FLOAT); map(Types.BYTE); map(Types.VAR_INT); read(Types.BOOLEAN); // Dismount vehicle ¯\_(ツ)_/¯ } }); protocol.registerClientbound(ClientboundPackets1_17.UPDATE_ATTRIBUTES, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Entity id handler(wrapper -> wrapper.write(Types.INT, wrapper.read(Types.VAR_INT))); // Collection length } }); protocol.mergePacket(ClientboundPackets1_17.PLAYER_COMBAT_ENTER, ClientboundPackets1_16_2.PLAYER_COMBAT, 0); protocol.mergePacket(ClientboundPackets1_17.PLAYER_COMBAT_END, ClientboundPackets1_16_2.PLAYER_COMBAT, 1); protocol.registerClientbound(ClientboundPackets1_17.PLAYER_COMBAT_KILL, ClientboundPackets1_16_2.PLAYER_COMBAT, wrapper -> { wrapper.write(Types.VAR_INT, 2); wrapper.passthrough(Types.VAR_INT); // Duration/Player id wrapper.passthrough(Types.INT); // Killer id protocol.getComponentRewriter().processText(wrapper.user(), wrapper.passthrough(Types.COMPONENT)); }); } @Override protected void registerRewrites() { filter().handler((event, data) -> { data.setDataType(Types1_16.ENTITY_DATA_TYPES.byId(data.dataType().typeId())); EntityDataType type = data.dataType(); if (type == Types1_16.ENTITY_DATA_TYPES.particleType) { Particle particle = (Particle) data.getValue(); if (particle.id() == 16) { // Dust / Dust Transition // Remove transition target color values 4-6 particle.getArguments().subList(4, 7).clear(); } else if (particle.id() == 37) { // Vibration Signal // No nice mapping possible without tracking entity positions and doing particle tasks particle.setId(0); particle.getArguments().clear(); return; } protocol.getParticleRewriter().rewriteParticle(event.user(), particle); } else if (type == Types1_16.ENTITY_DATA_TYPES.poseType) { // Goat LONG_JUMP added at 6 int pose = data.value(); if (pose == 6) { data.setValue(1); // FALL_FLYING } else if (pose > 6) { data.setValue(pose - 1); } } }); // Particles have already been handled registerEntityDataTypeHandler(Types1_16.ENTITY_DATA_TYPES.itemType, null, Types1_16.ENTITY_DATA_TYPES.optionalBlockStateType, null, Types1_16.ENTITY_DATA_TYPES.componentType, Types1_16.ENTITY_DATA_TYPES.optionalComponentType); filter().type(EntityTypes1_17.AXOLOTL).cancel(17); filter().type(EntityTypes1_17.AXOLOTL).cancel(18); filter().type(EntityTypes1_17.AXOLOTL).cancel(19); filter().type(EntityTypes1_17.GLOW_SQUID).cancel(16); filter().type(EntityTypes1_17.GOAT).cancel(17); filter().type(EntityTypes1_17.SHULKER).addIndex(17); // TODO Handle attachment pos? registerBlockStateHandler(EntityTypes1_17.ABSTRACT_MINECART, 11); filter().removeIndex(7); // Ticks frozen } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); mapEntityTypeWithData(EntityTypes1_17.AXOLOTL, EntityTypes1_17.TROPICAL_FISH).jsonName(); mapEntityTypeWithData(EntityTypes1_17.GOAT, EntityTypes1_17.SHEEP).jsonName(); mapEntityTypeWithData(EntityTypes1_17.GLOW_SQUID, EntityTypes1_17.SQUID).jsonName(); mapEntityTypeWithData(EntityTypes1_17.GLOW_ITEM_FRAME, EntityTypes1_17.ITEM_FRAME); } @Override public EntityType typeFromId(int typeId) { return EntityTypes1_17.getTypeFromId(typeId); } private void reduceExtendedHeight(CompoundTag tag, boolean warn) { NumberTag minY = tag.getNumberTag("min_y"); NumberTag height = tag.getNumberTag("height"); NumberTag logicalHeight = tag.getNumberTag("logical_height"); if (minY.asInt() != 0 || height.asInt() > 256 || logicalHeight.asInt() > 256) { if (warn && !warned) { protocol.getLogger().warning("Increased world height is NOT SUPPORTED for 1.16 players and below. They will see a void below y 0 and above 256. You can enable the `bedrock-at-y-0` config option to replace the air with a bedrock layer."); warned = true; } tag.putInt("height", Math.min(256, height.asInt())); tag.putInt("logical_height", Math.min(256, logicalHeight.asInt())); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_17to1_16_4/storage/PlayerLastCursorItem.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_17to1_16_4.storage; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.minecraft.item.Item; public class PlayerLastCursorItem implements StorableObject { private Item lastCursorItem; public Item getLastCursorItem() { return copyItem(lastCursorItem); } public void setLastCursorItem(Item item) { this.lastCursorItem = copyItem(item); } public void setLastCursorItem(Item item, int amount) { this.lastCursorItem = copyItem(item); this.lastCursorItem.setAmount(amount); } public boolean isSet() { return lastCursorItem != null; } private static Item copyItem(Item item) { if (item == null) { return null; } return item.copy(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_18_2to1_18/Protocol1_18_2To1_18.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_18_2to1_18; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.protocol.v1_18_2to1_18.rewriter.CommandRewriter1_18_2; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ServerboundPackets1_17; import com.viaversion.viaversion.protocols.v1_17_1to1_18.packet.ClientboundPackets1_18; import com.viaversion.viaversion.util.TagUtil; public final class Protocol1_18_2To1_18 extends BackwardsProtocol { public Protocol1_18_2To1_18() { super(ClientboundPackets1_18.class, ClientboundPackets1_18.class, ServerboundPackets1_17.class, ServerboundPackets1_17.class); } @Override protected void registerPackets() { new CommandRewriter1_18_2(this).registerDeclareCommands(ClientboundPackets1_18.COMMANDS); final PacketHandler entityEffectIdHandler = wrapper -> { final int id = wrapper.read(Types.VAR_INT); if ((byte) id != id) { if (Via.getConfig().logOtherConversionWarnings()) { getLogger().warning("Cannot send entity effect id " + id + " to old client"); } wrapper.cancel(); return; } wrapper.write(Types.BYTE, (byte) id); }; registerClientbound(ClientboundPackets1_18.UPDATE_MOB_EFFECT, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Entity id handler(entityEffectIdHandler); } }); registerClientbound(ClientboundPackets1_18.REMOVE_MOB_EFFECT, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Entity id handler(entityEffectIdHandler); } }); registerClientbound(ClientboundPackets1_18.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // Entity ID map(Types.BOOLEAN); // Hardcore map(Types.BYTE); // Gamemode map(Types.BYTE); // Previous Gamemode map(Types.STRING_ARRAY); // World List map(Types.NAMED_COMPOUND_TAG); // Registry map(Types.NAMED_COMPOUND_TAG); // Current dimension data handler(wrapper -> { final CompoundTag registry = wrapper.get(Types.NAMED_COMPOUND_TAG, 0); final ListTag dimensions = TagUtil.getRegistryEntries(registry, "dimension_type"); for (final CompoundTag dimension : dimensions) { removeTagPrefix(dimension.getCompoundTag("element")); } removeTagPrefix(wrapper.get(Types.NAMED_COMPOUND_TAG, 1)); }); } }); registerClientbound(ClientboundPackets1_18.RESPAWN, wrapper -> removeTagPrefix(wrapper.passthrough(Types.NAMED_COMPOUND_TAG))); } private void removeTagPrefix(CompoundTag tag) { final StringTag infiniburnTag = tag.getStringTag("infiniburn"); if (infiniburnTag != null) { infiniburnTag.setValue(infiniburnTag.getValue().substring(1)); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_18_2to1_18/rewriter/CommandRewriter1_18_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_18_2to1_18.rewriter; import com.viaversion.viabackwards.protocol.v1_18_2to1_18.Protocol1_18_2To1_18; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_17_1to1_18.packet.ClientboundPackets1_18; import com.viaversion.viaversion.rewriter.CommandRewriter; import org.checkerframework.checker.nullness.qual.Nullable; public final class CommandRewriter1_18_2 extends CommandRewriter { public CommandRewriter1_18_2(Protocol1_18_2To1_18 protocol) { super(protocol); // Uncompletable without the full list this.parserHandlers.put("minecraft:resource", wrapper -> { wrapper.read(Types.STRING); wrapper.write(Types.VAR_INT, 1); // Quotable string }); this.parserHandlers.put("minecraft:resource_or_tag", wrapper -> { wrapper.read(Types.STRING); wrapper.write(Types.VAR_INT, 1); // Quotable string }); } @Override public @Nullable String handleArgumentType(String argumentType) { if (argumentType.equals("minecraft:resource") || argumentType.equals("minecraft:resource_or_tag")) { return "brigadier:string"; } return super.handleArgumentType(argumentType); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_18to1_17_1/Protocol1_18To1_17_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_18to1_17_1; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.SoundRewriter; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_18to1_17_1.data.BackwardsMappingData1_18; import com.viaversion.viabackwards.protocol.v1_18to1_17_1.rewriter.BlockItemPacketRewriter1_18; import com.viaversion.viabackwards.protocol.v1_18to1_17_1.rewriter.EntityPacketRewriter1_18; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_17; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ServerboundPackets1_17; import com.viaversion.viaversion.protocols.v1_17_1to1_18.packet.ClientboundPackets1_18; import com.viaversion.viaversion.protocols.v1_17to1_17_1.packet.ClientboundPackets1_17_1; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; public final class Protocol1_18To1_17_1 extends BackwardsProtocol { private static final BackwardsMappingData1_18 MAPPINGS = new BackwardsMappingData1_18(); private final EntityPacketRewriter1_18 entityRewriter = new EntityPacketRewriter1_18(this); private final BlockItemPacketRewriter1_18 itemRewriter = new BlockItemPacketRewriter1_18(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final JsonNBTComponentRewriter translatableRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); private final TagRewriter tagRewriter = new TagRewriter<>(this); public Protocol1_18To1_17_1() { super(ClientboundPackets1_18.class, ClientboundPackets1_17_1.class, ServerboundPackets1_17.class, ServerboundPackets1_17.class); } @Override protected void registerPackets() { super.registerPackets(); tagRewriter.addEmptyTag(RegistryType.BLOCK, "minecraft:lava_pool_stone_replaceables"); registerServerbound(ServerboundPackets1_17.CLIENT_INFORMATION, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Language map(Types.BYTE); // View distance map(Types.VAR_INT); // Chat visibility map(Types.BOOLEAN); // Chat colors map(Types.UNSIGNED_BYTE); // Model customization map(Types.VAR_INT); // Main hand map(Types.BOOLEAN); // Text filtering enabled create(Types.BOOLEAN, true); // Allow listing in server list preview } }); appendClientbound(ClientboundPackets1_18.SET_OBJECTIVE, cutName(0, 16)); registerClientbound(ClientboundPackets1_18.SET_DISPLAY_OBJECTIVE, new PacketHandlers() { @Override public void register() { map(Types.BYTE); // Slot map(Types.STRING); // Name handler(cutName(0, 16)); } }); appendClientbound(ClientboundPackets1_18.SET_PLAYER_TEAM, cutName(0, 16)); registerClientbound(ClientboundPackets1_18.SET_SCORE, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Owner map(Types.VAR_INT); // Method map(Types.STRING); // Name handler(cutName(0, 40)); handler(cutName(1, 16)); } }); } private PacketHandler cutName(final int index, final int maxLength) { // May in some case cause clashes or bad ordering, but nothing we can do about that return wrapper -> { final String s = wrapper.get(Types.STRING, index); if (s.length() > maxLength) { wrapper.set(Types.STRING, index, s.substring(0, maxLength)); } }; } @Override public void init(final UserConnection connection) { addEntityTracker(connection, new EntityTrackerBase(connection, EntityTypes1_17.PLAYER)); } @Override public BackwardsMappingData1_18 getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_18 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_18 getItemRewriter() { return itemRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_18to1_17_1/data/BackwardsMappingData1_18.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_18to1_17_1.data; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectOpenHashMap; import com.viaversion.viaversion.libs.fastutil.objects.Object2IntMap; import com.viaversion.viaversion.protocols.v1_17_1to1_18.Protocol1_17_1To1_18; import com.viaversion.viaversion.protocols.v1_17_1to1_18.data.BlockEntities1_18; public final class BackwardsMappingData1_18 extends BackwardsMappingData { private final Int2ObjectMap blockEntities = new Int2ObjectOpenHashMap<>(); public BackwardsMappingData1_18() { super("1.18", "1.17", Protocol1_17_1To1_18.class); } @Override protected void loadExtras(final CompoundTag data) { super.loadExtras(data); for (final Object2IntMap.Entry entry : BlockEntities1_18.blockEntityIds().object2IntEntrySet()) { blockEntities.put(entry.getIntValue(), entry.getKey()); } } public Int2ObjectMap blockEntities() { return blockEntities; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_18to1_17_1/data/BlockEntityMappings1_17_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_18to1_17_1.data; import com.viaversion.viaversion.protocols.v1_17_1to1_18.data.BlockEntityMappings1_18; import java.util.Arrays; public final class BlockEntityMappings1_17_1 { private static final int[] IDS; static { final int[] ids = BlockEntityMappings1_18.getIds(); IDS = new int[Arrays.stream(ids).max().getAsInt() + 1]; Arrays.fill(IDS, -1); for (int i = 0; i < ids.length; i++) { final int id = ids[i]; if (id != -1) { IDS[id] = i; } } } public static int mappedId(final int id) { if (id < 0 || id > IDS.length) { return -1; } return IDS[id]; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_18to1_17_1/rewriter/BlockItemPacketRewriter1_18.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_18to1_17_1.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.protocol.v1_18to1_17_1.Protocol1_18To1_17_1; import com.viaversion.viabackwards.protocol.v1_18to1_17_1.data.BlockEntityMappings1_17_1; import com.viaversion.viaversion.api.data.ParticleMappings; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.blockentity.BlockEntity; import com.viaversion.viaversion.api.minecraft.chunks.BaseChunk; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; import com.viaversion.viaversion.api.minecraft.chunks.DataPalette; import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_17; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_18; import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ServerboundPackets1_17; import com.viaversion.viaversion.protocols.v1_17_1to1_18.packet.ClientboundPackets1_18; import com.viaversion.viaversion.protocols.v1_17to1_17_1.packet.ClientboundPackets1_17_1; import com.viaversion.viaversion.rewriter.RecipeRewriter; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.MathUtil; import java.util.ArrayList; import java.util.BitSet; import java.util.List; public final class BlockItemPacketRewriter1_18 extends BackwardsItemRewriter { public BlockItemPacketRewriter1_18(final Protocol1_18To1_17_1 protocol) { super(protocol, Types.ITEM1_13_2, Types.ITEM1_13_2_ARRAY); } @Override protected void registerPackets() { new RecipeRewriter<>(protocol).register(ClientboundPackets1_18.UPDATE_RECIPES); protocol.registerClientbound(ClientboundPackets1_18.LEVEL_EVENT, new PacketHandlers() { @Override public void register() { map(Types.INT); // Effect id map(Types.BLOCK_POSITION1_14); // Location map(Types.INT); // Data handler(wrapper -> { int id = wrapper.get(Types.INT, 0); int data = wrapper.get(Types.INT, 1); if (id == 1010) { // Play record wrapper.set(Types.INT, 1, protocol.getMappingData().getNewItemId(data)); } }); } }); protocol.replaceClientbound(ClientboundPackets1_18.LEVEL_PARTICLES, new PacketHandlers() { @Override public void register() { map(Types.INT); // Particle id map(Types.BOOLEAN); // Override limiter map(Types.DOUBLE); // X map(Types.DOUBLE); // Y map(Types.DOUBLE); // Z map(Types.FLOAT); // Offset X map(Types.FLOAT); // Offset Y map(Types.FLOAT); // Offset Z map(Types.FLOAT); // Max speed map(Types.INT); // Particle Count handler(wrapper -> { int id = wrapper.get(Types.INT, 0); if (id == 3) { // Block marker int blockState = wrapper.read(Types.VAR_INT); if (blockState == 7786) { // Light block wrapper.set(Types.INT, 0, 3); } else { // Else assume barrier block wrapper.set(Types.INT, 0, 2); } return; } ParticleMappings mappings = protocol.getMappingData().getParticleMappings(); if (mappings.isBlockParticle(id)) { int data = wrapper.passthrough(Types.VAR_INT); wrapper.set(Types.VAR_INT, 0, protocol.getMappingData().getNewBlockStateId(data)); } else if (mappings.isItemParticle(id)) { passthroughClientboundItem(wrapper); } int newId = protocol.getMappingData().getNewParticleId(id); if (newId != id) { wrapper.set(Types.INT, 0, newId); } }); } }); protocol.registerClientbound(ClientboundPackets1_18.BLOCK_ENTITY_DATA, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_14); handler(wrapper -> { final int id = wrapper.read(Types.VAR_INT); final CompoundTag tag = wrapper.read(Types.NAMED_COMPOUND_TAG); final int mappedId = BlockEntityMappings1_17_1.mappedId(id); if (mappedId == -1) { wrapper.cancel(); return; } final String identifier = protocol.getMappingData().blockEntities().get(id); if (identifier == null) { wrapper.cancel(); return; } // The 1.18 server doesn't include the id and positions (x, y, z) in the NBT anymore // If those were the only fields on the block entity (e.g.: skull, bed), we'll receive a null NBT // We initialize one and add the missing fields, so it can be handled correctly down the line final CompoundTag newTag = tag == null ? new CompoundTag() : tag; final BlockPosition pos = wrapper.get(Types.BLOCK_POSITION1_14, 0); // The protocol converters downstream rely on this field, let's add it back newTag.putString("id", Key.namespaced(identifier)); // Weird glitches happen with the 1.17 client and below if these fields are missing // Some examples are block entity models becoming invisible (e.g.: signs, banners) newTag.putInt("x", pos.x()); newTag.putInt("y", pos.y()); newTag.putInt("z", pos.z()); handleSpawner(id, newTag); wrapper.write(Types.UNSIGNED_BYTE, (short) mappedId); wrapper.write(Types.NAMED_COMPOUND_TAG, newTag); }); } }); protocol.registerClientbound(ClientboundPackets1_18.LEVEL_CHUNK_WITH_LIGHT, ClientboundPackets1_17_1.LEVEL_CHUNK, wrapper -> { final EntityTracker tracker = protocol.getEntityRewriter().tracker(wrapper.user()); final ChunkType1_18 chunkType = new ChunkType1_18(tracker.currentWorldSectionHeight(), MathUtil.ceilLog2(protocol.getMappingData().getBlockStateMappings().mappedSize()), MathUtil.ceilLog2(tracker.biomesSent())); final Chunk oldChunk = wrapper.read(chunkType); final ChunkSection[] sections = oldChunk.getSections(); final BitSet mask = new BitSet(oldChunk.getSections().length); final int[] biomeData = new int[sections.length * ChunkSection.BIOME_SIZE]; int biomeIndex = 0; for (int j = 0; j < sections.length; j++) { final ChunkSection section = sections[j]; // Write biome palette into biome array final DataPalette biomePalette = section.palette(PaletteType.BIOMES); for (int i = 0; i < ChunkSection.BIOME_SIZE; i++) { biomeData[biomeIndex++] = biomePalette.idAt(i); } // Rewrite to empty section if (section.getNonAirBlocksCount() == 0) { sections[j] = null; } else { mask.set(j); } } final List blockEntityTags = new ArrayList<>(oldChunk.blockEntities().size()); for (final BlockEntity blockEntity : oldChunk.blockEntities()) { final String id = protocol.getMappingData().blockEntities().get(blockEntity.typeId()); if (id == null) { // Shrug continue; } final CompoundTag tag; if (blockEntity.tag() != null) { tag = blockEntity.tag(); handleSpawner(blockEntity.typeId(), tag); } else { tag = new CompoundTag(); } blockEntityTags.add(tag); tag.putInt("x", (oldChunk.getX() << 4) + blockEntity.sectionX()); tag.putInt("y", blockEntity.y()); tag.putInt("z", (oldChunk.getZ() << 4) + blockEntity.sectionZ()); tag.putString("id", Key.namespaced(id)); } final Chunk chunk = new BaseChunk(oldChunk.getX(), oldChunk.getZ(), true, false, mask, oldChunk.getSections(), biomeData, oldChunk.getHeightMap(), blockEntityTags); wrapper.write(new ChunkType1_17(tracker.currentWorldSectionHeight()), chunk); // Create and send light packet first final PacketWrapper lightPacket = wrapper.create(ClientboundPackets1_17_1.LIGHT_UPDATE); lightPacket.write(Types.VAR_INT, chunk.getX()); lightPacket.write(Types.VAR_INT, chunk.getZ()); lightPacket.write(Types.BOOLEAN, wrapper.read(Types.BOOLEAN)); // Trust edges lightPacket.write(Types.LONG_ARRAY_PRIMITIVE, wrapper.read(Types.LONG_ARRAY_PRIMITIVE)); // Sky light mask lightPacket.write(Types.LONG_ARRAY_PRIMITIVE, wrapper.read(Types.LONG_ARRAY_PRIMITIVE)); // Block light mask lightPacket.write(Types.LONG_ARRAY_PRIMITIVE, wrapper.read(Types.LONG_ARRAY_PRIMITIVE)); // Empty sky light mask lightPacket.write(Types.LONG_ARRAY_PRIMITIVE, wrapper.read(Types.LONG_ARRAY_PRIMITIVE)); // Empty block light mask final int skyLightLength = wrapper.read(Types.VAR_INT); lightPacket.write(Types.VAR_INT, skyLightLength); for (int i = 0; i < skyLightLength; i++) { lightPacket.write(Types.BYTE_ARRAY_PRIMITIVE, wrapper.read(Types.BYTE_ARRAY_PRIMITIVE)); } final int blockLightLength = wrapper.read(Types.VAR_INT); lightPacket.write(Types.VAR_INT, blockLightLength); for (int i = 0; i < blockLightLength; i++) { lightPacket.write(Types.BYTE_ARRAY_PRIMITIVE, wrapper.read(Types.BYTE_ARRAY_PRIMITIVE)); } lightPacket.send(Protocol1_18To1_17_1.class); }); protocol.cancelClientbound(ClientboundPackets1_18.SET_SIMULATION_DISTANCE); } private void handleSpawner(final int typeId, final CompoundTag tag) { if (typeId == 8) { final CompoundTag spawnData = tag.getCompoundTag("SpawnData"); final CompoundTag entity; if (spawnData != null && (entity = spawnData.getCompoundTag("entity")) != null) { tag.put("SpawnData", entity); } } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_18to1_17_1/rewriter/EntityPacketRewriter1_18.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_18to1_17_1.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_18to1_17_1.Protocol1_18To1_17_1; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_17; import com.viaversion.viaversion.api.minecraft.entitydata.EntityDataType; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_17; import com.viaversion.viaversion.api.type.types.version.Types1_18; import com.viaversion.viaversion.protocols.v1_17_1to1_18.packet.ClientboundPackets1_18; import com.viaversion.viaversion.util.TagUtil; public final class EntityPacketRewriter1_18 extends EntityRewriter { public EntityPacketRewriter1_18(final Protocol1_18To1_17_1 protocol) { super(protocol); } @Override protected void registerPackets() { registerSetEntityData(ClientboundPackets1_18.SET_ENTITY_DATA, Types1_18.ENTITY_DATA_LIST, Types1_17.ENTITY_DATA_LIST); protocol.registerClientbound(ClientboundPackets1_18.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // Entity ID map(Types.BOOLEAN); // Hardcore map(Types.BYTE); // Gamemode map(Types.BYTE); // Previous Gamemode map(Types.STRING_ARRAY); // Worlds map(Types.NAMED_COMPOUND_TAG); // Dimension registry map(Types.NAMED_COMPOUND_TAG); // Current dimension data map(Types.STRING); // World map(Types.LONG); // Seed map(Types.VAR_INT); // Max players map(Types.VAR_INT); // Chunk radius read(Types.VAR_INT); // Read simulation distance handler(worldDataTrackerHandler(1)); handler(wrapper -> { final CompoundTag registry = wrapper.get(Types.NAMED_COMPOUND_TAG, 0); final ListTag biomes = TagUtil.getRegistryEntries(registry, "worldgen/biome"); for (final CompoundTag biome : biomes) { final CompoundTag biomeCompound = biome.getCompoundTag("element"); final StringTag category = biomeCompound.getStringTag("category"); if (category.getValue().equals("mountain")) { category.setValue("extreme_hills"); } // The client just needs something biomeCompound.putFloat("depth", 0.125F); biomeCompound.putFloat("scale", 0.05F); } // Track amount of biomes sent tracker(wrapper.user()).setBiomesSent(biomes.size()); }); } }); protocol.registerClientbound(ClientboundPackets1_18.RESPAWN, new PacketHandlers() { @Override public void register() { map(Types.NAMED_COMPOUND_TAG); // Dimension data map(Types.STRING); // World handler(worldDataTrackerHandler(0)); } }); } @Override protected void registerRewrites() { filter().handler((event, data) -> { data.setDataType(Types1_17.ENTITY_DATA_TYPES.byId(data.dataType().typeId())); EntityDataType type = data.dataType(); if (type == Types1_17.ENTITY_DATA_TYPES.particleType) { Particle particle = data.value(); if (particle.id() == 3) { // Block marker Particle.ParticleData value = particle.getArguments().remove(0); int blockState = (int) value.getValue(); if (blockState == 7786) { // Light block particle.setId(3); } else { // Else assume barrier block particle.setId(2); } return; } protocol.getParticleRewriter().rewriteParticle(event.user(), particle); } }); // Particles have already been handled registerEntityDataTypeHandler(Types1_17.ENTITY_DATA_TYPES.itemType, null, null, null, Types1_17.ENTITY_DATA_TYPES.componentType, Types1_17.ENTITY_DATA_TYPES.optionalComponentType); } @Override public EntityType typeFromId(final int typeId) { return EntityTypes1_17.getTypeFromId(typeId); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_1to1_19/Protocol1_19_1To1_19.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_1to1_19; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_19_1to1_19.rewriter.EntityPacketRewriter1_19_1; import com.viaversion.viabackwards.protocol.v1_19_1to1_19.storage.ChatRegistryStorage; import com.viaversion.viabackwards.protocol.v1_19_1to1_19.storage.ChatRegistryStorage1_19_1; import com.viaversion.viabackwards.protocol.v1_19_1to1_19.storage.NonceStorage; import com.viaversion.viabackwards.protocol.v1_19_1to1_19.storage.ReceivedMessagesStorage; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.PlayerMessageSignature; import com.viaversion.viaversion.api.minecraft.ProfileKey; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19; import com.viaversion.viaversion.api.minecraft.signature.SignableCommandArgumentsProvider; import com.viaversion.viaversion.api.minecraft.signature.model.DecoratableMessage; import com.viaversion.viaversion.api.minecraft.signature.model.MessageMetadata; import com.viaversion.viaversion.api.minecraft.signature.storage.ChatSession1_19_1; import com.viaversion.viaversion.api.protocol.Protocol; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.packet.State; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets; import com.viaversion.viaversion.protocols.base.ServerboundLoginPackets; import com.viaversion.viaversion.protocols.v1_18_2to1_19.Protocol1_18_2To1_19; import com.viaversion.viaversion.protocols.v1_18_2to1_19.packet.ClientboundPackets1_19; import com.viaversion.viaversion.protocols.v1_18_2to1_19.packet.ServerboundPackets1_19; import com.viaversion.viaversion.protocols.v1_19to1_19_1.Protocol1_19To1_19_1; import com.viaversion.viaversion.protocols.v1_19to1_19_1.packet.ClientboundPackets1_19_1; import com.viaversion.viaversion.protocols.v1_19to1_19_1.packet.ServerboundPackets1_19_1; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; import com.viaversion.viaversion.util.CipherUtil; import com.viaversion.viaversion.util.ComponentUtil; import com.viaversion.viaversion.util.Pair; import com.viaversion.viaversion.util.TagUtil; import java.security.SignatureException; import java.util.List; import java.util.UUID; import org.checkerframework.checker.nullness.qual.Nullable; public final class Protocol1_19_1To1_19 extends BackwardsProtocol { public static final int SYSTEM_CHAT_ID = 1; public static final int GAME_INFO_ID = 2; private static final UUID ZERO_UUID = new UUID(0, 0); private static final byte[] EMPTY_BYTES = new byte[0]; private final EntityPacketRewriter1_19_1 entityRewriter = new EntityPacketRewriter1_19_1(this); private final JsonNBTComponentRewriter translatableRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); public Protocol1_19_1To1_19() { super(ClientboundPackets1_19_1.class, ClientboundPackets1_19.class, ServerboundPackets1_19_1.class, ServerboundPackets1_19.class); } @Override protected void registerPackets() { super.registerPackets(); registerClientbound(ClientboundPackets1_19_1.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // Entity ID map(Types.BOOLEAN); // Hardcore map(Types.BYTE); // Gamemode map(Types.BYTE); // Previous Gamemode map(Types.STRING_ARRAY); // World List map(Types.NAMED_COMPOUND_TAG); // Dimension registry map(Types.STRING); // Dimension key map(Types.STRING); // World handler(wrapper -> { final ChatRegistryStorage chatTypeStorage = wrapper.user().get(ChatRegistryStorage1_19_1.class); chatTypeStorage.clear(); final CompoundTag registry = wrapper.get(Types.NAMED_COMPOUND_TAG, 0); final ListTag chatTypes = TagUtil.removeRegistryEntries(registry, "chat_type", new ListTag<>(CompoundTag.class)); for (final CompoundTag chatType : chatTypes) { final NumberTag idTag = chatType.getNumberTag("id"); chatTypeStorage.addChatType(idTag.asInt(), chatType); } // Replace with 1.19 chat types // Ensures that the client has a chat type for system message, with and without overlay registry.put("minecraft:chat_type", Protocol1_18_2To1_19.MAPPINGS.chatRegistry()); }); handler(entityRewriter.worldTrackerHandlerByKey()); } }); registerClientbound(ClientboundPackets1_19_1.PLAYER_CHAT, ClientboundPackets1_19.SYSTEM_CHAT, wrapper -> { wrapper.read(Types.OPTIONAL_BYTE_ARRAY_PRIMITIVE); // Previous signature final PlayerMessageSignature signature = wrapper.read(Types.PLAYER_MESSAGE_SIGNATURE); // Store message signature for last seen if (!signature.uuid().equals(ZERO_UUID) && signature.signatureBytes().length != 0) { final ReceivedMessagesStorage messagesStorage = wrapper.user().get(ReceivedMessagesStorage.class); messagesStorage.add(signature); if (messagesStorage.tickUnacknowledged() > 64) { messagesStorage.resetUnacknowledgedCount(); // Send chat acknowledgement final PacketWrapper chatAckPacket = wrapper.create(ServerboundPackets1_19_1.CHAT_ACK); chatAckPacket.write(Types.PLAYER_MESSAGE_SIGNATURE_ARRAY, messagesStorage.lastSignatures()); chatAckPacket.write(Types.OPTIONAL_PLAYER_MESSAGE_SIGNATURE, null); chatAckPacket.sendToServer(Protocol1_19_1To1_19.class); } } // Send the unsigned message if present, otherwise the signed message final String plainMessage = wrapper.read(Types.STRING); // Plain message JsonElement message = null; JsonElement decoratedMessage = wrapper.read(Types.OPTIONAL_COMPONENT); if (decoratedMessage != null) { message = decoratedMessage; } wrapper.read(Types.LONG); // Timestamp wrapper.read(Types.LONG); // Salt wrapper.read(Types.PLAYER_MESSAGE_SIGNATURE_ARRAY); // Last seen final JsonElement unsignedMessage = wrapper.read(Types.OPTIONAL_COMPONENT); if (unsignedMessage != null) { message = unsignedMessage; } if (message == null) { // If no decorated or unsigned message is given, use the plain one message = ComponentUtil.plainToJson(plainMessage); } final int filterMaskType = wrapper.read(Types.VAR_INT); if (filterMaskType == 2) { // Partially filtered wrapper.read(Types.LONG_ARRAY_PRIMITIVE); // Mask } final int chatTypeId = wrapper.read(Types.VAR_INT); final JsonElement senderName = wrapper.read(Types.COMPONENT); final JsonElement targetName = wrapper.read(Types.OPTIONAL_COMPONENT); decoratedMessage = decorateChatMessage(this, wrapper.user().get(ChatRegistryStorage1_19_1.class), chatTypeId, senderName, targetName, message); if (decoratedMessage == null) { wrapper.cancel(); return; } translatableRewriter.processText(wrapper.user(), decoratedMessage); wrapper.write(Types.COMPONENT, decoratedMessage); wrapper.write(Types.VAR_INT, SYSTEM_CHAT_ID); }); replaceClientbound(ClientboundPackets1_19_1.SYSTEM_CHAT, wrapper -> { final JsonElement content = wrapper.passthrough(Types.COMPONENT); translatableRewriter.processText(wrapper.user(), content); final boolean overlay = wrapper.read(Types.BOOLEAN); wrapper.write(Types.VAR_INT, overlay ? GAME_INFO_ID : SYSTEM_CHAT_ID); }); registerServerbound(ServerboundPackets1_19.CHAT, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Message map(Types.LONG); // Timestamp map(Types.LONG); // Salt read(Types.BYTE_ARRAY_PRIMITIVE); // Signature read(Types.BOOLEAN); // Signed preview handler(wrapper -> { final ChatSession1_19_1 chatSession = wrapper.user().get(ChatSession1_19_1.class); final ReceivedMessagesStorage messagesStorage = wrapper.user().get(ReceivedMessagesStorage.class); if (chatSession != null) { final UUID sender = wrapper.user().getProtocolInfo().getUuid(); final String message = wrapper.get(Types.STRING, 0); final long timestamp = wrapper.get(Types.LONG, 0); final long salt = wrapper.get(Types.LONG, 1); final MessageMetadata metadata = new MessageMetadata(sender, timestamp, salt); final DecoratableMessage decoratableMessage = new DecoratableMessage(message); final byte[] signature; try { signature = chatSession.signChatMessage(metadata, decoratableMessage, messagesStorage.lastSignatures()); } catch (final SignatureException e) { throw new RuntimeException(e); } wrapper.write(Types.BYTE_ARRAY_PRIMITIVE, signature); // Signature wrapper.write(Types.BOOLEAN, decoratableMessage.isDecorated()); // Signed preview } else { wrapper.write(Types.BYTE_ARRAY_PRIMITIVE, EMPTY_BYTES); // Signature wrapper.write(Types.BOOLEAN, false); // Signed preview } messagesStorage.resetUnacknowledgedCount(); wrapper.write(Types.PLAYER_MESSAGE_SIGNATURE_ARRAY, messagesStorage.lastSignatures()); wrapper.write(Types.OPTIONAL_PLAYER_MESSAGE_SIGNATURE, null); // No last unacknowledged }); } }); registerServerbound(ServerboundPackets1_19.CHAT_COMMAND, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Command map(Types.LONG); // Timestamp map(Types.LONG); // Salt handler(wrapper -> { final ReceivedMessagesStorage messagesStorage = wrapper.user().get(ReceivedMessagesStorage.class); final ChatSession1_19_1 chatSession = wrapper.user().get(ChatSession1_19_1.class); final SignableCommandArgumentsProvider argumentsProvider = Via.getManager().getProviders().get(SignableCommandArgumentsProvider.class); if (chatSession != null && argumentsProvider != null) { final int signatures = wrapper.read(Types.VAR_INT); for (int i = 0; i < signatures; i++) { wrapper.read(Types.STRING); // Argument name wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); // Signature } final UUID sender = wrapper.user().getProtocolInfo().getUuid(); final String command = wrapper.get(Types.STRING, 0); final long timestamp = wrapper.get(Types.LONG, 0); final long salt = wrapper.get(Types.LONG, 1); final MessageMetadata metadata = new MessageMetadata(sender, timestamp, salt); final List> arguments = argumentsProvider.getSignableArguments(command); wrapper.write(Types.VAR_INT, arguments.size()); for (final Pair argument : arguments) { final byte[] signature; try { signature = chatSession.signChatMessage(metadata, new DecoratableMessage(argument.value()), messagesStorage.lastSignatures()); } catch (final SignatureException e) { throw new RuntimeException(e); } wrapper.write(Types.STRING, argument.key()); wrapper.write(Types.BYTE_ARRAY_PRIMITIVE, signature); } } else { final int signatures = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < signatures; i++) { wrapper.passthrough(Types.STRING); // Argument name // Set empty signature wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); wrapper.write(Types.BYTE_ARRAY_PRIMITIVE, EMPTY_BYTES); } } wrapper.passthrough(Types.BOOLEAN); // Signed preview messagesStorage.resetUnacknowledgedCount(); wrapper.write(Types.PLAYER_MESSAGE_SIGNATURE_ARRAY, messagesStorage.lastSignatures()); wrapper.write(Types.OPTIONAL_PLAYER_MESSAGE_SIGNATURE, null); // No last unacknowledged }); } }); replaceClientbound(ClientboundPackets1_19_1.SERVER_DATA, new PacketHandlers() { @Override public void register() { map(Types.OPTIONAL_COMPONENT); // Motd map(Types.OPTIONAL_STRING); // Encoded icon map(Types.BOOLEAN); // Previews chat read(Types.BOOLEAN); // Enforces secure chat } }); registerServerbound(State.LOGIN, ServerboundLoginPackets.HELLO, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Name handler(wrapper -> { final ProfileKey profileKey = wrapper.read(Types.OPTIONAL_PROFILE_KEY); // Profile Key final ChatSession1_19_1 chatSession = wrapper.user().get(ChatSession1_19_1.class); wrapper.write(Types.OPTIONAL_PROFILE_KEY, chatSession == null ? null : chatSession.getProfileKey()); // Profile Key wrapper.write(Types.OPTIONAL_UUID, chatSession == null ? null : chatSession.getUuid()); // Profile uuid if (profileKey == null || chatSession != null) { wrapper.user().put(new NonceStorage(null)); } }); } }); registerClientbound(State.LOGIN, ClientboundLoginPackets.HELLO, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Server id handler(wrapper -> { if (wrapper.user().has(NonceStorage.class)) { return; } final byte[] publicKey = wrapper.passthrough(Types.BYTE_ARRAY_PRIMITIVE); final byte[] nonce = wrapper.passthrough(Types.BYTE_ARRAY_PRIMITIVE); wrapper.user().put(new NonceStorage(CipherUtil.encryptNonce(publicKey, nonce))); }); } }); registerServerbound(State.LOGIN, ServerboundLoginPackets.ENCRYPTION_KEY, new PacketHandlers() { @Override public void register() { map(Types.BYTE_ARRAY_PRIMITIVE); // Key handler(wrapper -> { final NonceStorage nonceStorage = wrapper.user().remove(NonceStorage.class); if (nonceStorage.nonce() == null) { return; } final boolean isNonce = wrapper.read(Types.BOOLEAN); wrapper.write(Types.BOOLEAN, true); if (!isNonce) { // Should never be true at this point, but /shrug otherwise wrapper.read(Types.LONG); // Salt wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); // Signature wrapper.write(Types.BYTE_ARRAY_PRIMITIVE, nonceStorage.nonce()); } }); } }); registerClientbound(State.LOGIN, ClientboundLoginPackets.CUSTOM_QUERY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); map(Types.STRING); handler(wrapper -> { final String identifier = wrapper.get(Types.STRING, 0); if (identifier.equals("velocity:player_info")) { byte[] data = wrapper.passthrough(Types.REMAINING_BYTES); // Velocity modern forwarding version above 1 includes the players public key. // This is an issue because the player does not have a public key. // Velocity itself does adjust the version accordingly: https://github.com/PaperMC/Velocity/blob/1a3fba4250553702d9dcd05731d04347bfc24c9f/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java#L176-L197 // However this becomes an issue when an 1.19.0 client tries to join a 1.19.1 server. // (The protocol translation will go 1.19.1 -> 1.18.2 -> 1.19.0) // The player does have a public key, but an outdated one. // Velocity modern forwarding versions: https://github.com/PaperMC/Velocity/blob/1a3fba4250553702d9dcd05731d04347bfc24c9f/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java#L27-L29 // And the version can be specified with a single byte: https://github.com/PaperMC/Velocity/blob/1a3fba4250553702d9dcd05731d04347bfc24c9f/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java#L88 if (data.length == 1 && data[0] > 1) { data[0] = 1; } else if (data.length == 0) { // Or the version is omitted (default version would be used) data = new byte[]{1}; wrapper.set(Types.REMAINING_BYTES, 0, data); } else { getLogger().warning("Received unexpected data in velocity:player_info (length=" + data.length + ")"); } } }); } }); cancelClientbound(ClientboundPackets1_19_1.CUSTOM_CHAT_COMPLETIONS); // Can't do anything with them unless we add clutter clients with fake player profiles cancelClientbound(ClientboundPackets1_19_1.DELETE_CHAT); // Can't do without the old "send 50 empty lines and then resend previous messages" trick cancelClientbound(ClientboundPackets1_19_1.PLAYER_CHAT_HEADER); } @Override public void init(final UserConnection user) { user.put(new ChatRegistryStorage1_19_1()); user.put(new ReceivedMessagesStorage()); addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_19.PLAYER)); } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public EntityPacketRewriter1_19_1 getEntityRewriter() { return entityRewriter; } public static @Nullable JsonElement decorateChatMessage(final Protocol protocol, final ChatRegistryStorage chatRegistryStorage, final int chatTypeId, final JsonElement senderName, @Nullable final JsonElement targetName, final JsonElement message) { CompoundTag chatType = chatRegistryStorage.chatType(chatTypeId); if (chatType == null) { protocol.getLogger().warning("Chat message has unknown chat type id " + chatTypeId + ". Message: " + message); return null; } chatType = chatType.getCompoundTag("element").getCompoundTag("chat"); if (chatType == null) { return null; } return Protocol1_19To1_19_1.translatabaleComponentFromTag(chatType, senderName, targetName, message); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_1to1_19/rewriter/EntityPacketRewriter1_19_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_1to1_19.rewriter; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_19_1to1_19.Protocol1_19_1To1_19; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19; import com.viaversion.viaversion.api.type.types.version.Types1_19; import com.viaversion.viaversion.protocols.v1_19to1_19_1.packet.ClientboundPackets1_19_1; public final class EntityPacketRewriter1_19_1 extends EntityRewriter { public EntityPacketRewriter1_19_1(final Protocol1_19_1To1_19 protocol) { super(protocol, Types1_19.ENTITY_DATA_TYPES.optionalComponentType, Types1_19.ENTITY_DATA_TYPES.booleanType); } @Override protected void registerPackets() { registerSetEntityData(ClientboundPackets1_19_1.SET_ENTITY_DATA, Types1_19.ENTITY_DATA_LIST); } @Override public void registerRewrites() { filter().type(EntityTypes1_19.ALLAY).cancel(16); // Dancing filter().type(EntityTypes1_19.ALLAY).cancel(17); // Can duplicate } @Override public EntityType typeFromId(final int typeId) { return EntityTypes1_19.getTypeFromId(typeId); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_1to1_19/storage/ChatRegistryStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_1to1_19.storage; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectOpenHashMap; import com.viaversion.viaversion.protocols.v1_18_2to1_19.Protocol1_18_2To1_19; import org.checkerframework.checker.nullness.qual.Nullable; public abstract class ChatRegistryStorage implements StorableObject { private final Int2ObjectMap chatTypes = new Int2ObjectOpenHashMap<>(); public @Nullable CompoundTag chatType(final int id) { return chatTypes.isEmpty() ? Protocol1_18_2To1_19.MAPPINGS.chatType(id) : chatTypes.get(id); } public void addChatType(final int id, final CompoundTag chatType) { chatTypes.put(id, chatType); } public void clear() { chatTypes.clear(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_1to1_19/storage/ChatRegistryStorage1_19_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_1to1_19.storage; public final class ChatRegistryStorage1_19_1 extends ChatRegistryStorage { } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_1to1_19/storage/NonceStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_1to1_19.storage; import com.viaversion.viaversion.api.connection.StorableObject; public record NonceStorage(byte[] nonce) implements StorableObject { } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_1to1_19/storage/ReceivedMessagesStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_1to1_19.storage; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.minecraft.PlayerMessageSignature; import java.util.Arrays; public final class ReceivedMessagesStorage implements StorableObject { private final PlayerMessageSignature[] signatures = new PlayerMessageSignature[5]; private int size; private int unacknowledged; public void add(final PlayerMessageSignature signature) { PlayerMessageSignature toPush = signature; for (int i = 0; i < this.size; ++i) { final PlayerMessageSignature entry = this.signatures[i]; this.signatures[i] = toPush; toPush = entry; if (entry.uuid().equals(signature.uuid())) { return; } } if (this.size < this.signatures.length) { this.signatures[this.size++] = toPush; } } public PlayerMessageSignature[] lastSignatures() { return Arrays.copyOf(this.signatures, size); } public int tickUnacknowledged() { return unacknowledged++; } public void resetUnacknowledgedCount() { unacknowledged = 0; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_3to1_19_1/Protocol1_19_3To1_19_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_3to1_19_1; import com.google.common.base.Preconditions; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_19_1to1_19.Protocol1_19_1To1_19; import com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.rewriter.BlockItemPacketRewriter1_19_3; import com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.rewriter.EntityPacketRewriter1_19_3; import com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.storage.ChatSessionStorage; import com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.storage.ChatTypeStorage1_19_3; import com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.storage.NonceStorage; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.Holder; import com.viaversion.viaversion.api.minecraft.PlayerMessageSignature; import com.viaversion.viaversion.api.minecraft.ProfileKey; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.SoundEvent; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_3; import com.viaversion.viaversion.api.minecraft.signature.SignableCommandArgumentsProvider; import com.viaversion.viaversion.api.minecraft.signature.model.MessageMetadata; import com.viaversion.viaversion.api.minecraft.signature.storage.ChatSession1_19_3; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.packet.State; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.BitSetType; import com.viaversion.viaversion.api.type.types.ByteArrayType; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_18; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets; import com.viaversion.viaversion.protocols.base.ServerboundLoginPackets; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.Protocol1_19_1To1_19_3; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ServerboundPackets1_19_3; import com.viaversion.viaversion.protocols.v1_19to1_19_1.packet.ClientboundPackets1_19_1; import com.viaversion.viaversion.protocols.v1_19to1_19_1.packet.ServerboundPackets1_19_1; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.CommandRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; import com.viaversion.viaversion.util.CipherUtil; import com.viaversion.viaversion.util.ComponentUtil; import com.viaversion.viaversion.util.Pair; import java.security.SignatureException; import java.util.BitSet; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; public final class Protocol1_19_3To1_19_1 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.19.3", "1.19", Protocol1_19_1To1_19_3.class); public static final ByteArrayType.OptionalByteArrayType OPTIONAL_SIGNATURE_BYTES_TYPE = new ByteArrayType.OptionalByteArrayType(256); public static final ByteArrayType SIGNATURE_BYTES_TYPE = new ByteArrayType(256); private final EntityPacketRewriter1_19_3 entityRewriter = new EntityPacketRewriter1_19_3(this); private final BlockItemPacketRewriter1_19_3 itemRewriter = new BlockItemPacketRewriter1_19_3(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final JsonNBTComponentRewriter translatableRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BlockRewriter blockRewriter = BlockRewriter.for1_18(this, ChunkType1_18::new); public Protocol1_19_3To1_19_1() { super(ClientboundPackets1_19_3.class, ClientboundPackets1_19_1.class, ServerboundPackets1_19_3.class, ServerboundPackets1_19_1.class); } @Override protected void registerPackets() { super.registerPackets(); replaceClientbound(ClientboundPackets1_19_3.SOUND, wrapper -> { final String mappedIdentifier = rewriteSound(wrapper); if (mappedIdentifier != null) { wrapper.write(Types.STRING, mappedIdentifier); wrapper.setPacketType(ClientboundPackets1_19_1.CUSTOM_SOUND); } }); replaceClientbound(ClientboundPackets1_19_3.SOUND_ENTITY, wrapper -> { final String mappedIdentifier = rewriteSound(wrapper); if (mappedIdentifier == null) { return; } final int mappedId = MAPPINGS.getFullSoundMappings().mappedId(mappedIdentifier); if (mappedId == -1) { wrapper.cancel(); return; } wrapper.write(Types.VAR_INT, mappedId); }); tagRewriter.addEmptyTag(RegistryType.BLOCK, "minecraft:non_flammable_wood"); tagRewriter.addEmptyTag(RegistryType.ITEM, "minecraft:overworld_natural_logs"); final CommandRewriter commandRewriter = new CommandRewriter<>(this); replaceClientbound(ClientboundPackets1_19_3.COMMANDS, wrapper -> { final int size = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < size; i++) { final byte flags = wrapper.passthrough(Types.BYTE); wrapper.passthrough(Types.VAR_INT_ARRAY_PRIMITIVE); // Children indices if ((flags & 0x08) != 0) { wrapper.passthrough(Types.VAR_INT); // Redirect node index } final int nodeType = flags & 0x03; if (nodeType == 1 || nodeType == 2) { // Literal/argument node wrapper.passthrough(Types.STRING); // Name } if (nodeType == 2) { // Argument node final int argumentTypeId = wrapper.read(Types.VAR_INT); final int mappedArgumentTypeId = MAPPINGS.getArgumentTypeMappings().getNewId(argumentTypeId); Preconditions.checkArgument(mappedArgumentTypeId != -1, "Unknown command argument type id: " + argumentTypeId); wrapper.write(Types.VAR_INT, mappedArgumentTypeId); final String identifier = MAPPINGS.getArgumentTypeMappings().identifier(argumentTypeId); commandRewriter.handleArgument(wrapper, identifier); if (identifier.equals("minecraft:gamemode")) { wrapper.write(Types.VAR_INT, 0); // Word } if ((flags & 0x10) != 0) { wrapper.passthrough(Types.STRING); // Suggestion type } } } wrapper.passthrough(Types.VAR_INT); // Root node index }); replaceClientbound(ClientboundPackets1_19_3.SERVER_DATA, new PacketHandlers() { @Override public void register() { map(Types.OPTIONAL_COMPONENT); // Motd map(Types.OPTIONAL_STRING); // Encoded icon create(Types.BOOLEAN, false); // Previews chat } }); // Remove the key once again registerServerbound(State.LOGIN, ServerboundLoginPackets.HELLO, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Name handler(wrapper -> { final ProfileKey profileKey = wrapper.read(Types.OPTIONAL_PROFILE_KEY); if (profileKey == null) { wrapper.user().put(new NonceStorage(null)); } }); } }); registerClientbound(State.LOGIN, ClientboundLoginPackets.HELLO, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Server id handler(wrapper -> { if (wrapper.user().has(NonceStorage.class)) { return; } final byte[] publicKey = wrapper.passthrough(Types.BYTE_ARRAY_PRIMITIVE); final byte[] nonce = wrapper.passthrough(Types.BYTE_ARRAY_PRIMITIVE); wrapper.user().put(new NonceStorage(CipherUtil.encryptNonce(publicKey, nonce))); }); } }); registerServerbound(State.LOGIN, ServerboundLoginPackets.ENCRYPTION_KEY, new PacketHandlers() { @Override public void register() { map(Types.BYTE_ARRAY_PRIMITIVE); // Keys handler(wrapper -> { final NonceStorage nonceStorage = wrapper.user().remove(NonceStorage.class); final boolean isNonce = wrapper.read(Types.BOOLEAN); if (!isNonce) { wrapper.read(Types.LONG); // Salt wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); // Signature wrapper.write(Types.BYTE_ARRAY_PRIMITIVE, nonceStorage.nonce() != null ? nonceStorage.nonce() : new byte[0]); } }); } }); registerServerbound(ServerboundPackets1_19_1.CHAT, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Message map(Types.LONG); // Timestamp map(Types.LONG); // Salt read(Types.BYTE_ARRAY_PRIMITIVE); // Signature read(Types.BOOLEAN); // Signed preview read(Types.PLAYER_MESSAGE_SIGNATURE_ARRAY); // Last seen messages read(Types.OPTIONAL_PLAYER_MESSAGE_SIGNATURE); // Last received message handler(wrapper -> { final ChatSession1_19_3 chatSession = wrapper.user().get(ChatSession1_19_3.class); if (chatSession != null) { final String message = wrapper.get(Types.STRING, 0); final long timestamp = wrapper.get(Types.LONG, 0); final long salt = wrapper.get(Types.LONG, 1); final MessageMetadata metadata = new MessageMetadata(null, timestamp, salt); final byte[] signature; try { signature = chatSession.signChatMessage(metadata, message, new PlayerMessageSignature[0]); } catch (final SignatureException e) { throw new RuntimeException(e); } wrapper.write(Protocol1_19_3To1_19_1.OPTIONAL_SIGNATURE_BYTES_TYPE, signature); // Signature } else { wrapper.write(Protocol1_19_3To1_19_1.OPTIONAL_SIGNATURE_BYTES_TYPE, null); // Signature } //TODO is this fine (probably not)? same for chat_command wrapper.write(Types.VAR_INT, 0); // Offset wrapper.write(new BitSetType(20), new BitSet(20)); // Acknowledged }); } }); registerServerbound(ServerboundPackets1_19_1.CHAT_COMMAND, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Command map(Types.LONG); // Timestamp map(Types.LONG); // Salt handler(wrapper -> { final ChatSession1_19_3 chatSession = wrapper.user().get(ChatSession1_19_3.class); final SignableCommandArgumentsProvider argumentsProvider = Via.getManager().getProviders().get(SignableCommandArgumentsProvider.class); final String command = wrapper.get(Types.STRING, 0); final long timestamp = wrapper.get(Types.LONG, 0); final long salt = wrapper.get(Types.LONG, 1); final int signatures = wrapper.read(Types.VAR_INT); for (int i = 0; i < signatures; i++) { wrapper.read(Types.STRING); // Name wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); // Signature } wrapper.read(Types.BOOLEAN); // Signed preview if (chatSession != null && argumentsProvider != null) { final MessageMetadata metadata = new MessageMetadata(null, timestamp, salt); final List> arguments = argumentsProvider.getSignableArguments(command); wrapper.write(Types.VAR_INT, arguments.size()); for (final Pair argument : arguments) { final byte[] signature; try { signature = chatSession.signChatMessage(metadata, argument.value(), new PlayerMessageSignature[0]); } catch (final SignatureException e) { throw new RuntimeException(e); } wrapper.write(Types.STRING, argument.key()); wrapper.write(Protocol1_19_3To1_19_1.SIGNATURE_BYTES_TYPE, signature); } } else { wrapper.write(Types.VAR_INT, 0); // No signatures } final int offset = 0; final BitSet acknowledged = new BitSet(20); wrapper.write(Types.VAR_INT, offset); wrapper.write(new BitSetType(20), acknowledged); }); read(Types.PLAYER_MESSAGE_SIGNATURE_ARRAY); // Last seen messages read(Types.OPTIONAL_PLAYER_MESSAGE_SIGNATURE); // Last received message } }); registerClientbound(ClientboundPackets1_19_3.PLAYER_CHAT, ClientboundPackets1_19_1.SYSTEM_CHAT, new PacketHandlers() { @Override public void register() { read(Types.UUID); // Sender read(Types.VAR_INT); // Index read(OPTIONAL_SIGNATURE_BYTES_TYPE); // Signature handler(wrapper -> { final String plainContent = wrapper.read(Types.STRING); wrapper.read(Types.LONG); // Timestamp wrapper.read(Types.LONG); // Salt final int lastSeen = wrapper.read(Types.VAR_INT); for (int i = 0; i < lastSeen; i++) { final int index = wrapper.read(Types.VAR_INT); if (index == 0) { wrapper.read(SIGNATURE_BYTES_TYPE); } } final JsonElement unsignedContent = wrapper.read(Types.OPTIONAL_COMPONENT); final JsonElement content = unsignedContent != null ? unsignedContent : ComponentUtil.plainToJson(plainContent); translatableRewriter.processText(wrapper.user(), content); final int filterMaskType = wrapper.read(Types.VAR_INT); if (filterMaskType == 2) { wrapper.read(Types.LONG_ARRAY_PRIMITIVE); // Mask } final int chatTypeId = wrapper.read(Types.VAR_INT); final JsonElement senderName = wrapper.read(Types.COMPONENT); final JsonElement targetName = wrapper.read(Types.OPTIONAL_COMPONENT); final JsonElement result = Protocol1_19_1To1_19.decorateChatMessage(Protocol1_19_3To1_19_1.this, wrapper.user().get(ChatTypeStorage1_19_3.class), chatTypeId, senderName, targetName, content); if (result == null) { wrapper.cancel(); return; } wrapper.write(Types.COMPONENT, result); wrapper.write(Types.BOOLEAN, false); }); } }); registerClientbound(ClientboundPackets1_19_3.DISGUISED_CHAT, ClientboundPackets1_19_1.SYSTEM_CHAT, wrapper -> { final JsonElement content = wrapper.read(Types.COMPONENT); translatableRewriter.processText(wrapper.user(), content); final int chatTypeId = wrapper.read(Types.VAR_INT); final JsonElement senderName = wrapper.read(Types.COMPONENT); final JsonElement targetName = wrapper.read(Types.OPTIONAL_COMPONENT); final JsonElement result = Protocol1_19_1To1_19.decorateChatMessage(this, wrapper.user().get(ChatTypeStorage1_19_3.class), chatTypeId, senderName, targetName, content); if (result == null) { wrapper.cancel(); return; } wrapper.write(Types.COMPONENT, result); wrapper.write(Types.BOOLEAN, false); }); cancelClientbound(ClientboundPackets1_19_3.UPDATE_ENABLED_FEATURES); cancelServerbound(ServerboundPackets1_19_1.CHAT_PREVIEW); cancelServerbound(ServerboundPackets1_19_1.CHAT_ACK); } private @Nullable String rewriteSound(final PacketWrapper wrapper) { final Holder holder = wrapper.read(Types.SOUND_EVENT); if (holder.hasId()) { final int mappedId = MAPPINGS.getSoundMappings().getNewId(holder.id()); if (mappedId == -1) { wrapper.cancel(); return null; } wrapper.write(Types.VAR_INT, mappedId); return null; } // Convert the resource location to the corresponding integer id final String soundIdentifier = holder.value().identifier(); final String mappedIdentifier = MAPPINGS.getMappedNamedSound(soundIdentifier); if (mappedIdentifier == null) { return soundIdentifier; } if (mappedIdentifier.isEmpty()) { wrapper.cancel(); return null; } return mappedIdentifier; } @Override public void init(final UserConnection user) { user.put(new ChatSessionStorage()); user.put(new ChatTypeStorage1_19_3()); addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_19_3.PLAYER)); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public BlockItemPacketRewriter1_19_3 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public EntityPacketRewriter1_19_3 getEntityRewriter() { return entityRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_3to1_19_1/rewriter/BlockItemPacketRewriter1_19_3.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.rewriter; import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.Protocol1_19_3To1_19_1; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3; import com.viaversion.viaversion.protocols.v1_19to1_19_1.packet.ServerboundPackets1_19_1; import com.viaversion.viaversion.rewriter.RecipeRewriter; import com.viaversion.viaversion.util.Key; public final class BlockItemPacketRewriter1_19_3 extends BackwardsItemRewriter { public BlockItemPacketRewriter1_19_3(final Protocol1_19_3To1_19_1 protocol) { super(protocol, Types.ITEM1_13_2, Types.ITEM1_13_2_ARRAY); } @Override protected void registerPackets() { protocol.registerClientbound(ClientboundPackets1_19_3.EXPLODE, new PacketHandlers() { @Override public void register() { map(Types.DOUBLE, Types.FLOAT); // X map(Types.DOUBLE, Types.FLOAT); // Y map(Types.DOUBLE, Types.FLOAT); // Z } }); final RecipeRewriter recipeRewriter = new RecipeRewriter<>(protocol); protocol.registerClientbound(ClientboundPackets1_19_3.UPDATE_RECIPES, wrapper -> { final int size = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < size; i++) { final String type = Key.stripMinecraftNamespace(wrapper.passthrough(Types.STRING)); wrapper.passthrough(Types.STRING); // Recipe Identifier switch (type) { case "crafting_shapeless" -> { wrapper.passthrough(Types.STRING); // Group wrapper.read(Types.VAR_INT); // Crafting book category final int ingredients = wrapper.passthrough(Types.VAR_INT); for (int j = 0; j < ingredients; j++) { final Item[] items = wrapper.passthrough(Types.ITEM1_13_2_ARRAY); // Ingredients for (int k = 0; k < items.length; k++) { items[k] = handleItemToClient(wrapper.user(), items[k]); } } passthroughClientboundItem(wrapper); // Result } case "crafting_shaped" -> { final int ingredients = wrapper.passthrough(Types.VAR_INT) * wrapper.passthrough(Types.VAR_INT); wrapper.passthrough(Types.STRING); // Group wrapper.read(Types.VAR_INT); // Crafting book category for (int j = 0; j < ingredients; j++) { final Item[] items = wrapper.passthrough(Types.ITEM1_13_2_ARRAY); // Ingredients for (int k = 0; k < items.length; k++) { items[k] = handleItemToClient(wrapper.user(), items[k]); } } passthroughClientboundItem(wrapper); // Result } case "smelting", "campfire_cooking", "blasting", "smoking" -> { wrapper.passthrough(Types.STRING); // Group wrapper.read(Types.VAR_INT); // Crafting book category final Item[] items = wrapper.passthrough(Types.ITEM1_13_2_ARRAY); // Ingredients for (int j = 0; j < items.length; j++) { items[j] = handleItemToClient(wrapper.user(), items[j]); } passthroughClientboundItem(wrapper); // Result wrapper.passthrough(Types.FLOAT); // EXP wrapper.passthrough(Types.VAR_INT); // Cooking time } case "crafting_special_armordye", "crafting_special_bookcloning", "crafting_special_mapcloning", "crafting_special_mapextending", "crafting_special_firework_rocket", "crafting_special_firework_star", "crafting_special_firework_star_fade", "crafting_special_tippedarrow", "crafting_special_bannerduplicate", "crafting_special_shielddecoration", "crafting_special_shulkerboxcoloring", "crafting_special_suspiciousstew", "crafting_special_repairitem" -> wrapper.read(Types.VAR_INT); // Crafting book category default -> recipeRewriter.handleRecipeType(wrapper, type); } } }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_3to1_19_1/rewriter/EntityPacketRewriter1_19_3.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.Protocol1_19_3To1_19_1; import com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.storage.ChatTypeStorage1_19_3; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ProfileKey; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_3; import com.viaversion.viaversion.api.minecraft.signature.storage.ChatSession1_19_3; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_19; import com.viaversion.viaversion.api.type.types.version.Types1_19_3; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ServerboundPackets1_19_3; import com.viaversion.viaversion.protocols.v1_19to1_19_1.packet.ClientboundPackets1_19_1; import com.viaversion.viaversion.util.TagUtil; import java.util.BitSet; import java.util.UUID; import org.checkerframework.checker.nullness.qual.Nullable; public final class EntityPacketRewriter1_19_3 extends EntityRewriter { private static final int[] PROFILE_ACTIONS = {2, 3, 4, 5}; // Ignore initialize chat; add player already handled before private static final int ADD_PLAYER = 0; private static final int INITIALIZE_CHAT = 1; private static final int UPDATE_GAMEMODE = 2; private static final int UPDATE_LISTED = 3; private static final int UPDATE_LATENCY = 4; private static final int UPDATE_DISPLAYNAME = 5; public EntityPacketRewriter1_19_3(final Protocol1_19_3To1_19_1 protocol) { super(protocol, Types1_19.ENTITY_DATA_TYPES.optionalComponentType, Types1_19.ENTITY_DATA_TYPES.booleanType); } @Override protected void registerPackets() { registerSetEntityData(ClientboundPackets1_19_3.SET_ENTITY_DATA, Types1_19_3.ENTITY_DATA_LIST, Types1_19.ENTITY_DATA_LIST); protocol.registerClientbound(ClientboundPackets1_19_3.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // Entity id map(Types.BOOLEAN); // Hardcore map(Types.BYTE); // Gamemode map(Types.BYTE); // Previous Gamemode map(Types.STRING_ARRAY); // World List map(Types.NAMED_COMPOUND_TAG); // Dimension registry map(Types.STRING); // Dimension key map(Types.STRING); // World handler(dimensionDataHandler()); handler(biomeSizeTracker()); handler(worldDataTrackerHandlerByKey()); handler(wrapper -> { final ChatTypeStorage1_19_3 chatTypeStorage = wrapper.user().get(ChatTypeStorage1_19_3.class); chatTypeStorage.clear(); final CompoundTag registry = wrapper.get(Types.NAMED_COMPOUND_TAG, 0); final ListTag chatTypes = TagUtil.getRegistryEntries(registry, "chat_type", new ListTag<>(CompoundTag.class)); for (final CompoundTag chatType : chatTypes) { final NumberTag idTag = chatType.getNumberTag("id"); chatTypeStorage.addChatType(idTag.asInt(), chatType); } }); handler(wrapper -> { final ChatSession1_19_3 chatSession = wrapper.user().get(ChatSession1_19_3.class); if (chatSession != null) { final PacketWrapper chatSessionUpdate = wrapper.create(ServerboundPackets1_19_3.CHAT_SESSION_UPDATE); chatSessionUpdate.write(Types.UUID, chatSession.getSessionId()); chatSessionUpdate.write(Types.PROFILE_KEY, chatSession.getProfileKey()); chatSessionUpdate.sendToServer(Protocol1_19_3To1_19_1.class); } }); } }); protocol.registerClientbound(ClientboundPackets1_19_3.RESPAWN, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Dimension map(Types.STRING); // World map(Types.LONG); // Seed map(Types.UNSIGNED_BYTE); // Gamemode map(Types.BYTE); // Previous gamemode map(Types.BOOLEAN); // Debug map(Types.BOOLEAN); // Flat handler(worldDataTrackerHandlerByKey()); handler(wrapper -> { // Old clients will always keep entity data (packed here as 0x02), nothing we can do there final byte keepDataMask = wrapper.read(Types.BYTE); wrapper.write(Types.BOOLEAN, (keepDataMask & 1) != 0); // Keep attributes }); } }); protocol.registerClientbound(ClientboundPackets1_19_3.PLAYER_INFO_UPDATE, ClientboundPackets1_19_1.PLAYER_INFO, wrapper -> { wrapper.cancel(); final BitSet actions = wrapper.read(Types.PROFILE_ACTIONS_ENUM1_19_3); final int entries = wrapper.read(Types.VAR_INT); if (actions.get(ADD_PLAYER)) { // Special case, as we need to write everything into one action final PacketWrapper playerInfoPacket = wrapper.create(ClientboundPackets1_19_1.PLAYER_INFO); playerInfoPacket.write(Types.VAR_INT, 0); playerInfoPacket.write(Types.VAR_INT, entries); for (int i = 0; i < entries; i++) { playerInfoPacket.write(Types.UUID, wrapper.read(Types.UUID)); playerInfoPacket.write(Types.STRING, wrapper.read(Types.STRING)); // Player Name playerInfoPacket.write(Types.PROFILE_PROPERTY_ARRAY, wrapper.read(Types.PROFILE_PROPERTY_ARRAY)); // Now check for the other parts individually and add dummy values if not present final ProfileKey profileKey; if (actions.get(INITIALIZE_CHAT) && wrapper.read(Types.BOOLEAN)) { wrapper.read(Types.UUID); // Session UUID profileKey = wrapper.read(Types.PROFILE_KEY); } else { profileKey = null; } final int gamemode = actions.get(UPDATE_GAMEMODE) ? wrapper.read(Types.VAR_INT) : 0; if (actions.get(UPDATE_LISTED)) { wrapper.read(Types.BOOLEAN); // Listed - throw away } final int latency = actions.get(UPDATE_LATENCY) ? wrapper.read(Types.VAR_INT) : 0; final JsonElement displayName = actions.get(UPDATE_DISPLAYNAME) ? wrapper.read(Types.OPTIONAL_COMPONENT) : null; playerInfoPacket.write(Types.VAR_INT, gamemode); playerInfoPacket.write(Types.VAR_INT, latency); playerInfoPacket.write(Types.OPTIONAL_COMPONENT, displayName); playerInfoPacket.write(Types.OPTIONAL_PROFILE_KEY, profileKey); } playerInfoPacket.send(Protocol1_19_3To1_19_1.class); return; } final PlayerProfileUpdate[] updates = new PlayerProfileUpdate[entries]; for (int i = 0; i < entries; i++) { final UUID uuid = wrapper.read(Types.UUID); int gamemode = 0; int latency = 0; JsonElement displayName = null; for (final int action : PROFILE_ACTIONS) { if (!actions.get(action)) { continue; } switch (action) { case UPDATE_GAMEMODE -> gamemode = wrapper.read(Types.VAR_INT); case UPDATE_LISTED -> wrapper.read(Types.BOOLEAN); // Throw away case UPDATE_LATENCY -> latency = wrapper.read(Types.VAR_INT); case UPDATE_DISPLAYNAME -> displayName = wrapper.read(Types.OPTIONAL_COMPONENT); } } updates[i] = new PlayerProfileUpdate(uuid, gamemode, latency, displayName); } if (actions.get(UPDATE_GAMEMODE)) { sendPlayerProfileUpdate(wrapper.user(), 1, updates); } else if (actions.get(UPDATE_LATENCY)) { sendPlayerProfileUpdate(wrapper.user(), 2, updates); } else if (actions.get(UPDATE_DISPLAYNAME)) { sendPlayerProfileUpdate(wrapper.user(), 3, updates); } }); protocol.registerClientbound(ClientboundPackets1_19_3.PLAYER_INFO_REMOVE, ClientboundPackets1_19_1.PLAYER_INFO, wrapper -> { final UUID[] uuids = wrapper.read(Types.UUID_ARRAY); wrapper.write(Types.VAR_INT, 4); // Remove player wrapper.write(Types.VAR_INT, uuids.length); for (final UUID uuid : uuids) { wrapper.write(Types.UUID, uuid); } }); } private void sendPlayerProfileUpdate(final UserConnection connection, final int action, final PlayerProfileUpdate[] updates) { final PacketWrapper playerInfoPacket = PacketWrapper.create(ClientboundPackets1_19_1.PLAYER_INFO, connection); playerInfoPacket.write(Types.VAR_INT, action); playerInfoPacket.write(Types.VAR_INT, updates.length); for (final PlayerProfileUpdate update : updates) { playerInfoPacket.write(Types.UUID, update.uuid()); if (action == 1) { playerInfoPacket.write(Types.VAR_INT, update.gamemode()); } else if (action == 2) { playerInfoPacket.write(Types.VAR_INT, update.latency()); } else if (action == 3) { playerInfoPacket.write(Types.OPTIONAL_COMPONENT, update.displayName()); } else { throw new IllegalArgumentException("Invalid action: " + action); } } playerInfoPacket.send(Protocol1_19_3To1_19_1.class); } @Override public void registerRewrites() { filter().handler((event, data) -> { final int id = data.dataType().typeId(); if (id > 2) { data.setDataType(Types1_19.ENTITY_DATA_TYPES.byId(id - 1)); // long added } else if (id != 2) { data.setDataType(Types1_19.ENTITY_DATA_TYPES.byId(id)); } }); registerEntityDataTypeHandler(Types1_19.ENTITY_DATA_TYPES.itemType, null, Types1_19.ENTITY_DATA_TYPES.optionalBlockStateType, Types1_19.ENTITY_DATA_TYPES.particleType, Types1_19.ENTITY_DATA_TYPES.componentType, Types1_19.ENTITY_DATA_TYPES.optionalComponentType); registerBlockStateHandler(EntityTypes1_19_3.ABSTRACT_MINECART, 11); filter().dataType(Types1_19.ENTITY_DATA_TYPES.poseType).handler((event, data) -> { // Sitting pose added final int pose = data.value(); if (pose == 10) { data.setValue(0); // Standing } else if (pose > 10) { data.setValue(pose - 1); } }); filter().type(EntityTypes1_19_3.CAMEL).cancel(19); // Dashing filter().type(EntityTypes1_19_3.CAMEL).cancel(20); // Last pose change time } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); mapEntityTypeWithData(EntityTypes1_19_3.CAMEL, EntityTypes1_19_3.DONKEY).jsonName(); } @Override public EntityType typeFromId(final int typeId) { return EntityTypes1_19_3.getTypeFromId(typeId); } private record PlayerProfileUpdate(UUID uuid, int gamemode, int latency, @Nullable JsonElement displayName) { } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_3to1_19_1/storage/ChatSessionStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.storage; import com.viaversion.viaversion.api.connection.StorableObject; import java.util.UUID; public final class ChatSessionStorage implements StorableObject { private final UUID uuid = UUID.randomUUID(); public UUID uuid() { return uuid; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_3to1_19_1/storage/ChatTypeStorage1_19_3.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.storage; import com.viaversion.viabackwards.protocol.v1_19_1to1_19.storage.ChatRegistryStorage; public final class ChatTypeStorage1_19_3 extends ChatRegistryStorage { } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_3to1_19_1/storage/NonceStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_3to1_19_1.storage; import com.viaversion.viaversion.api.connection.StorableObject; import org.checkerframework.checker.nullness.qual.Nullable; public record NonceStorage(byte @Nullable [] nonce) implements StorableObject { } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/Protocol1_19_4To1_19_3.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_4to1_19_3; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.rewriter.BlockItemPacketRewriter1_19_4; import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.rewriter.EntityPacketRewriter1_19_4; import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.storage.EntityTracker1_19_4; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_18; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ServerboundPackets1_19_3; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.Protocol1_19_3To1_19_4; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ClientboundPackets1_19_4; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ServerboundPackets1_19_4; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.CommandRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; import java.nio.charset.StandardCharsets; import java.util.Base64; public final class Protocol1_19_4To1_19_3 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.19.4", "1.19.3", Protocol1_19_3To1_19_4.class); private final EntityPacketRewriter1_19_4 entityRewriter = new EntityPacketRewriter1_19_4(this); private final BlockItemPacketRewriter1_19_4 itemRewriter = new BlockItemPacketRewriter1_19_4(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final JsonNBTComponentRewriter translatableRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BlockRewriter blockRewriter = BlockRewriter.for1_18(this, ChunkType1_18::new); public Protocol1_19_4To1_19_3() { super(ClientboundPackets1_19_4.class, ClientboundPackets1_19_3.class, ServerboundPackets1_19_4.class, ServerboundPackets1_19_3.class); } @Override protected void registerPackets() { super.registerPackets(); final CommandRewriter commandRewriter = new CommandRewriter<>(this) { @Override public void handleArgument(final PacketWrapper wrapper, final String argumentType) { switch (argumentType) { case "minecraft:heightmap" -> wrapper.write(Types.VAR_INT, 0); case "minecraft:time" -> wrapper.read(Types.INT); // Minimum case "minecraft:resource", "minecraft:resource_or_tag" -> { final String resource = wrapper.read(Types.STRING); // Replace damage types with... something wrapper.write(Types.STRING, resource.equals("minecraft:damage_type") ? "minecraft:mob_effect" : resource); } default -> super.handleArgument(wrapper, argumentType); } } }; replaceClientbound(ClientboundPackets1_19_4.COMMANDS, commandRewriter::handle1_19); tagRewriter.removeTags("minecraft:damage_type"); replaceClientbound(ClientboundPackets1_19_4.SERVER_DATA, wrapper -> { final JsonElement element = wrapper.read(Types.COMPONENT); translatableRewriter.processText(wrapper.user(), element); wrapper.write(Types.OPTIONAL_COMPONENT, element); final byte[] iconBytes = wrapper.read(Types.OPTIONAL_BYTE_ARRAY_PRIMITIVE); final String iconBase64 = iconBytes != null ? "data:image/png;base64," + new String(Base64.getEncoder().encode(iconBytes), StandardCharsets.UTF_8) : null; wrapper.write(Types.OPTIONAL_STRING, iconBase64); }); cancelClientbound(ClientboundPackets1_19_4.BUNDLE_DELIMITER); cancelClientbound(ClientboundPackets1_19_4.CHUNKS_BIOMES); // We definitely do not want to cache every single chunk just to resent them with new biomes } @Override public void init(final UserConnection user) { addEntityTracker(user, new EntityTracker1_19_4(user)); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public BlockItemPacketRewriter1_19_4 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public EntityPacketRewriter1_19_4 getEntityRewriter() { return entityRewriter; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/rewriter/BlockItemPacketRewriter1_19_4.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.rewriter; import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.Protocol1_19_4To1_19_3; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ServerboundPackets1_19_3; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.rewriter.RecipeRewriter1_19_3; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ClientboundPackets1_19_4; import com.viaversion.viaversion.util.Key; public final class BlockItemPacketRewriter1_19_4 extends BackwardsItemRewriter { public BlockItemPacketRewriter1_19_4(final Protocol1_19_4To1_19_3 protocol) { super(protocol, Types.ITEM1_13_2, Types.ITEM1_13_2_ARRAY); } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_19_4.OPEN_SCREEN, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Container id map(Types.VAR_INT); // Container type map(Types.COMPONENT); // Title handler(wrapper -> { final int windowType = wrapper.get(Types.VAR_INT, 1); if (windowType == 21) { // New smithing menu wrapper.cancel(); } else if (windowType > 21) { wrapper.set(Types.VAR_INT, 1, windowType - 1); } protocol.getComponentRewriter().processText(wrapper.user(), wrapper.get(Types.COMPONENT, 0)); }); } }); final RecipeRewriter1_19_3 recipeRewriter = new RecipeRewriter1_19_3<>(protocol) { @Override public void handleCraftingShaped(final PacketWrapper wrapper) { final int ingredients = wrapper.passthrough(Types.VAR_INT) * wrapper.passthrough(Types.VAR_INT); wrapper.passthrough(Types.STRING); // Group wrapper.passthrough(Types.VAR_INT); // Crafting book category for (int i = 0; i < ingredients; i++) { handleIngredient(wrapper); } rewrite(wrapper.user(), wrapper.passthrough(Types.ITEM1_13_2)); // Result // Remove notification boolean wrapper.read(Types.BOOLEAN); } }; protocol.registerClientbound(ClientboundPackets1_19_4.UPDATE_RECIPES, wrapper -> { final int size = wrapper.passthrough(Types.VAR_INT); int newSize = size; for (int i = 0; i < size; i++) { final String type = wrapper.read(Types.STRING); final String cutType = Key.stripMinecraftNamespace(type); if (cutType.equals("smithing_transform") || cutType.equals("smithing_trim")) { newSize--; wrapper.read(Types.STRING); // Recipe identifier wrapper.read(Types.ITEM1_13_2_ARRAY); // Template wrapper.read(Types.ITEM1_13_2_ARRAY); // Base wrapper.read(Types.ITEM1_13_2_ARRAY); // Additions if (cutType.equals("smithing_transform")) { wrapper.read(Types.ITEM1_13_2); // Result } continue; } else if (cutType.equals("crafting_decorated_pot")) { newSize--; wrapper.read(Types.STRING); // Recipe identifier wrapper.read(Types.VAR_INT); // Crafting book category continue; } wrapper.write(Types.STRING, type); wrapper.passthrough(Types.STRING); // Recipe Identifier recipeRewriter.handleRecipeType(wrapper, cutType); } wrapper.set(Types.VAR_INT, 0, newSize); }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/rewriter/EntityPacketRewriter1_19_4.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.entities.storage.EntityPositionHandler; import com.viaversion.viabackwards.api.entities.storage.EntityReplacement; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.Protocol1_19_4To1_19_3; import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.storage.EntityTracker1_19_4; import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.storage.LinkedEntityStorage; import com.viaversion.viaversion.api.data.entity.StoredEntityData; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_3; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_4; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_19_3; import com.viaversion.viaversion.api.type.types.version.Types1_19_4; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ClientboundPackets1_19_4; import com.viaversion.viaversion.util.TagUtil; public final class EntityPacketRewriter1_19_4 extends EntityRewriter { private static final double TEXT_DISPLAY_Y_OFFSET = -0.25; // Move emulated armor stands down to match text display height offsets public EntityPacketRewriter1_19_4(final Protocol1_19_4To1_19_3 protocol) { super(protocol, Types1_19_3.ENTITY_DATA_TYPES.optionalComponentType, Types1_19_3.ENTITY_DATA_TYPES.booleanType); } @Override public void registerPackets() { registerSetEntityData(ClientboundPackets1_19_4.SET_ENTITY_DATA, Types1_19_4.ENTITY_DATA_LIST, Types1_19_3.ENTITY_DATA_LIST); protocol.replaceClientbound(ClientboundPackets1_19_4.ADD_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Entity id map(Types.UUID); // Entity UUID map(Types.VAR_INT); // Entity type map(Types.DOUBLE); // X map(Types.DOUBLE); // Y map(Types.DOUBLE); // Z map(Types.BYTE); // Pitch map(Types.BYTE); // Yaw map(Types.BYTE); // Head yaw map(Types.VAR_INT); // Data handler(wrapper -> { final int entityId = wrapper.get(Types.VAR_INT, 0); final int entityType = wrapper.get(Types.VAR_INT, 1); if (!ViaBackwards.getConfig().mapDisplayEntities()) { if (entityType == EntityTypes1_19_4.BLOCK_DISPLAY.getId() || entityType == EntityTypes1_19_4.ITEM_DISPLAY.getId() || entityType == EntityTypes1_19_4.TEXT_DISPLAY.getId()) { wrapper.cancel(); return; } } final double y = wrapper.get(Types.DOUBLE, 1); if (entityType == EntityTypes1_19_4.TEXT_DISPLAY.getId()) { wrapper.set(Types.DOUBLE, 1, y + TEXT_DISPLAY_Y_OFFSET); } // First track (and remap) entity, then put storage for block display entity getSpawnTrackerWithDataHandler1_19().handle(wrapper); if (entityType != EntityTypes1_19_4.BLOCK_DISPLAY.getId()) { return; } final StoredEntityData data = tracker(wrapper.user()).entityData(entityId); if (data != null) { final LinkedEntityStorage storage = new LinkedEntityStorage(); final double x = wrapper.get(Types.DOUBLE, 0); final double z = wrapper.get(Types.DOUBLE, 2); storage.setPosition(x, y, z); data.put(storage); } }); } }); protocol.registerClientbound(ClientboundPackets1_19_4.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // Entity id map(Types.BOOLEAN); // Hardcore map(Types.BYTE); // Gamemode map(Types.BYTE); // Previous Gamemode map(Types.STRING_ARRAY); // World List map(Types.NAMED_COMPOUND_TAG); // Dimension registry map(Types.STRING); // Dimension key map(Types.STRING); // World handler(dimensionDataHandler()); handler(biomeSizeTracker()); handler(worldDataTrackerHandlerByKey()); handler(wrapper -> { final CompoundTag registry = wrapper.get(Types.NAMED_COMPOUND_TAG, 0); TagUtil.removeNamespaced(registry, "trim_pattern"); TagUtil.removeNamespaced(registry, "trim_material"); TagUtil.removeNamespaced(registry, "damage_type"); final ListTag biomes = TagUtil.getRegistryEntries(registry, "worldgen/biome"); for (final CompoundTag biomeTag : biomes) { final CompoundTag biomeData = biomeTag.getCompoundTag("element"); final NumberTag hasPrecipitation = biomeData.getNumberTag("has_precipitation"); biomeData.putString("precipitation", hasPrecipitation.asByte() == 1 ? "rain" : "none"); } }); } }); protocol.registerClientbound(ClientboundPackets1_19_4.PLAYER_POSITION, new PacketHandlers() { @Override protected void register() { map(Types.DOUBLE); // X map(Types.DOUBLE); // Y map(Types.DOUBLE); // Z map(Types.FLOAT); // Yaw map(Types.FLOAT); // Pitch map(Types.BYTE); // Relative arguments map(Types.VAR_INT); // Id create(Types.BOOLEAN, false); // Dismount vehicle } }); protocol.registerClientbound(ClientboundPackets1_19_4.DAMAGE_EVENT, ClientboundPackets1_19_3.ENTITY_EVENT, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT, Types.INT); // Entity id read(Types.VAR_INT); // Damage type read(Types.VAR_INT); // Cause entity read(Types.VAR_INT); // Direct cause entity handler(wrapper -> { // Source position if (wrapper.read(Types.BOOLEAN)) { wrapper.read(Types.DOUBLE); wrapper.read(Types.DOUBLE); wrapper.read(Types.DOUBLE); } }); create(Types.BYTE, (byte) 2); // Generic hurt } }); protocol.registerClientbound(ClientboundPackets1_19_4.HURT_ANIMATION, ClientboundPackets1_19_3.ANIMATE, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Entity id read(Types.FLOAT); // Yaw create(Types.UNSIGNED_BYTE, (short) 1); // Hit } }); protocol.registerClientbound(ClientboundPackets1_19_4.RESPAWN, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Dimension map(Types.STRING); // World handler(worldDataTrackerHandlerByKey()); } }); protocol.registerClientbound(ClientboundPackets1_19_4.UPDATE_MOB_EFFECT, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Entity id wrapper.passthrough(Types.VAR_INT); // Effect id wrapper.passthrough(Types.BYTE); // Amplifier // Handle infinite duration. Use a value the client still accepts without bugging out the display while still being practically infinite final int duration = wrapper.read(Types.VAR_INT); wrapper.write(Types.VAR_INT, duration == -1 ? 999999 : duration); }); // Track the position of block display entities to later spawn the linked entities, we will put them // as passengers but the spawn position needs to be in the players view distance protocol.registerClientbound(ClientboundPackets1_19_4.TELEPORT_ENTITY, wrapper -> { final int entityId = wrapper.passthrough(Types.VAR_INT); final double x = wrapper.passthrough(Types.DOUBLE); final double y = wrapper.passthrough(Types.DOUBLE); final double z = wrapper.passthrough(Types.DOUBLE); final EntityTracker1_19_4 tracker = tracker(wrapper.user()); if (tracker.entityType(entityId) == EntityTypes1_19_4.TEXT_DISPLAY) { wrapper.set(Types.DOUBLE, 1, y + TEXT_DISPLAY_Y_OFFSET); } final LinkedEntityStorage storage = tracker.linkedEntityStorage(entityId); if (storage != null) { storage.setPosition(x, y, z); } }); final PacketHandler entityPositionHandler = wrapper -> { final int entityId = wrapper.passthrough(Types.VAR_INT); final double x = wrapper.passthrough(Types.SHORT) / EntityPositionHandler.RELATIVE_MOVE_FACTOR; final double y = wrapper.passthrough(Types.SHORT) / EntityPositionHandler.RELATIVE_MOVE_FACTOR; final double z = wrapper.passthrough(Types.SHORT) / EntityPositionHandler.RELATIVE_MOVE_FACTOR; final EntityTracker1_19_4 tracker = tracker(wrapper.user()); final LinkedEntityStorage storage = tracker.linkedEntityStorage(entityId); if (storage != null) { storage.addRelativePosition(x, y, z); } }; protocol.registerClientbound(ClientboundPackets1_19_4.MOVE_ENTITY_POS, entityPositionHandler); protocol.registerClientbound(ClientboundPackets1_19_4.MOVE_ENTITY_POS_ROT, entityPositionHandler); } @Override public void registerRewrites() { filter().handler((event, data) -> { int id = data.dataType().typeId(); if (id >= 25) { // Sniffer state, Vector3f, Quaternion types event.cancel(); return; } else if (id >= 15) { // Optional block state - just map down to block state id--; } data.setDataType(Types1_19_3.ENTITY_DATA_TYPES.byId(id)); }); registerEntityDataTypeHandler(Types1_19_3.ENTITY_DATA_TYPES.itemType, null, Types1_19_3.ENTITY_DATA_TYPES.optionalBlockStateType, Types1_19_3.ENTITY_DATA_TYPES.particleType, Types1_19_3.ENTITY_DATA_TYPES.componentType, Types1_19_3.ENTITY_DATA_TYPES.optionalComponentType); registerBlockStateHandler(EntityTypes1_19_4.ABSTRACT_MINECART, 11); filter().type(EntityTypes1_19_4.BOAT).index(11).handler((event, data) -> { final int boatType = data.value(); if (boatType > 4) { // Cherry data.setValue(boatType - 1); } }); filter().type(EntityTypes1_19_4.TEXT_DISPLAY).index(22).handler(((event, data) -> { // Send as custom display name event.setIndex(2); data.setDataType(Types1_19_3.ENTITY_DATA_TYPES.optionalComponentType); event.createExtraData(new EntityData(3, Types1_19_3.ENTITY_DATA_TYPES.booleanType, true)); // Show custom name })); filter().type(EntityTypes1_19_4.BLOCK_DISPLAY).index(22).handler((event, data) -> { final int value = data.value(); final EntityTracker1_19_4 tracker = tracker(event.user()); tracker.clearLinkedEntities(event.entityId()); final LinkedEntityStorage storage = tracker.linkedEntityStorage(event.entityId()); if (storage == null) { return; } final int linkedEntity = tracker.spawnEntity(EntityTypes1_19_3.FALLING_BLOCK, storage.x(), storage.y(), storage.z(), value); storage.setEntities(linkedEntity); final PacketWrapper wrapper = PacketWrapper.create(ClientboundPackets1_19_3.SET_PASSENGERS, event.user()); wrapper.write(Types.VAR_INT, event.entityId()); // Entity id wrapper.write(Types.VAR_INT_ARRAY_PRIMITIVE, new int[]{linkedEntity}); // Passenger entity ids wrapper.send(Protocol1_19_4To1_19_3.class); }); filter().type(EntityTypes1_19_4.ITEM_DISPLAY).index(22).handler((event, data) -> { final Item value = data.value(); final PacketWrapper setEquipment = PacketWrapper.create(ClientboundPackets1_19_3.SET_EQUIPMENT, event.user()); setEquipment.write(Types.VAR_INT, event.entityId()); // Entity id setEquipment.write(Types.BYTE, (byte) 5); // Slot - head setEquipment.write(Types.ITEM1_13_2, value); setEquipment.send(Protocol1_19_4To1_19_3.class); }); filter().type(EntityTypes1_19_4.DISPLAY).handler((event, data) -> { // Remove a large heap of display entity data if (event.index() > 7) { event.cancel(); } }); filter().type(EntityTypes1_19_4.INTERACTION).cancel(8); // Width filter().type(EntityTypes1_19_4.INTERACTION).cancel(9); // Height filter().type(EntityTypes1_19_4.INTERACTION).cancel(10); // Response filter().type(EntityTypes1_19_4.SNIFFER).cancel(17); // State filter().type(EntityTypes1_19_4.SNIFFER).cancel(18); // Drop seed at tick filter().type(EntityTypes1_19_4.ABSTRACT_HORSE).addIndex(18); // Owner UUID } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); final EntityReplacement.EntityDataCreator displayDataCreator = storage -> { storage.add(new EntityData(0, Types1_19_3.ENTITY_DATA_TYPES.byteType, (byte) 0x20)); // Invisible storage.add(new EntityData(5, Types1_19_3.ENTITY_DATA_TYPES.booleanType, true)); // No gravity storage.add(new EntityData(15, Types1_19_3.ENTITY_DATA_TYPES.byteType, (byte) (0x01 | 0x10))); // Small marker }; mapEntityTypeWithData(EntityTypes1_19_4.TEXT_DISPLAY, EntityTypes1_19_4.ARMOR_STAND).spawnEntityData(displayDataCreator); mapEntityTypeWithData(EntityTypes1_19_4.ITEM_DISPLAY, EntityTypes1_19_4.ARMOR_STAND).spawnEntityData(displayDataCreator); mapEntityTypeWithData(EntityTypes1_19_4.BLOCK_DISPLAY, EntityTypes1_19_4.ARMOR_STAND).spawnEntityData(displayDataCreator); mapEntityTypeWithData(EntityTypes1_19_4.INTERACTION, EntityTypes1_19_4.ARMOR_STAND).spawnEntityData(displayDataCreator); // Not much we can do about this one mapEntityTypeWithData(EntityTypes1_19_4.SNIFFER, EntityTypes1_19_4.RAVAGER).jsonName(); } @Override public EntityType typeFromId(final int type) { return EntityTypes1_19_4.getTypeFromId(type); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/storage/EntityTracker1_19_4.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.storage; import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.Protocol1_19_4To1_19_3; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.entity.TrackedEntity; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_3; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_4; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.libs.fastutil.ints.IntOpenHashSet; import com.viaversion.viaversion.libs.fastutil.ints.IntSet; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; public final class EntityTracker1_19_4 extends EntityTrackerBase { private final IntSet generatedEntities = new IntOpenHashSet(); // Track entities spawned to prevent duplicated entity ids public EntityTracker1_19_4(final UserConnection connection) { super(connection, EntityTypes1_19_4.PLAYER); } public int spawnEntity(final EntityTypes1_19_3 entityType, final double x, final double y, final double z, final int data) { final int entityId = nextEntityId(); final PacketWrapper addEntity = PacketWrapper.create(ClientboundPackets1_19_3.ADD_ENTITY, user()); addEntity.write(Types.VAR_INT, entityId); // Entity id addEntity.write(Types.UUID, UUID.randomUUID()); // Entity UUID addEntity.write(Types.VAR_INT, entityType.getId()); // Entity type addEntity.write(Types.DOUBLE, x); // X addEntity.write(Types.DOUBLE, y); // Y addEntity.write(Types.DOUBLE, z); // Z addEntity.write(Types.BYTE, (byte) 0); // Pitch addEntity.write(Types.BYTE, (byte) 0); // Yaw addEntity.write(Types.BYTE, (byte) 0); // Head yaw addEntity.write(Types.VAR_INT, data); // Data addEntity.write(Types.SHORT, (short) 0); // Velocity X addEntity.write(Types.SHORT, (short) 0); // Velocity Y addEntity.write(Types.SHORT, (short) 0); // Velocity Z addEntity.send(Protocol1_19_4To1_19_3.class); generatedEntities.add(entityId); return entityId; } @Override public void clearEntities() { for (final int id : entities.keySet()) { clearLinkedEntities(id); } super.clearEntities(); } @Override public void removeEntity(final int id) { clearLinkedEntities(id); super.removeEntity(id); } public void clearLinkedEntities(final int id) { final LinkedEntityStorage storage = linkedEntityStorage(id); if (storage != null && storage.entities() != null) { storage.remove(user()); generatedEntities.remove(id); } } public LinkedEntityStorage linkedEntityStorage(final int id) { final TrackedEntity entity = entity(id); if (entity != null && entity.hasData()) { return entity.data().get(LinkedEntityStorage.class); } return null; } private int nextEntityId() { final int entityId = -ThreadLocalRandom.current().nextInt(10_000); if (generatedEntities.contains(entityId)) { return nextEntityId(); } return entityId; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/storage/LinkedEntityStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.storage; import com.viaversion.viabackwards.api.entities.storage.EntityPositionStorage; import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.Protocol1_19_4To1_19_3; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3; public class LinkedEntityStorage extends EntityPositionStorage implements StorableObject { private int[] entities; public int[] entities() { return entities; } public void setEntities(final int... entities) { this.entities = entities; } public void remove(final UserConnection connection) { final PacketWrapper wrapper = PacketWrapper.create(ClientboundPackets1_19_3.REMOVE_ENTITIES, connection); wrapper.write(Types.VAR_INT_ARRAY_PRIMITIVE, entities); wrapper.send(Protocol1_19_4To1_19_3.class); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/Protocol1_19To1_18_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19to1_18_2; import com.google.common.primitives.Longs; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.SoundRewriter; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_19_1to1_19.Protocol1_19_1To1_19; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.data.BackwardsMappingData1_19; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter.BlockItemPacketRewriter1_19; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter.CommandRewriter1_19; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter.EntityPacketRewriter1_19; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.DimensionRegistryStorage; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.EntityTracker1_19; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.NonceStorage; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.signature.SignableCommandArgumentsProvider; import com.viaversion.viaversion.api.minecraft.signature.model.DecoratableMessage; import com.viaversion.viaversion.api.minecraft.signature.model.MessageMetadata; import com.viaversion.viaversion.api.minecraft.signature.storage.ChatSession1_19_0; import com.viaversion.viaversion.api.protocol.packet.State; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_18; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets; import com.viaversion.viaversion.protocols.base.ServerboundLoginPackets; import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ServerboundPackets1_17; import com.viaversion.viaversion.protocols.v1_17_1to1_18.packet.ClientboundPackets1_18; import com.viaversion.viaversion.protocols.v1_18_2to1_19.packet.ClientboundPackets1_19; import com.viaversion.viaversion.protocols.v1_18_2to1_19.packet.ServerboundPackets1_19; import com.viaversion.viaversion.protocols.v1_19to1_19_1.Protocol1_19To1_19_1; import com.viaversion.viaversion.protocols.v1_19to1_19_1.data.ChatDecorationResult; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.CommandRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; import com.viaversion.viaversion.util.Pair; import java.security.SignatureException; import java.time.Instant; import java.util.List; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; public final class Protocol1_19To1_18_2 extends BackwardsProtocol { public static final BackwardsMappingData1_19 MAPPINGS = new BackwardsMappingData1_19(); private static final UUID ZERO_UUID = new UUID(0, 0); private static final byte[] EMPTY_BYTES = new byte[0]; private final EntityPacketRewriter1_19 entityRewriter = new EntityPacketRewriter1_19(this); private final BlockItemPacketRewriter1_19 blockItemPackets = new BlockItemPacketRewriter1_19(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final JsonNBTComponentRewriter translatableRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BlockRewriter blockRewriter = BlockRewriter.for1_18(this, ChunkType1_18::new); public Protocol1_19To1_18_2() { super(ClientboundPackets1_19.class, ClientboundPackets1_18.class, ServerboundPackets1_19.class, ServerboundPackets1_17.class); } @Override protected void registerPackets() { super.registerPackets(); final SoundRewriter soundRewriter = new SoundRewriter<>(this); replaceClientbound(ClientboundPackets1_19.SOUND, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Sound id map(Types.VAR_INT); // Source map(Types.INT); // X map(Types.INT); // Y map(Types.INT); // Z map(Types.FLOAT); // Volume map(Types.FLOAT); // Pitch read(Types.LONG); // Seed handler(soundRewriter.getSoundHandler()); } }); replaceClientbound(ClientboundPackets1_19.SOUND_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Sound id map(Types.VAR_INT); // Source map(Types.VAR_INT); // Entity id map(Types.FLOAT); // Volume map(Types.FLOAT); // Pitch read(Types.LONG); // Seed handler(soundRewriter.getSoundHandler()); } }); replaceClientbound(ClientboundPackets1_19.CUSTOM_SOUND, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Sound name map(Types.VAR_INT); // Source map(Types.INT); // X map(Types.INT); // Y map(Types.INT); // Z map(Types.FLOAT); // Volume map(Types.FLOAT); // Pitch read(Types.LONG); // Seed handler(soundRewriter.getNamedSoundHandler()); } }); tagRewriter.removeTags("minecraft:banner_pattern"); tagRewriter.removeTags("minecraft:instrument"); tagRewriter.removeTags("minecraft:cat_variant"); tagRewriter.removeTags("minecraft:painting_variant"); tagRewriter.addEmptyTag(RegistryType.BLOCK, "minecraft:polar_bears_spawnable_on_in_frozen_ocean"); tagRewriter.renameTag(RegistryType.BLOCK, "minecraft:wool_carpets", "minecraft:carpets"); tagRewriter.renameTag(RegistryType.ITEM, "minecraft:wool_carpets", "minecraft:carpets"); tagRewriter.addEmptyTag(RegistryType.ITEM, "minecraft:occludes_vibration_signals"); final CommandRewriter commandRewriter = new CommandRewriter1_19(this); replaceClientbound(ClientboundPackets1_19.COMMANDS, wrapper -> { final int size = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < size; i++) { final byte flags = wrapper.passthrough(Types.BYTE); wrapper.passthrough(Types.VAR_INT_ARRAY_PRIMITIVE); // Children indices if ((flags & 0x08) != 0) { wrapper.passthrough(Types.VAR_INT); // Redirect node index } final int nodeType = flags & 0x03; if (nodeType == 1 || nodeType == 2) { // Literal/argument node wrapper.passthrough(Types.STRING); // Name } if (nodeType == 2) { // Argument node final int argumentTypeId = wrapper.read(Types.VAR_INT); String argumentType = MAPPINGS.getArgumentTypeMappings().identifier(argumentTypeId); if (argumentType == null) { getLogger().warning("Unknown command argument type id: " + argumentTypeId); argumentType = "minecraft:no"; } wrapper.write(Types.STRING, commandRewriter.handleArgumentType(argumentType)); commandRewriter.handleArgument(wrapper, argumentType); if ((flags & 0x10) != 0) { wrapper.passthrough(Types.STRING); // Suggestion type } } } wrapper.passthrough(Types.VAR_INT); // Root node index }); cancelClientbound(ClientboundPackets1_19.SERVER_DATA); cancelClientbound(ClientboundPackets1_19.CHAT_PREVIEW); cancelClientbound(ClientboundPackets1_19.SET_DISPLAY_CHAT_PREVIEW); registerClientbound(ClientboundPackets1_19.PLAYER_CHAT, ClientboundPackets1_18.CHAT, new PacketHandlers() { @Override public void register() { handler(wrapper -> { final JsonElement signedContent = wrapper.read(Types.COMPONENT); final JsonElement unsignedContent = wrapper.read(Types.OPTIONAL_COMPONENT); final int chatTypeId = wrapper.read(Types.VAR_INT); final UUID sender = wrapper.read(Types.UUID); final JsonElement senderName = wrapper.read(Types.COMPONENT); final JsonElement teamName = wrapper.read(Types.OPTIONAL_COMPONENT); final CompoundTag chatType = wrapper.user().get(DimensionRegistryStorage.class).chatType(chatTypeId); final ChatDecorationResult decorationResult = Protocol1_19To1_19_1.decorateChatMessage(chatType, chatTypeId, senderName, teamName, unsignedContent != null ? unsignedContent : signedContent); if (decorationResult == null) { wrapper.cancel(); return; } translatableRewriter.processText(wrapper.user(), decorationResult.content()); wrapper.write(Types.COMPONENT, decorationResult.content()); wrapper.write(Types.BYTE, decorationResult.overlay() ? (byte) 2 : 1); wrapper.write(Types.UUID, sender); }); read(Types.LONG); // Timestamp read(Types.LONG); // Salt read(Types.BYTE_ARRAY_PRIMITIVE); // Signature } }); registerClientbound(ClientboundPackets1_19.SYSTEM_CHAT, ClientboundPackets1_18.CHAT, new PacketHandlers() { @Override public void register() { handler(wrapper -> { final JsonElement content = wrapper.passthrough(Types.COMPONENT); translatableRewriter.processText(wrapper.user(), content); // Screw everything that isn't a system or game info type (which would only happen on funny 1.19.0 servers) final int typeId = wrapper.read(Types.VAR_INT); wrapper.write(Types.BYTE, typeId == Protocol1_19_1To1_19.GAME_INFO_ID ? (byte) 2 : (byte) 0); }); create(Types.UUID, ZERO_UUID); // Sender } }, true); registerServerbound(ServerboundPackets1_17.CHAT, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Message handler(wrapper -> { final ChatSession1_19_0 chatSession = wrapper.user().get(ChatSession1_19_0.class); final UUID sender = wrapper.user().getProtocolInfo().getUuid(); final Instant timestamp = Instant.now(); final long salt = ThreadLocalRandom.current().nextLong(); wrapper.write(Types.LONG, timestamp.toEpochMilli()); // Timestamp wrapper.write(Types.LONG, chatSession != null ? salt : 0L); // Salt final String message = wrapper.get(Types.STRING, 0); if (!message.isEmpty() && message.charAt(0) == '/') { final String command = message.substring(1); wrapper.setPacketType(ServerboundPackets1_19.CHAT_COMMAND); wrapper.set(Types.STRING, 0, command); final SignableCommandArgumentsProvider argumentsProvider = Via.getManager().getProviders().get(SignableCommandArgumentsProvider.class); if (chatSession != null && argumentsProvider != null) { final MessageMetadata metadata = new MessageMetadata(sender, timestamp, salt); final List> arguments = argumentsProvider.getSignableArguments(command); wrapper.write(Types.VAR_INT, arguments.size()); for (final Pair argument : arguments) { final byte[] signature; try { signature = chatSession.signChatMessage(metadata, new DecoratableMessage(argument.value())); } catch (final SignatureException e) { throw new RuntimeException(e); } wrapper.write(Types.STRING, argument.key()); wrapper.write(Types.BYTE_ARRAY_PRIMITIVE, signature); } } else { wrapper.write(Types.VAR_INT, 0); // No signatures } } else { if (chatSession != null) { final MessageMetadata metadata = new MessageMetadata(sender, timestamp, salt); final DecoratableMessage decoratableMessage = new DecoratableMessage(message); final byte[] signature; try { signature = chatSession.signChatMessage(metadata, decoratableMessage); } catch (final SignatureException e) { throw new RuntimeException(e); } wrapper.write(Types.BYTE_ARRAY_PRIMITIVE, signature); // Signature } else { wrapper.write(Types.BYTE_ARRAY_PRIMITIVE, EMPTY_BYTES); // Signature } } wrapper.write(Types.BOOLEAN, false); // No signed preview }); } }); // Login changes registerClientbound(State.LOGIN, ClientboundLoginPackets.LOGIN_FINISHED, new PacketHandlers() { @Override public void register() { map(Types.UUID); // UUID map(Types.STRING); // Name read(Types.PROFILE_PROPERTY_ARRAY); } }); registerClientbound(State.LOGIN, ClientboundLoginPackets.HELLO, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Server id map(Types.BYTE_ARRAY_PRIMITIVE); // Public key handler(wrapper -> { if (wrapper.user().has(ChatSession1_19_0.class)) { wrapper.user().put(new NonceStorage(wrapper.passthrough(Types.BYTE_ARRAY_PRIMITIVE))); // Nonce } }); } }); registerServerbound(State.LOGIN, ServerboundLoginPackets.HELLO, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Name handler(wrapper -> { final ChatSession1_19_0 chatSession = wrapper.user().get(ChatSession1_19_0.class); wrapper.write(Types.OPTIONAL_PROFILE_KEY, chatSession == null ? null : chatSession.getProfileKey()); // Profile Key }); } }); registerServerbound(State.LOGIN, ServerboundLoginPackets.ENCRYPTION_KEY, new PacketHandlers() { @Override public void register() { map(Types.BYTE_ARRAY_PRIMITIVE); // Public key handler(wrapper -> { final ChatSession1_19_0 chatSession = wrapper.user().get(ChatSession1_19_0.class); final byte[] verifyToken = wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); // Verify token wrapper.write(Types.BOOLEAN, chatSession == null); // Is nonce if (chatSession != null) { final long salt = ThreadLocalRandom.current().nextLong(); final byte[] signature; try { signature = chatSession.sign(signer -> { signer.accept(wrapper.user().remove(NonceStorage.class).nonce()); signer.accept(Longs.toByteArray(salt)); }); } catch (final SignatureException e) { throw new RuntimeException(e); } wrapper.write(Types.LONG, salt); // Salt wrapper.write(Types.BYTE_ARRAY_PRIMITIVE, signature); // Signature } else { wrapper.write(Types.BYTE_ARRAY_PRIMITIVE, verifyToken); // Nonce } }); } }); } @Override public void init(final UserConnection user) { user.put(new DimensionRegistryStorage()); addEntityTracker(user, new EntityTracker1_19(user)); } @Override public BackwardsMappingData1_19 getMappingData() { return MAPPINGS; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public EntityPacketRewriter1_19 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_19 getItemRewriter() { return blockItemPackets; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/data/BackwardsMappingData1_19.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19to1_18_2.data; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.data.BackwardsMappingDataLoader; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectOpenHashMap; import com.viaversion.viaversion.protocols.v1_18_2to1_19.Protocol1_18_2To1_19; import org.checkerframework.checker.nullness.qual.Nullable; public final class BackwardsMappingData1_19 extends BackwardsMappingData { private final Int2ObjectMap defaultChatTypes = new Int2ObjectOpenHashMap<>(); public BackwardsMappingData1_19() { super("1.19", "1.18", Protocol1_18_2To1_19.class); } @Override protected void loadExtras(final CompoundTag data) { if (ViaBackwards.getConfig().sculkShriekerToCryingObsidian()) { for (int i = 18900; i <= 18907; i++) { blockStateMappings.setNewId(i, 16082); } itemMappings.setNewId(329, 1065); } super.loadExtras(data); final ListTag chatTypes = BackwardsMappingDataLoader.INSTANCE.loadNBT("chat-types-1.19.1.nbt").getListTag("values", CompoundTag.class); for (final CompoundTag chatType : chatTypes) { final NumberTag idTag = chatType.getNumberTag("id"); defaultChatTypes.put(idTag.asInt(), chatType); } } public @Nullable CompoundTag chatType(final int id) { return defaultChatTypes.get(id); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/rewriter/BlockItemPacketRewriter1_19.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.api.rewriters.EnchantmentRewriter; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.Protocol1_19To1_18_2; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.LastDeathPosition; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.ParticleMappings; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.minecraft.GlobalBlockPosition; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; import com.viaversion.viaversion.api.minecraft.chunks.DataPalette; import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_18; import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ServerboundPackets1_17; import com.viaversion.viaversion.protocols.v1_18_2to1_19.packet.ClientboundPackets1_19; import com.viaversion.viaversion.rewriter.RecipeRewriter; import com.viaversion.viaversion.util.MathUtil; public final class BlockItemPacketRewriter1_19 extends BackwardsItemRewriter { private final EnchantmentRewriter enchantmentRewriter = new EnchantmentRewriter(this); public BlockItemPacketRewriter1_19(final Protocol1_19To1_18_2 protocol) { super(protocol, Types.ITEM1_13_2, Types.ITEM1_13_2_ARRAY); } @Override protected void registerPackets() { new RecipeRewriter<>(protocol).register(ClientboundPackets1_19.UPDATE_RECIPES); protocol.replaceClientbound(ClientboundPackets1_19.MERCHANT_OFFERS, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Container id handler(wrapper -> { final int size = wrapper.read(Types.VAR_INT); wrapper.write(Types.UNSIGNED_BYTE, (short) size); for (int i = 0; i < size; i++) { passthroughClientboundItem(wrapper); // First item passthroughClientboundItem(wrapper); // Result Item secondItem = wrapper.read(Types.ITEM1_13_2); if (secondItem != null) { secondItem = handleItemToClient(wrapper.user(), secondItem); wrapper.write(Types.BOOLEAN, true); wrapper.write(Types.ITEM1_13_2, secondItem); } else { wrapper.write(Types.BOOLEAN, false); } wrapper.passthrough(Types.BOOLEAN); // Out of stock wrapper.passthrough(Types.INT); // Uses wrapper.passthrough(Types.INT); // Max uses wrapper.passthrough(Types.INT); // Xp wrapper.passthrough(Types.INT); // Special price diff wrapper.passthrough(Types.FLOAT); // Price multiplier wrapper.passthrough(Types.INT); //Demand } }); } }); protocol.registerClientbound(ClientboundPackets1_19.BLOCK_CHANGED_ACK, null, new PacketHandlers() { @Override public void register() { read(Types.VAR_INT); // Sequence handler(PacketWrapper::cancel); // This is fine:tm: } }); protocol.replaceClientbound(ClientboundPackets1_19.LEVEL_PARTICLES, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT, Types.INT); // Particle id map(Types.BOOLEAN); // Override limiter map(Types.DOUBLE); // X map(Types.DOUBLE); // Y map(Types.DOUBLE); // Z map(Types.FLOAT); // Offset X map(Types.FLOAT); // Offset Y map(Types.FLOAT); // Offset Z map(Types.FLOAT); // Max speed map(Types.INT); // Particle Count handler(wrapper -> { final int id = wrapper.get(Types.INT, 0); final ParticleMappings particleMappings = protocol.getMappingData().getParticleMappings(); if (id == particleMappings.id("sculk_charge")) { //TODO wrapper.set(Types.INT, 0, -1); wrapper.cancel(); } else if (id == particleMappings.id("shriek")) { //TODO wrapper.set(Types.INT, 0, -1); wrapper.cancel(); } else if (id == particleMappings.id("vibration")) { // Can't do without the position wrapper.set(Types.INT, 0, -1); wrapper.cancel(); } }); handler(protocol.getParticleRewriter().levelParticlesHandler1_13(Types.INT)); } }); // The server does nothing but track the sequence, so we can just set it as 0 protocol.registerServerbound(ServerboundPackets1_17.PLAYER_ACTION, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Action map(Types.BLOCK_POSITION1_14); // Block position map(Types.UNSIGNED_BYTE); // Direction create(Types.VAR_INT, 0); // Sequence } }); protocol.registerServerbound(ServerboundPackets1_17.USE_ITEM_ON, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Hand map(Types.BLOCK_POSITION1_14); // Block position map(Types.VAR_INT); // Direction map(Types.FLOAT); // X map(Types.FLOAT); // Y map(Types.FLOAT); // Z map(Types.BOOLEAN); // Inside create(Types.VAR_INT, 0); // Sequence } }); protocol.registerServerbound(ServerboundPackets1_17.USE_ITEM, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Hand create(Types.VAR_INT, 0); // Sequence } }); protocol.registerServerbound(ServerboundPackets1_17.SET_BEACON, wrapper -> { final int primaryEffect = wrapper.read(Types.VAR_INT); if (primaryEffect > 0) { wrapper.write(Types.BOOLEAN, true); wrapper.write(Types.VAR_INT, primaryEffect); } else { wrapper.write(Types.BOOLEAN, false); } final int secondaryEffect = wrapper.read(Types.VAR_INT); if (secondaryEffect > 0) { wrapper.write(Types.BOOLEAN, true); wrapper.write(Types.VAR_INT, secondaryEffect); } else { wrapper.write(Types.BOOLEAN, false); } }); } @Override protected void registerRewrites() { enchantmentRewriter.registerEnchantment("minecraft:swift_sneak", "§7Swift Sneak"); } @Override public Item handleItemToClient(final UserConnection connection, Item item) { if (item == null) return null; final int identifier = item.identifier(); item = super.handleItemToClient(connection, item); if (identifier != 834) { return item; } final LastDeathPosition lastDeathPosition = connection.get(LastDeathPosition.class); if (lastDeathPosition == null) { return item; } final GlobalBlockPosition position = lastDeathPosition.position(); final CompoundTag lodestonePosTag = new CompoundTag(); item.tag().putBoolean(nbtTagName(), true); item.tag().put("LodestonePos", lodestonePosTag); item.tag().putString("LodestoneDimension", position.dimension()); lodestonePosTag.putInt("X", position.x()); lodestonePosTag.putInt("Y", position.y()); lodestonePosTag.putInt("Z", position.z()); enchantmentRewriter.handleToClient(item); return item; } @Override public Item handleItemToServer(final UserConnection connection, Item item) { if (item == null) return null; item = super.handleItemToServer(connection, item); CompoundTag tag = item.tag(); if (item.identifier() == 834 && tag != null) { if (tag.contains(nbtTagName())) { tag.remove(nbtTagName()); tag.remove("LodestonePos"); tag.remove("LodestoneDimension"); } if (tag.isEmpty()) { item.setTag(null); } } enchantmentRewriter.handleToServer(item); return item; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/rewriter/CommandRewriter1_19.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.Protocol1_19To1_18_2; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_18_2to1_19.packet.ClientboundPackets1_19; import com.viaversion.viaversion.rewriter.CommandRewriter; public final class CommandRewriter1_19 extends CommandRewriter { public CommandRewriter1_19(Protocol1_19To1_18_2 protocol) { super(protocol); this.parserHandlers.put("minecraft:template_mirror", wrapper -> wrapper.write(Types.VAR_INT, 0)); this.parserHandlers.put("minecraft:template_rotation", wrapper -> wrapper.write(Types.VAR_INT, 0)); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/rewriter/EntityPacketRewriter1_19.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.Protocol1_19To1_18_2; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.DimensionRegistryStorage; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.EntityTracker1_19; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.LastDeathPosition; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.StoredPainting; import com.viaversion.viaversion.api.data.ParticleMappings; import com.viaversion.viaversion.api.data.entity.StoredEntityData; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.GlobalBlockPosition; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19; import com.viaversion.viaversion.api.minecraft.entitydata.EntityDataType; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_18; import com.viaversion.viaversion.api.type.types.version.Types1_19; import com.viaversion.viaversion.libs.fastutil.ints.IntOpenHashSet; import com.viaversion.viaversion.libs.fastutil.ints.IntSet; import com.viaversion.viaversion.protocols.v1_17_1to1_18.packet.ClientboundPackets1_18; import com.viaversion.viaversion.protocols.v1_18_2to1_19.packet.ClientboundPackets1_19; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.TagUtil; public final class EntityPacketRewriter1_19 extends EntityRewriter { private static final IntSet WIDE_PAINTINGS = IntOpenHashSet.of( 7, 8, 9, 10, 11, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 ); public EntityPacketRewriter1_19(final Protocol1_19To1_18_2 protocol) { super(protocol); } @Override protected void registerPackets() { registerTracker(ClientboundPackets1_19.ADD_EXPERIENCE_ORB, EntityTypes1_19.EXPERIENCE_ORB); registerTracker(ClientboundPackets1_19.ADD_PLAYER, EntityTypes1_19.PLAYER); registerSetEntityData(ClientboundPackets1_19.SET_ENTITY_DATA, Types1_19.ENTITY_DATA_LIST, Types1_18.ENTITY_DATA_LIST); protocol.replaceClientbound(ClientboundPackets1_19.ADD_ENTITY, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Entity id map(Types.UUID); // Entity UUID map(Types.VAR_INT); // Entity Type map(Types.DOUBLE); // X map(Types.DOUBLE); // Y map(Types.DOUBLE); // Z map(Types.BYTE); // Pitch map(Types.BYTE); // Yaw handler(wrapper -> { final byte headYaw = wrapper.read(Types.BYTE); int data = wrapper.read(Types.VAR_INT); final EntityType entityType = trackAndMapEntity(wrapper); if (entityType.isOrHasParent(EntityTypes1_19.LIVING_ENTITY)) { wrapper.write(Types.BYTE, headYaw); // Switch pitch and yaw position final byte pitch = wrapper.get(Types.BYTE, 0); final byte yaw = wrapper.get(Types.BYTE, 1); wrapper.set(Types.BYTE, 0, yaw); wrapper.set(Types.BYTE, 1, pitch); wrapper.setPacketType(ClientboundPackets1_18.ADD_MOB); return; } else if (entityType == EntityTypes1_19.PAINTING) { wrapper.cancel(); // The entity has been tracked, now we wait for the entity data packet final int entityId = wrapper.get(Types.VAR_INT, 0); final StoredEntityData entityData = tracker(wrapper.user()).entityData(entityId); final int x = wrapper.get(Types.DOUBLE, 0).intValue(); final int y = wrapper.get(Types.DOUBLE, 1).intValue(); final int z = wrapper.get(Types.DOUBLE, 2).intValue(); final BlockPosition position = new BlockPosition(x, y, z); entityData.put(new StoredPainting(entityId, wrapper.get(Types.UUID, 0), position, data)); return; } if (entityType == EntityTypes1_19.FALLING_BLOCK) { data = protocol.getMappingData().getNewBlockStateId(data); } wrapper.write(Types.INT, data); }); } }); protocol.registerClientbound(ClientboundPackets1_19.TELEPORT_ENTITY, wrapper -> { final int entityId = wrapper.passthrough(Types.VAR_INT); if (tracker(wrapper.user()).entityType(entityId) != EntityTypes1_19.PAINTING) { return; } final double x = wrapper.read(Types.DOUBLE); final double y = wrapper.read(Types.DOUBLE); final double z = wrapper.read(Types.DOUBLE); final StoredEntityData entityData = tracker(wrapper.user()).entityDataIfPresent(entityId); final StoredPainting storedPainting = entityData != null ? entityData.get(StoredPainting.class) : null; // Presumably there is a more correct way of fixing this? Only north and east looking paintings with a width of >1 seem to be extra special if (storedPainting != null && (storedPainting.direction() == 2 || storedPainting.direction() == 3) && WIDE_PAINTINGS.contains(storedPainting.type())) { wrapper.write(Types.DOUBLE, storedPainting.direction() == 2 ? x : x + 1); wrapper.write(Types.DOUBLE, y + 1); wrapper.write(Types.DOUBLE, storedPainting.direction() == 3 ? z : z + 1); } else { wrapper.write(Types.DOUBLE, x + 1); wrapper.write(Types.DOUBLE, y + 1); wrapper.write(Types.DOUBLE, z + 1); } }); protocol.registerClientbound(ClientboundPackets1_19.UPDATE_MOB_EFFECT, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Entity id map(Types.VAR_INT); // Effect id map(Types.BYTE); // Amplifier map(Types.VAR_INT); // Duration map(Types.BYTE); // Flags handler(wrapper -> { // Remove factor data wrapper.read(Types.OPTIONAL_NAMED_COMPOUND_TAG); if (!ViaBackwards.getConfig().mapDarknessEffect()) { return; } final EntityTracker1_19 tracker = tracker(wrapper.user()); final int entityId = wrapper.get(Types.VAR_INT, 0); final int effectId = wrapper.get(Types.VAR_INT, 1); if (effectId == 33) { // Newly added darkness, rewrite to blindness tracker.getAffectedByDarkness().add(entityId); wrapper.set(Types.VAR_INT, 1, 15); } else if (effectId == 15) { // Track actual blindness effect for removal later tracker.getAffectedByBlindness().add(entityId); } }); } }); protocol.registerClientbound(ClientboundPackets1_19.REMOVE_MOB_EFFECT, new PacketHandlers() { @Override protected void register() { map(Types.VAR_INT); // Entity id map(Types.VAR_INT); // Effect id handler(wrapper -> { if (!ViaBackwards.getConfig().mapDarknessEffect()) { return; } final int entityId = wrapper.get(Types.VAR_INT, 0); final int effectId = wrapper.get(Types.VAR_INT, 1); final EntityTracker1_19 tracker = tracker(wrapper.user()); if (effectId == 33) { // Remove darkness and the fake blindness effect if the client doesn't have actual blindness tracker.getAffectedByDarkness().rem(entityId); if (!tracker.getAffectedByBlindness().contains(entityId)) { wrapper.set(Types.VAR_INT, 1, 15); } } else if (effectId == 15) { // Remove blindness and cancel if the client has darkness (will be removed by darkness removal) tracker.getAffectedByBlindness().rem(entityId); if (tracker.getAffectedByDarkness().contains(entityId)) { wrapper.cancel(); } } }); } }); protocol.registerClientbound(ClientboundPackets1_19.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // Entity ID map(Types.BOOLEAN); // Hardcore map(Types.BYTE); // Gamemode map(Types.BYTE); // Previous Gamemode map(Types.STRING_ARRAY); // Worlds map(Types.NAMED_COMPOUND_TAG); // Dimension registry handler(wrapper -> { final DimensionRegistryStorage dimensionRegistryStorage = wrapper.user().get(DimensionRegistryStorage.class); dimensionRegistryStorage.clear(); // Cache dimensions and find current dimension final String dimensionKey = Key.stripMinecraftNamespace(wrapper.read(Types.STRING)); final CompoundTag registry = wrapper.get(Types.NAMED_COMPOUND_TAG, 0); final ListTag dimensions = TagUtil.getRegistryEntries(registry, "dimension_type"); boolean found = false; for (final CompoundTag dimension : dimensions) { final String name = Key.stripMinecraftNamespace(dimension.getString("name")); final CompoundTag dimensionData = dimension.getCompoundTag("element"); dimensionRegistryStorage.addDimension(name, dimensionData.copy()); if (!found && name.equals(dimensionKey)) { wrapper.write(Types.NAMED_COMPOUND_TAG, dimensionData); found = true; } } if (!found) { throw new IllegalArgumentException("Could not find dimension " + dimensionKey + " in dimension registry"); } // Add biome category and track biomes final ListTag biomes = TagUtil.getRegistryEntries(registry, "worldgen/biome"); for (final CompoundTag biome : biomes) { final CompoundTag biomeCompound = biome.getCompoundTag("element"); biomeCompound.putString("category", "none"); } tracker(wrapper.user()).setBiomesSent(biomes.size()); // Cache and remove chat types final ListTag chatTypes = TagUtil.removeRegistryEntries(registry, "chat_type"); for (final CompoundTag chatType : chatTypes) { final NumberTag idTag = chatType.getNumberTag("id"); dimensionRegistryStorage.addChatType(idTag.asInt(), chatType); } }); map(Types.STRING); // World map(Types.LONG); // Seed map(Types.VAR_INT); // Max players map(Types.VAR_INT); // Chunk radius map(Types.VAR_INT); // Simulation distance map(Types.BOOLEAN); // Reduced debug info map(Types.BOOLEAN); // Show death screen map(Types.BOOLEAN); // Debug map(Types.BOOLEAN); // Flat handler(wrapper -> { final GlobalBlockPosition lastDeathPosition = wrapper.read(Types.OPTIONAL_GLOBAL_POSITION); if (lastDeathPosition != null) { wrapper.user().put(new LastDeathPosition(lastDeathPosition)); } else { wrapper.user().remove(LastDeathPosition.class); } }); handler(worldDataTrackerHandler(1)); handler(playerTrackerHandler()); } }); protocol.registerClientbound(ClientboundPackets1_19.RESPAWN, new PacketHandlers() { @Override public void register() { handler(wrapper -> { final String dimensionKey = wrapper.read(Types.STRING); final CompoundTag dimension = wrapper.user().get(DimensionRegistryStorage.class).dimension(dimensionKey); if (dimension == null) { throw new IllegalArgumentException("Could not find dimension " + dimensionKey + " in dimension registry"); } wrapper.write(Types.NAMED_COMPOUND_TAG, dimension); }); map(Types.STRING); // World map(Types.LONG); // Seed map(Types.UNSIGNED_BYTE); // Gamemode map(Types.BYTE); // Previous gamemode map(Types.BOOLEAN); // Debug map(Types.BOOLEAN); // Flat map(Types.BOOLEAN); // Keep player attributes handler(wrapper -> { final GlobalBlockPosition lastDeathPosition = wrapper.read(Types.OPTIONAL_GLOBAL_POSITION); if (lastDeathPosition != null) { wrapper.user().put(new LastDeathPosition(lastDeathPosition)); } else { wrapper.user().remove(LastDeathPosition.class); } }); handler(worldDataTrackerHandler(0)); } }); protocol.registerClientbound(ClientboundPackets1_19.PLAYER_INFO, wrapper -> { final int action = wrapper.passthrough(Types.VAR_INT); final int entries = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < entries; i++) { wrapper.passthrough(Types.UUID); // UUID if (action == 0) { // Add player wrapper.passthrough(Types.STRING); // Player Name wrapper.passthrough(Types.PROFILE_PROPERTY_ARRAY); wrapper.passthrough(Types.VAR_INT); // Gamemode wrapper.passthrough(Types.VAR_INT); // Ping wrapper.passthrough(Types.OPTIONAL_COMPONENT); // Display name // Remove public profile signature wrapper.read(Types.OPTIONAL_PROFILE_KEY); } else if (action == 1 || action == 2) { // Update gamemode/update latency wrapper.passthrough(Types.VAR_INT); } else if (action == 3) { // Update display name wrapper.passthrough(Types.OPTIONAL_COMPONENT); } } }); } @Override protected void registerRewrites() { filter().handler((event, data) -> { if (data.dataType().typeId() <= Types1_18.ENTITY_DATA_TYPES.poseType.typeId()) { data.setDataType(Types1_18.ENTITY_DATA_TYPES.byId(data.dataType().typeId())); } final EntityDataType type = data.dataType(); if (type == Types1_18.ENTITY_DATA_TYPES.particleType) { final Particle particle = (Particle) data.getValue(); final ParticleMappings particleMappings = protocol.getMappingData().getParticleMappings(); if (particle.id() == particleMappings.id("sculk_charge")) { //TODO event.cancel(); return; } else if (particle.id() == particleMappings.id("shriek")) { //TODO event.cancel(); return; } else if (particle.id() == particleMappings.id("vibration")) { // Can't do without the position event.cancel(); return; } protocol.getParticleRewriter().rewriteParticle(event.user(), particle); } else if (type == Types1_18.ENTITY_DATA_TYPES.poseType) { final int pose = data.value(); if (pose >= 8) { // Croaking, using_tongue, roaring, sniffing, emerging, digging -> standing -> standing data.setValue(0); } } }); registerEntityDataTypeHandler(Types1_18.ENTITY_DATA_TYPES.itemType, null, Types1_18.ENTITY_DATA_TYPES.optionalBlockStateType, null, Types1_18.ENTITY_DATA_TYPES.componentType, Types1_18.ENTITY_DATA_TYPES.optionalComponentType); registerBlockStateHandler(EntityTypes1_19.ABSTRACT_MINECART, 11); filter().type(EntityTypes1_19.PAINTING).index(8).handler((event, data) -> { event.cancel(); final StoredEntityData entityData = tracker(event.user()).entityDataIfPresent(event.entityId()); final StoredPainting storedPainting = entityData != null ? entityData.get(StoredPainting.class) : null; if (storedPainting != null) { final int type = data.value(); storedPainting.setType(type); final PacketWrapper packet = PacketWrapper.create(ClientboundPackets1_18.ADD_PAINTING, event.user()); packet.write(Types.VAR_INT, storedPainting.entityId()); packet.write(Types.UUID, storedPainting.uuid()); packet.write(Types.VAR_INT, type); packet.write(Types.BLOCK_POSITION1_14, storedPainting.position()); packet.write(Types.BYTE, storedPainting.direction()); try { // TODO Race condition packet.send(Protocol1_19To1_18_2.class); } catch (Exception e) { throw new RuntimeException(e); } } }); filter().type(EntityTypes1_19.CAT).index(19).handler((event, data) -> data.setDataType(Types1_18.ENTITY_DATA_TYPES.varIntType)); filter().type(EntityTypes1_19.FROG).cancel(16); // Age filter().type(EntityTypes1_19.FROG).cancel(17); // Variant filter().type(EntityTypes1_19.FROG).cancel(18); // Tongue target filter().type(EntityTypes1_19.WARDEN).cancel(16); // Anger filter().type(EntityTypes1_19.GOAT).cancel(18); filter().type(EntityTypes1_19.GOAT).cancel(19); } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); mapEntityTypeWithData(EntityTypes1_19.FROG, EntityTypes1_19.RABBIT).jsonName(); mapEntityTypeWithData(EntityTypes1_19.TADPOLE, EntityTypes1_19.PUFFERFISH).jsonName(); mapEntityTypeWithData(EntityTypes1_19.CHEST_BOAT, EntityTypes1_19.BOAT); mapEntityTypeWithData(EntityTypes1_19.WARDEN, EntityTypes1_19.IRON_GOLEM).jsonName(); mapEntityTypeWithData(EntityTypes1_19.ALLAY, EntityTypes1_19.VEX).jsonName(); } @Override public EntityType typeFromId(final int typeId) { return EntityTypes1_19.getTypeFromId(typeId); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/storage/DimensionRegistryStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.Protocol1_19To1_18_2; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectMap; import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectOpenHashMap; import com.viaversion.viaversion.util.Key; import java.util.HashMap; import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; public final class DimensionRegistryStorage implements StorableObject { private final Map dimensions = new HashMap<>(); private final Int2ObjectMap chatTypes = new Int2ObjectOpenHashMap<>(); public @Nullable CompoundTag dimension(final String dimensionKey) { final CompoundTag compoundTag = dimensions.get(Key.stripMinecraftNamespace(dimensionKey)); return compoundTag != null ? compoundTag.copy() : null; } public void addDimension(final String dimensionKey, final CompoundTag dimension) { dimensions.put(dimensionKey, dimension); } public @Nullable CompoundTag chatType(final int id) { return chatTypes.isEmpty() ? Protocol1_19To1_18_2.MAPPINGS.chatType(id) : chatTypes.get(id); } public void addChatType(final int id, final CompoundTag chatType) { chatTypes.put(id, chatType); } public void clear() { dimensions.clear(); chatTypes.clear(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/storage/EntityTracker1_19.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.libs.fastutil.ints.IntArrayList; import com.viaversion.viaversion.libs.fastutil.ints.IntList; public final class EntityTracker1_19 extends EntityTrackerBase { private final IntList affectedByBlindness = new IntArrayList(); private final IntList affectedByDarkness = new IntArrayList(); public EntityTracker1_19(final UserConnection connection) { super(connection, EntityTypes1_19.PLAYER); } @Override public void removeEntity(final int id) { super.removeEntity(id); this.affectedByBlindness.rem(id); this.affectedByDarkness.rem(id); } public IntList getAffectedByBlindness() { return affectedByBlindness; } public IntList getAffectedByDarkness() { return affectedByDarkness; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/storage/LastDeathPosition.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.minecraft.GlobalBlockPosition; public record LastDeathPosition(GlobalBlockPosition position) implements StorableObject { } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/storage/NonceStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage; import com.viaversion.viaversion.api.connection.StorableObject; import org.checkerframework.checker.nullness.qual.Nullable; public record NonceStorage(byte @Nullable [] nonce) implements StorableObject { } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/storage/StoredPainting.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.minecraft.BlockPosition; import java.util.UUID; public final class StoredPainting implements StorableObject { private final int entityId; private final UUID uuid; private final BlockPosition position; private final byte direction; private int type; public StoredPainting(int entityId, UUID uuid, BlockPosition position, byte direction) { this.entityId = entityId; this.uuid = uuid; this.position = position; this.direction = direction; } public StoredPainting(final int entityId, final UUID uuid, final BlockPosition position, final int direction) { this(entityId, uuid, position, to2dDirection(direction)); } private static byte to2dDirection(int direction) { return switch (direction) { case 0, 1 -> -1; // No worky case 2 -> 2; case 3 -> 0; case 4 -> 1; case 5 -> 3; default -> throw new IllegalArgumentException("Invalid direction: " + direction); }; } public int entityId() { return entityId; } public UUID uuid() { return uuid; } public BlockPosition position() { return position; } public byte direction() { return direction; } public int type() { return type; } public void setType(final int type) { this.type = type; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_2to1_20/Protocol1_20_2To1_20.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_2to1_20; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.protocol.v1_20_2to1_20.provider.AdvancementCriteriaProvider; import com.viaversion.viabackwards.protocol.v1_20_2to1_20.rewriter.BlockItemPacketRewriter1_20_2; import com.viaversion.viabackwards.protocol.v1_20_2to1_20.rewriter.BlockRewriter1_20_2; import com.viaversion.viabackwards.protocol.v1_20_2to1_20.rewriter.EntityPacketRewriter1_20_2; import com.viaversion.viabackwards.protocol.v1_20_2to1_20.storage.ConfigurationPacketStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_4; import com.viaversion.viaversion.api.platform.providers.ViaProviders; import com.viaversion.viaversion.api.protocol.packet.Direction; 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.data.entity.EntityTrackerBase; import com.viaversion.viaversion.exception.CancelException; import com.viaversion.viaversion.exception.InformativeException; import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets; import com.viaversion.viaversion.protocols.base.ServerboundLoginPackets; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ClientboundPackets1_19_4; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ServerboundPackets1_19_4; import com.viaversion.viaversion.protocols.v1_20to1_20_2.Protocol1_20To1_20_2; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ClientboundConfigurationPackets1_20_2; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ClientboundPackets1_20_2; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ServerboundConfigurationPackets1_20_2; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ServerboundPackets1_20_2; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import java.util.UUID; public final class Protocol1_20_2To1_20 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.20.2", "1.20", Protocol1_20To1_20_2.class); private final EntityPacketRewriter1_20_2 entityPacketRewriter = new EntityPacketRewriter1_20_2(this); private final BlockItemPacketRewriter1_20_2 itemPacketRewriter = new BlockItemPacketRewriter1_20_2(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BlockRewriter blockRewriter = new BlockRewriter1_20_2(this); public Protocol1_20_2To1_20() { super(ClientboundPackets1_20_2.class, ClientboundPackets1_19_4.class, ServerboundPackets1_20_2.class, ServerboundPackets1_19_4.class); } @Override protected void registerPackets() { super.registerPackets(); registerClientbound(ClientboundPackets1_20_2.SET_DISPLAY_OBJECTIVE, wrapper -> { final int slot = wrapper.read(Types.VAR_INT); wrapper.write(Types.BYTE, (byte) slot); }); registerClientbound(State.LOGIN, ClientboundLoginPackets.LOGIN_FINISHED, wrapper -> { // We can't set the internal state to configuration here as protocols down the line will expect the state to be play // Add this *before* sending the ack since the server might immediately answer wrapper.user().put(new ConfigurationPacketStorage()); // Overwrite what is set by the base protocol wrapper.user().getProtocolInfo().setClientState(State.LOGIN); // States set to configuration in the base protocol wrapper.create(ServerboundLoginPackets.LOGIN_ACKNOWLEDGED).scheduleSendToServer(Protocol1_20_2To1_20.class); }); registerClientbound(State.CONFIGURATION, ClientboundConfigurationPackets1_20_2.FINISH_CONFIGURATION, wrapper -> { wrapper.cancel(); wrapper.user().getProtocolInfo().setServerState(State.PLAY); wrapper.user().get(ConfigurationPacketStorage.class).setFinished(true); wrapper.create(ServerboundConfigurationPackets1_20_2.FINISH_CONFIGURATION).sendToServer(Protocol1_20_2To1_20.class); wrapper.user().getProtocolInfo().setClientState(State.PLAY); }); registerServerbound(State.LOGIN, ServerboundLoginPackets.HELLO, wrapper -> { wrapper.passthrough(Types.STRING); // Name // TODO Bad final UUID uuid = wrapper.read(Types.OPTIONAL_UUID); wrapper.write(Types.UUID, uuid != null ? uuid : new UUID(0, 0)); }); registerClientbound(ClientboundPackets1_20_2.START_CONFIGURATION, null, wrapper -> { wrapper.cancel(); wrapper.user().getProtocolInfo().setServerState(State.CONFIGURATION); // TODO: Check whether all the necessary data for the join game packet is always expected by the client or if we need to cache it from the initial login final PacketWrapper configAcknowledgedPacket = wrapper.create(ServerboundPackets1_20_2.CONFIGURATION_ACKNOWLEDGED); configAcknowledgedPacket.sendToServer(Protocol1_20_2To1_20.class); wrapper.user().getProtocolInfo().setClientState(State.CONFIGURATION); wrapper.user().put(new ConfigurationPacketStorage()); }); cancelClientbound(ClientboundPackets1_20_2.PONG_RESPONSE); // Some can be directly remapped to play packets, others need to be queued // Set the packet type properly so the state on it is changed registerClientbound(State.CONFIGURATION, ClientboundConfigurationPackets1_20_2.DISCONNECT.getId(), -1, wrapper -> { wrapper.setPacketType(ClientboundPackets1_19_4.DISCONNECT); }); registerClientbound(State.CONFIGURATION, ClientboundConfigurationPackets1_20_2.KEEP_ALIVE.getId(), -1, wrapper -> { wrapper.setPacketType(ClientboundPackets1_19_4.KEEP_ALIVE); }); registerClientbound(State.CONFIGURATION, ClientboundConfigurationPackets1_20_2.PING.getId(), -1, wrapper -> { wrapper.setPacketType(ClientboundPackets1_19_4.PING); }); registerClientbound(State.CONFIGURATION, ClientboundConfigurationPackets1_20_2.RESOURCE_PACK.getId(), -1, wrapper -> { // Send after join. We have to pretend the client accepted, else the server won't continue... wrapper.user().get(ConfigurationPacketStorage.class).setResourcePack(wrapper); wrapper.cancel(); final PacketWrapper acceptedResponse = wrapper.create(ServerboundConfigurationPackets1_20_2.RESOURCE_PACK); acceptedResponse.write(Types.VAR_INT, 3); acceptedResponse.sendToServer(Protocol1_20_2To1_20.class); final PacketWrapper downloadedResponse = wrapper.create(ServerboundConfigurationPackets1_20_2.RESOURCE_PACK); downloadedResponse.write(Types.VAR_INT, 0); downloadedResponse.sendToServer(Protocol1_20_2To1_20.class); }); registerClientbound(State.CONFIGURATION, ClientboundConfigurationPackets1_20_2.REGISTRY_DATA.getId(), -1, wrapper -> { wrapper.cancel(); final CompoundTag registry = wrapper.read(Types.COMPOUND_TAG); entityPacketRewriter.trackBiomeSize(wrapper.user(), registry); entityPacketRewriter.cacheDimensionData(wrapper.user(), registry); wrapper.user().get(ConfigurationPacketStorage.class).setRegistry(registry); }); registerClientbound(State.CONFIGURATION, ClientboundConfigurationPackets1_20_2.UPDATE_ENABLED_FEATURES.getId(), -1, wrapper -> { final String[] enabledFeatures = wrapper.read(Types.STRING_ARRAY); wrapper.user().get(ConfigurationPacketStorage.class).setEnabledFeatures(enabledFeatures); wrapper.cancel(); }); registerClientbound(State.CONFIGURATION, ClientboundConfigurationPackets1_20_2.UPDATE_TAGS.getId(), -1, wrapper -> { tagRewriter.handleGeneric(wrapper); wrapper.user().get(ConfigurationPacketStorage.class).addRawPacket(wrapper, ClientboundPackets1_19_4.UPDATE_TAGS); wrapper.cancel(); }); registerClientbound(State.CONFIGURATION, ClientboundConfigurationPackets1_20_2.CUSTOM_PAYLOAD.getId(), -1, wrapper -> { wrapper.user().get(ConfigurationPacketStorage.class).addRawPacket(wrapper, ClientboundPackets1_19_4.CUSTOM_PAYLOAD); wrapper.cancel(); }); } @Override public void register(final ViaProviders providers) { providers.register(AdvancementCriteriaProvider.class, new AdvancementCriteriaProvider()); } @Override public void transform(final Direction direction, final State state, final PacketWrapper wrapper) throws InformativeException, CancelException { final ConfigurationPacketStorage configurationPacketStorage = wrapper.user().get(ConfigurationPacketStorage.class); if (configurationPacketStorage == null || configurationPacketStorage.isFinished()) { super.transform(direction, state, wrapper); return; } if (direction == Direction.CLIENTBOUND) { super.transform(direction, State.CONFIGURATION, wrapper); return; } // Map some of the packets to their configuration counterparts final int id = wrapper.getId(); if (id == ServerboundPackets1_19_4.CLIENT_INFORMATION.getId()) { wrapper.setPacketType(ServerboundConfigurationPackets1_20_2.CLIENT_INFORMATION); } else if (id == ServerboundPackets1_19_4.CUSTOM_PAYLOAD.getId()) { wrapper.setPacketType(ServerboundConfigurationPackets1_20_2.CUSTOM_PAYLOAD); } else if (id == ServerboundPackets1_19_4.KEEP_ALIVE.getId()) { wrapper.setPacketType(ServerboundConfigurationPackets1_20_2.KEEP_ALIVE); } else if (id == ServerboundPackets1_19_4.PONG.getId()) { wrapper.setPacketType(ServerboundConfigurationPackets1_20_2.PONG); } else if (id == ServerboundPackets1_19_4.RESOURCE_PACK.getId()) { wrapper.setPacketType(ServerboundConfigurationPackets1_20_2.RESOURCE_PACK); } else { // TODO Queue throw CancelException.generate(); } } @Override protected void registerConfigurationChangeHandlers() { // Don't register them in the transitioning protocol } @Override public void init(final UserConnection connection) { addEntityTracker(connection, new EntityTrackerBase(connection, EntityTypes1_19_4.PLAYER)); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_20_2 getEntityRewriter() { return entityPacketRewriter; } @Override public BlockItemPacketRewriter1_20_2 getItemRewriter() { return itemPacketRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_2to1_20/provider/AdvancementCriteriaProvider.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_2to1_20.provider; import com.viaversion.viaversion.api.platform.providers.Provider; public class AdvancementCriteriaProvider implements Provider { public String[] getCriteria(final String advancementKey) { return new String[0]; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_2to1_20/rewriter/BlockItemPacketRewriter1_20_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_2to1_20.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntArrayTag; import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.protocol.v1_20_2to1_20.Protocol1_20_2To1_20; import com.viaversion.viabackwards.protocol.v1_20_2to1_20.provider.AdvancementCriteriaProvider; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.ChunkPosition; import com.viaversion.viaversion.api.minecraft.blockentity.BlockEntity; import com.viaversion.viaversion.api.minecraft.blockentity.BlockEntityImpl; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; import com.viaversion.viaversion.api.minecraft.chunks.DataPalette; import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_18; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ServerboundPackets1_19_4; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ClientboundPackets1_20_2; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ServerboundPackets1_20_2; import com.viaversion.viaversion.protocols.v1_20to1_20_2.rewriter.RecipeRewriter1_20_2; import com.viaversion.viaversion.util.MathUtil; import org.checkerframework.checker.nullness.qual.Nullable; public final class BlockItemPacketRewriter1_20_2 extends BackwardsItemRewriter { public BlockItemPacketRewriter1_20_2(final Protocol1_20_2To1_20 protocol) { super(protocol, Types.ITEM1_20_2, Types.ITEM1_20_2_ARRAY, Types.ITEM1_13_2, Types.ITEM1_13_2_ARRAY); } @Override public void registerPackets() { protocol.cancelClientbound(ClientboundPackets1_20_2.CHUNK_BATCH_START); protocol.registerClientbound(ClientboundPackets1_20_2.CHUNK_BATCH_FINISHED, null, wrapper -> { wrapper.cancel(); final PacketWrapper receivedPacket = wrapper.create(ServerboundPackets1_20_2.CHUNK_BATCH_RECEIVED); receivedPacket.write(Types.FLOAT, 500F); // Requested next batch size... arbitrary value here receivedPacket.sendToServer(Protocol1_20_2To1_20.class); }); protocol.registerClientbound(ClientboundPackets1_20_2.FORGET_LEVEL_CHUNK, wrapper -> { final ChunkPosition chunkPosition = wrapper.read(Types.CHUNK_POSITION); wrapper.write(Types.INT, chunkPosition.chunkX()); wrapper.write(Types.INT, chunkPosition.chunkZ()); }); protocol.registerClientbound(ClientboundPackets1_20_2.MAP_ITEM_DATA, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Map id wrapper.passthrough(Types.BYTE); // Scale wrapper.passthrough(Types.BOOLEAN); // Locked if (wrapper.passthrough(Types.BOOLEAN)) { final int icons = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < icons; i++) { // Map new marker types to red marker final int markerType = wrapper.read(Types.VAR_INT); wrapper.write(Types.VAR_INT, markerType < 27 ? markerType : 2); wrapper.passthrough(Types.BYTE); // X wrapper.passthrough(Types.BYTE); // Y wrapper.passthrough(Types.BYTE); // Rotation wrapper.passthrough(Types.OPTIONAL_COMPONENT); // Display name } } }); protocol.registerClientbound(ClientboundPackets1_20_2.TAG_QUERY, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Transaction id wrapper.write(Types.NAMED_COMPOUND_TAG, wrapper.read(Types.COMPOUND_TAG)); }); protocol.replaceClientbound(ClientboundPackets1_20_2.BLOCK_ENTITY_DATA, wrapper -> { final BlockPosition position = wrapper.passthrough(Types.BLOCK_POSITION1_14); final int typeId = wrapper.passthrough(Types.VAR_INT); final CompoundTag tag = wrapper.read(Types.TRUSTED_COMPOUND_TAG); final BlockEntity blockEntity = new BlockEntityImpl(BlockEntity.pack(position.x(), position.z()), (short) position.y(), typeId, tag); protocol.getBlockRewriter().handleBlockEntity(wrapper.user(), blockEntity); wrapper.write(Types.NAMED_COMPOUND_TAG, blockEntity.tag()); }); protocol.registerServerbound(ServerboundPackets1_19_4.SET_BEACON, wrapper -> { // Effects start at 1 before 1.20.2 if (wrapper.passthrough(Types.BOOLEAN)) { // Primary effect wrapper.write(Types.VAR_INT, wrapper.read(Types.VAR_INT) - 1); } if (wrapper.passthrough(Types.BOOLEAN)) { // Secondary effect wrapper.write(Types.VAR_INT, wrapper.read(Types.VAR_INT) - 1); } }); protocol.replaceClientbound(ClientboundPackets1_20_2.UPDATE_ADVANCEMENTS, wrapper -> { wrapper.passthrough(Types.BOOLEAN); // Reset/clear final int size = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < size; i++) { final String advancement = wrapper.passthrough(Types.STRING); wrapper.passthrough(Types.OPTIONAL_STRING); // Parent // Display data if (wrapper.passthrough(Types.BOOLEAN)) { wrapper.passthrough(Types.COMPONENT); // Title wrapper.passthrough(Types.COMPONENT); // Description passthroughClientboundItem(wrapper); // Icon wrapper.passthrough(Types.VAR_INT); // Frame type final int flags = wrapper.passthrough(Types.INT); // Flags if ((flags & 1) != 0) { wrapper.passthrough(Types.STRING); // Background texture } wrapper.passthrough(Types.FLOAT); // X wrapper.passthrough(Types.FLOAT); // Y } final AdvancementCriteriaProvider criteriaProvider = Via.getManager().getProviders().get(AdvancementCriteriaProvider.class); wrapper.write(Types.STRING_ARRAY, criteriaProvider.getCriteria(advancement)); final int requirements = wrapper.passthrough(Types.VAR_INT); for (int array = 0; array < requirements; array++) { wrapper.passthrough(Types.STRING_ARRAY); } wrapper.passthrough(Types.BOOLEAN); // Send telemetry } }); protocol.replaceClientbound(ClientboundPackets1_20_2.SET_EQUIPMENT, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Entity ID handler(wrapper -> { byte slot; do { slot = wrapper.passthrough(Types.BYTE); wrapper.write(Types.ITEM1_13_2, handleItemToClient(wrapper.user(), wrapper.read(Types.ITEM1_20_2))); } while ((slot & 0xFFFFFF80) != 0); }); } }); new RecipeRewriter1_20_2<>(protocol) { @Override protected Type mappedItemType() { return BlockItemPacketRewriter1_20_2.this.mappedItemType(); } @Override protected Type mappedItemArrayType() { return BlockItemPacketRewriter1_20_2.this.mappedItemArrayType(); } }.register(ClientboundPackets1_20_2.UPDATE_RECIPES); } @Override public @Nullable Item handleItemToClient(UserConnection connection, @Nullable final Item item) { if (item == null) { return null; } if (item.tag() != null) { com.viaversion.viaversion.protocols.v1_20to1_20_2.rewriter.BlockItemPacketRewriter1_20_2.to1_20_1Effects(item); final CompoundTag skullOwnerTag = item.tag().getCompoundTag("SkullOwner"); if (skullOwnerTag != null && !skullOwnerTag.contains("Id") && skullOwnerTag.contains("Properties")) { skullOwnerTag.put("Id", new IntArrayTag(new int[]{0, 0, 0, 0})); } } return super.handleItemToClient(connection, item); } @Override public @Nullable Item handleItemToServer(UserConnection connection, @Nullable final Item item) { if (item == null) { return null; } if (item.tag() != null) { com.viaversion.viaversion.protocols.v1_20to1_20_2.rewriter.BlockItemPacketRewriter1_20_2.to1_20_2Effects(item); } return super.handleItemToServer(connection, item); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_2to1_20/rewriter/BlockRewriter1_20_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_2to1_20.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntArrayTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.blockentity.BlockEntity; import com.viaversion.viaversion.api.protocol.Protocol; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_18; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; import com.viaversion.viaversion.protocols.v1_20to1_20_2.data.PotionEffects1_20_2; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ClientboundPackets1_20_2; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.util.Key; public final class BlockRewriter1_20_2 extends BlockRewriter { public BlockRewriter1_20_2(final Protocol protocol) { super(protocol, Types.BLOCK_POSITION1_14, Types.NAMED_COMPOUND_TAG, ChunkType1_20_2::new, ChunkType1_18::new); } @Override public void handleBlockEntity(final UserConnection connection, final BlockEntity blockEntity) { final CompoundTag tag = blockEntity.tag(); final Tag primaryEffect = tag.remove("primary_effect"); if (primaryEffect instanceof StringTag) { final String effectKey = Key.stripMinecraftNamespace(((StringTag) primaryEffect).getValue()); tag.putInt("Primary", PotionEffects1_20_2.keyToId(effectKey) + 1); // Empty effect at 0 } final Tag secondaryEffect = tag.remove("secondary_effect"); if (secondaryEffect instanceof StringTag) { final String effectKey = Key.stripMinecraftNamespace(((StringTag) secondaryEffect).getValue()); tag.putInt("Secondary", PotionEffects1_20_2.keyToId(effectKey) + 1); // Empty effect at 0 } final CompoundTag skullOwnerTag = tag.getCompoundTag("SkullOwner"); if (skullOwnerTag != null && !skullOwnerTag.contains("Id") && skullOwnerTag.contains("Properties")) { skullOwnerTag.put("Id", new IntArrayTag(new int[]{0, 0, 0, 0})); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_2to1_20/rewriter/EntityPacketRewriter1_20_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_2to1_20.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_20_2to1_20.Protocol1_20_2To1_20; import com.viaversion.viabackwards.protocol.v1_20_2to1_20.storage.ConfigurationPacketStorage; import com.viaversion.viaversion.api.minecraft.GlobalBlockPosition; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_4; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_20; import com.viaversion.viaversion.api.type.types.version.Types1_20_2; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ClientboundPackets1_19_4; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ClientboundPackets1_20_2; public final class EntityPacketRewriter1_20_2 extends EntityRewriter { public EntityPacketRewriter1_20_2(final Protocol1_20_2To1_20 protocol) { super(protocol, Types1_20.ENTITY_DATA_TYPES.optionalComponentType, Types1_20.ENTITY_DATA_TYPES.booleanType); } @Override public void registerPackets() { registerSetEntityData(ClientboundPackets1_20_2.SET_ENTITY_DATA, Types1_20_2.ENTITY_DATA_LIST, Types1_20.ENTITY_DATA_LIST); protocol.replaceClientbound(ClientboundPackets1_20_2.ADD_ENTITY, new PacketHandlers() { @Override protected void register() { handler(wrapper -> { final int entityId = wrapper.passthrough(Types.VAR_INT); wrapper.passthrough(Types.UUID); // UUID final int entityType = wrapper.read(Types.VAR_INT); tracker(wrapper.user()).addEntity(entityId, typeFromId(entityType)); if (entityType != EntityTypes1_19_4.PLAYER.getId()) { wrapper.write(Types.VAR_INT, entityType); if (entityType == EntityTypes1_19_4.FALLING_BLOCK.getId()) { wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z wrapper.passthrough(Types.BYTE); // Pitch wrapper.passthrough(Types.BYTE); // Yaw wrapper.passthrough(Types.BYTE); // Head yaw final int blockState = wrapper.read(Types.VAR_INT); // Data wrapper.write(Types.VAR_INT, protocol.getMappingData().getNewBlockStateId(blockState)); } return; } // Map to spawn player packet wrapper.setPacketType(ClientboundPackets1_19_4.ADD_PLAYER); wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z final byte pitch = wrapper.read(Types.BYTE); wrapper.passthrough(Types.BYTE); // Yaw wrapper.write(Types.BYTE, pitch); wrapper.read(Types.BYTE); // Head yaw wrapper.read(Types.VAR_INT); // Data final short velocityX = wrapper.read(Types.SHORT); final short velocityY = wrapper.read(Types.SHORT); final short velocityZ = wrapper.read(Types.SHORT); if (velocityX == 0 && velocityY == 0 && velocityZ == 0) { return; } // Follow up with velocity packet wrapper.send(Protocol1_20_2To1_20.class); wrapper.cancel(); final PacketWrapper velocityPacket = wrapper.create(ClientboundPackets1_19_4.SET_ENTITY_MOTION); velocityPacket.write(Types.VAR_INT, entityId); velocityPacket.write(Types.SHORT, velocityX); velocityPacket.write(Types.SHORT, velocityY); velocityPacket.write(Types.SHORT, velocityZ); velocityPacket.send(Protocol1_20_2To1_20.class); }); } }); protocol.registerClientbound(ClientboundPackets1_20_2.LOGIN, new PacketHandlers() { @Override public void register() { handler(wrapper -> { final ConfigurationPacketStorage configurationPacketStorage = wrapper.user().get(ConfigurationPacketStorage.class); wrapper.passthrough(Types.INT); // Entity id wrapper.passthrough(Types.BOOLEAN); // Hardcore final String[] worlds = wrapper.read(Types.STRING_ARRAY); final int maxPlayers = wrapper.read(Types.VAR_INT); final int viewDistance = wrapper.read(Types.VAR_INT); final int simulationDistance = wrapper.read(Types.VAR_INT); final boolean reducedDebugInfo = wrapper.read(Types.BOOLEAN); final boolean showRespawnScreen = wrapper.read(Types.BOOLEAN); wrapper.read(Types.BOOLEAN); // Limited crafting final String dimensionType = wrapper.read(Types.STRING); final String world = wrapper.read(Types.STRING); final long seed = wrapper.read(Types.LONG); wrapper.passthrough(Types.BYTE); // Gamemode wrapper.passthrough(Types.BYTE); // Previous gamemode wrapper.write(Types.STRING_ARRAY, worlds); wrapper.write(Types.NAMED_COMPOUND_TAG, configurationPacketStorage.registry()); wrapper.write(Types.STRING, dimensionType); wrapper.write(Types.STRING, world); wrapper.write(Types.LONG, seed); wrapper.write(Types.VAR_INT, maxPlayers); wrapper.write(Types.VAR_INT, viewDistance); wrapper.write(Types.VAR_INT, simulationDistance); wrapper.write(Types.BOOLEAN, reducedDebugInfo); wrapper.write(Types.BOOLEAN, showRespawnScreen); worldDataTrackerHandlerByKey().handle(wrapper); wrapper.send(Protocol1_20_2To1_20.class); wrapper.cancel(); if (configurationPacketStorage.enabledFeatures() != null) { final PacketWrapper featuresPacket = wrapper.create(ClientboundPackets1_19_4.UPDATE_ENABLED_FEATURES); featuresPacket.write(Types.STRING_ARRAY, configurationPacketStorage.enabledFeatures()); featuresPacket.send(Protocol1_20_2To1_20.class); } configurationPacketStorage.sendQueuedPackets(wrapper.user()); }); } }); protocol.registerClientbound(ClientboundPackets1_20_2.RESPAWN, new PacketHandlers() { @Override public void register() { handler(wrapper -> { wrapper.passthrough(Types.STRING); // Dimension type wrapper.passthrough(Types.STRING); // World wrapper.passthrough(Types.LONG); // Seed wrapper.write(Types.UNSIGNED_BYTE, wrapper.read(Types.BYTE).shortValue()); // Gamemode wrapper.passthrough(Types.BYTE); // Previous gamemode wrapper.passthrough(Types.BOOLEAN); // Debug wrapper.passthrough(Types.BOOLEAN); // Flat final GlobalBlockPosition lastDeathPosition = wrapper.read(Types.OPTIONAL_GLOBAL_POSITION); final int portalCooldown = wrapper.read(Types.VAR_INT); wrapper.passthrough(Types.BYTE); // Data to keep wrapper.write(Types.OPTIONAL_GLOBAL_POSITION, lastDeathPosition); wrapper.write(Types.VAR_INT, portalCooldown); }); handler(worldDataTrackerHandlerByKey()); // Tracks world height and name for chunk data and entity (un)tracking } }); protocol.registerClientbound(ClientboundPackets1_20_2.UPDATE_MOB_EFFECT, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Entity id wrapper.write(Types.VAR_INT, wrapper.read(Types.VAR_INT) + 1); // Effect id wrapper.passthrough(Types.BYTE); // Amplifier wrapper.passthrough(Types.VAR_INT); // Duration wrapper.passthrough(Types.BYTE); // Flags final CompoundTag factorData = wrapper.read(Types.OPTIONAL_COMPOUND_TAG); wrapper.write(Types.OPTIONAL_NAMED_COMPOUND_TAG, factorData); // Factor data }); protocol.registerClientbound(ClientboundPackets1_20_2.REMOVE_MOB_EFFECT, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Entity id wrapper.write(Types.VAR_INT, wrapper.read(Types.VAR_INT) + 1); // Effect id }); } @Override protected void registerRewrites() { filter().mapDataType(Types1_20.ENTITY_DATA_TYPES::byId); registerEntityDataTypeHandler(Types1_20.ENTITY_DATA_TYPES.itemType, Types1_20.ENTITY_DATA_TYPES.blockStateType, Types1_20.ENTITY_DATA_TYPES.optionalBlockStateType, Types1_20.ENTITY_DATA_TYPES.particleType, null, null); registerBlockStateHandler(EntityTypes1_19_4.ABSTRACT_MINECART, 11); filter().type(EntityTypes1_19_4.DISPLAY).removeIndex(10); } @Override public EntityType typeFromId(final int type) { return EntityTypes1_19_4.getTypeFromId(type); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_2to1_20/storage/ConfigurationPacketStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_2to1_20.storage; import com.google.common.base.Preconditions; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.protocol.v1_20_2to1_20.Protocol1_20_2To1_20; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.protocol.packet.PacketType; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ClientboundPackets1_19_4; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import java.util.ArrayList; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; public final class ConfigurationPacketStorage implements StorableObject { private final List rawPackets = new ArrayList<>(); private CompoundTag registry; private String[] enabledFeatures; private boolean finished; private QueuedPacket resourcePack; public void setResourcePack(final PacketWrapper wrapper) { resourcePack = toQueuedPacket(wrapper, ClientboundPackets1_19_4.RESOURCE_PACK); } public CompoundTag registry() { Preconditions.checkNotNull(registry); return registry; } public void setRegistry(final CompoundTag registry) { this.registry = registry; } public String @Nullable [] enabledFeatures() { return enabledFeatures; } public void setEnabledFeatures(final String[] enabledFeatures) { this.enabledFeatures = enabledFeatures; } public void addRawPacket(final PacketWrapper wrapper, final PacketType type) { rawPackets.add(toQueuedPacket(wrapper, type)); } private QueuedPacket toQueuedPacket(final PacketWrapper wrapper, final PacketType type) { Preconditions.checkArgument(!wrapper.isCancelled(), "Wrapper should be cancelled AFTER calling toQueuedPacket"); // It's easier to just copy it to a byte array buffer than to manually read the data final ByteBuf buf = Unpooled.buffer(); //noinspection deprecation wrapper.setId(-1); // Don't write the packet id to the buffer wrapper.writeToBuffer(buf); return new QueuedPacket(buf, type); } public void sendQueuedPackets(final UserConnection connection) { // Send resource pack at the end List packets = rawPackets; if (resourcePack != null) { packets = new ArrayList<>(rawPackets); packets.add(resourcePack); resourcePack = null; } for (final QueuedPacket queuedPacket : packets) { // Don't clear the list or use the original buffer, we might need them later if a server skips subsequent config phases final ByteBuf buf = queuedPacket.buf().copy(); try { final PacketWrapper packet = PacketWrapper.create(queuedPacket.packetType(), buf, connection); packet.send(Protocol1_20_2To1_20.class); } finally { buf.release(); } } } public boolean isFinished() { return finished; } public void setFinished(final boolean finished) { this.finished = finished; } public record QueuedPacket(ByteBuf buf, PacketType packetType) { } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_3to1_20_2/Protocol1_20_3To1_20_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_3to1_20_2; 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.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.rewriter.BlockItemPacketRewriter1_20_3; import com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.rewriter.BlockPacketRewriter1_20_3; import com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.rewriter.EntityPacketRewriter1_20_3; import com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.storage.ResourcepackIDStorage; import com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.storage.SpawnPositionStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_20_3; import com.viaversion.viaversion.api.minecraft.item.Item; 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; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.libs.fastutil.Pair; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.rewriter.CommandRewriter1_19_4; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.Protocol1_20_2To1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundConfigurationPackets1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundPacket1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundPackets1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ServerboundPacket1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ServerboundPackets1_20_3; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ClientboundConfigurationPackets1_20_2; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ClientboundPacket1_20_2; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ClientboundPackets1_20_2; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ServerboundConfigurationPackets1_20_2; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ServerboundPacket1_20_2; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ServerboundPackets1_20_2; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; import com.viaversion.viaversion.util.ComponentUtil; import java.util.BitSet; import java.util.UUID; import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap; public final class Protocol1_20_3To1_20_2 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.20.3", "1.20.2", Protocol1_20_2To1_20_3.class); private final EntityPacketRewriter1_20_3 entityRewriter = new EntityPacketRewriter1_20_3(this); private final BlockItemPacketRewriter1_20_3 itemRewriter = new BlockItemPacketRewriter1_20_3(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final JsonNBTComponentRewriter translatableRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.NBT); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BlockRewriter blockRewriter = new BlockPacketRewriter1_20_3(this); public Protocol1_20_3To1_20_2() { super(ClientboundPacket1_20_3.class, ClientboundPacket1_20_2.class, ServerboundPacket1_20_3.class, ServerboundPacket1_20_2.class); } @Override protected void registerPackets() { super.registerPackets(); final CommandRewriter1_19_4 commandRewriter = new CommandRewriter1_19_4<>(this) { @Override public void handleArgument(final PacketWrapper wrapper, final String argumentType) { if (argumentType.equals("minecraft:style")) { wrapper.write(Types.VAR_INT, 1); // Phrase } else { super.handleArgument(wrapper, argumentType); } } }; replaceClientbound(ClientboundPackets1_20_3.COMMANDS, commandRewriter::handle1_19); registerClientbound(ClientboundPackets1_20_3.RESET_SCORE, ClientboundPackets1_20_2.SET_SCORE, wrapper -> { wrapper.passthrough(Types.STRING); // Owner wrapper.write(Types.VAR_INT, 1); // Reset score final String objectiveName = wrapper.read(Types.OPTIONAL_STRING); wrapper.write(Types.STRING, objectiveName != null ? objectiveName : ""); // Objective name }); replaceClientbound(ClientboundPackets1_20_3.SET_SCORE, wrapper -> { wrapper.passthrough(Types.STRING); // Owner wrapper.write(Types.VAR_INT, 0); // Change score wrapper.passthrough(Types.STRING); // Objective name wrapper.passthrough(Types.VAR_INT); // Score // Remove display and number format wrapper.clearInputBuffer(); }); replaceClientbound(ClientboundPackets1_20_3.SET_OBJECTIVE, wrapper -> { wrapper.passthrough(Types.STRING); // Objective Name final byte action = wrapper.passthrough(Types.BYTE); // Method if (action == 0 || action == 2) { convertComponent(wrapper); // Display Name wrapper.passthrough(Types.VAR_INT); // Render type // Remove number format wrapper.clearInputBuffer(); } }); cancelClientbound(ClientboundPackets1_20_3.TICKING_STATE); cancelClientbound(ClientboundPackets1_20_3.TICKING_STEP); registerServerbound(ServerboundPackets1_20_2.SET_JIGSAW_BLOCK, wrapper -> { wrapper.passthrough(Types.BLOCK_POSITION1_14); // Position wrapper.passthrough(Types.STRING); // Name wrapper.passthrough(Types.STRING); // Target wrapper.passthrough(Types.STRING); // Pool wrapper.passthrough(Types.STRING); // Final state wrapper.passthrough(Types.STRING); // Joint type wrapper.write(Types.VAR_INT, 0); // Selection priority wrapper.write(Types.VAR_INT, 0); // Placement priority }); // Components are now (mostly) written as nbt instead of json strings replaceClientbound(ClientboundPackets1_20_3.UPDATE_ADVANCEMENTS, wrapper -> { wrapper.passthrough(Types.BOOLEAN); // Reset/clear final int size = wrapper.passthrough(Types.VAR_INT); // Mapping size for (int i = 0; i < size; i++) { wrapper.passthrough(Types.STRING); // Identifier wrapper.passthrough(Types.OPTIONAL_STRING); // Parent // Display data if (wrapper.passthrough(Types.BOOLEAN)) { convertComponent(wrapper); // Title convertComponent(wrapper); // Description final Item icon = itemRewriter.handleItemToClient(wrapper.user(), wrapper.read(Types.ITEM1_20_2)); wrapper.write(Types.ITEM1_20_2, icon); wrapper.passthrough(Types.VAR_INT); // Frame type final int flags = wrapper.passthrough(Types.INT); if ((flags & 1) != 0) { wrapper.passthrough(Types.STRING); // Background texture } wrapper.passthrough(Types.FLOAT); // X wrapper.passthrough(Types.FLOAT); // Y } final int requirements = wrapper.passthrough(Types.VAR_INT); for (int array = 0; array < requirements; array++) { wrapper.passthrough(Types.STRING_ARRAY); } wrapper.passthrough(Types.BOOLEAN); // Send telemetry } }); registerClientbound(ClientboundPackets1_20_3.COMMAND_SUGGESTIONS, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Transaction id wrapper.passthrough(Types.VAR_INT); // Start wrapper.passthrough(Types.VAR_INT); // Length final int suggestions = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < suggestions; i++) { wrapper.passthrough(Types.STRING); // Suggestion convertOptionalComponent(wrapper); // Tooltip } }); registerClientbound(ClientboundPackets1_20_3.MAP_ITEM_DATA, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Map id wrapper.passthrough(Types.BYTE); // Scale wrapper.passthrough(Types.BOOLEAN); // Locked if (wrapper.passthrough(Types.BOOLEAN)) { final int icons = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < icons; i++) { wrapper.passthrough(Types.VAR_INT); // Type wrapper.passthrough(Types.BYTE); // X wrapper.passthrough(Types.BYTE); // Y wrapper.passthrough(Types.BYTE); // Rotation convertOptionalComponent(wrapper); // Display name } } }); replaceClientbound(ClientboundPackets1_20_3.BOSS_EVENT, wrapper -> { wrapper.passthrough(Types.UUID); // Id final int action = wrapper.passthrough(Types.VAR_INT); if (action == 0 || action == 3) { convertComponent(wrapper); } }); replaceClientbound(ClientboundPackets1_20_3.PLAYER_CHAT, wrapper -> { wrapper.passthrough(Types.UUID); // Sender wrapper.passthrough(Types.VAR_INT); // Index wrapper.passthrough(Types.OPTIONAL_SIGNATURE_BYTES); // Signature wrapper.passthrough(Types.STRING); // Plain content wrapper.passthrough(Types.LONG); // Timestamp wrapper.passthrough(Types.LONG); // Salt final int lastSeen = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < lastSeen; i++) { final int index = wrapper.passthrough(Types.VAR_INT); if (index == 0) { wrapper.passthrough(Types.SIGNATURE_BYTES); } } convertOptionalComponent(wrapper); // Unsigned content final int filterMaskType = wrapper.passthrough(Types.VAR_INT); if (filterMaskType == 2) { wrapper.passthrough(Types.LONG_ARRAY_PRIMITIVE); // Mask } wrapper.passthrough(Types.VAR_INT); // Chat type convertComponent(wrapper); // Sender convertOptionalComponent(wrapper); // Target }); replaceClientbound(ClientboundPackets1_20_3.SET_PLAYER_TEAM, wrapper -> { wrapper.passthrough(Types.STRING); // Team Name final byte action = wrapper.passthrough(Types.BYTE); // Mode if (action == 0 || action == 2) { convertComponent(wrapper); // Display Name wrapper.passthrough(Types.BYTE); // Flags wrapper.passthrough(Types.STRING); // Name Tag Visibility wrapper.passthrough(Types.STRING); // Collision rule wrapper.passthrough(Types.VAR_INT); // Color convertComponent(wrapper); // Prefix convertComponent(wrapper); // Suffix } }); replaceClientbound(ClientboundConfigurationPackets1_20_3.DISCONNECT, this::convertComponent); replaceClientbound(ClientboundPackets1_20_3.DISCONNECT, this::convertComponent); registerClientbound(ClientboundPackets1_20_3.RESOURCE_PACK_PUSH, ClientboundPackets1_20_2.RESOURCE_PACK, resourcePackHandler()); replaceClientbound(ClientboundPackets1_20_3.SERVER_DATA, this::convertComponent); replaceClientbound(ClientboundPackets1_20_3.SET_ACTION_BAR_TEXT, this::convertComponent); replaceClientbound(ClientboundPackets1_20_3.SET_TITLE_TEXT, this::convertComponent); replaceClientbound(ClientboundPackets1_20_3.SET_SUBTITLE_TEXT, this::convertComponent); replaceClientbound(ClientboundPackets1_20_3.DISGUISED_CHAT, wrapper -> { convertComponent(wrapper); wrapper.passthrough(Types.VAR_INT); // Chat type convertComponent(wrapper); // Name convertOptionalComponent(wrapper); // Target name }); replaceClientbound(ClientboundPackets1_20_3.SYSTEM_CHAT, this::convertComponent); replaceClientbound(ClientboundPackets1_20_3.OPEN_SCREEN, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Container id final int containerTypeId = wrapper.read(Types.VAR_INT); final int mappedContainerTypeId = MAPPINGS.getMenuMappings().getNewId(containerTypeId); if (mappedContainerTypeId == -1) { wrapper.cancel(); return; } wrapper.write(Types.VAR_INT, mappedContainerTypeId); convertComponent(wrapper); }); replaceClientbound(ClientboundPackets1_20_3.TAB_LIST, wrapper -> { convertComponent(wrapper); convertComponent(wrapper); }); replaceClientbound(ClientboundPackets1_20_3.PLAYER_COMBAT_KILL, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // Duration handler(wrapper -> convertComponent(wrapper)); } }); replaceClientbound(ClientboundPackets1_20_3.PLAYER_INFO_UPDATE, wrapper -> { final BitSet actions = wrapper.passthrough(Types.PROFILE_ACTIONS_ENUM1_19_3); final int entries = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < entries; i++) { wrapper.passthrough(Types.UUID); if (actions.get(0)) { wrapper.passthrough(Types.STRING); // Player Name wrapper.passthrough(Types.PROFILE_PROPERTY_ARRAY); } if (actions.get(1) && wrapper.passthrough(Types.BOOLEAN)) { wrapper.passthrough(Types.UUID); // Session UUID wrapper.passthrough(Types.PROFILE_KEY); } if (actions.get(2)) { wrapper.passthrough(Types.VAR_INT); // Gamemode } if (actions.get(3)) { wrapper.passthrough(Types.BOOLEAN); // Listed } if (actions.get(4)) { wrapper.passthrough(Types.VAR_INT); // Latency } if (actions.get(5)) { convertOptionalComponent(wrapper); // Display name } } }); registerClientbound(ClientboundPackets1_20_3.SET_DEFAULT_SPAWN_POSITION, wrapper -> { final BlockPosition position = wrapper.passthrough(Types.BLOCK_POSITION1_14); final float angle = wrapper.passthrough(Types.FLOAT); wrapper.user().get(SpawnPositionStorage.class).setSpawnPosition(Pair.of(position, angle)); }); registerClientbound(ClientboundPackets1_20_3.GAME_EVENT, wrapper -> { final short reason = wrapper.passthrough(Types.UNSIGNED_BYTE); if (reason == 13) { // Level chunks load start wrapper.cancel(); final Pair spawnPositionAndAngle = wrapper.user().get(SpawnPositionStorage.class).getSpawnPosition(); // To emulate the old behavior, we send a fake spawn pos packet containing the actual spawn pos which forces // the 1.20.2 client to close the downloading terrain screen like the new game state does final PacketWrapper spawnPosition = wrapper.create(ClientboundPackets1_20_2.SET_DEFAULT_SPAWN_POSITION); spawnPosition.write(Types.BLOCK_POSITION1_14, spawnPositionAndAngle.first()); // position spawnPosition.write(Types.FLOAT, spawnPositionAndAngle.second()); // angle spawnPosition.send(Protocol1_20_3To1_20_2.class, true); } }); cancelClientbound(ClientboundPackets1_20_3.RESOURCE_PACK_POP); registerServerbound(ServerboundPackets1_20_2.RESOURCE_PACK, resourcePackStatusHandler()); cancelClientbound(ClientboundConfigurationPackets1_20_3.RESOURCE_PACK_POP); registerServerbound(ServerboundConfigurationPackets1_20_2.RESOURCE_PACK, resourcePackStatusHandler()); registerClientbound(ClientboundConfigurationPackets1_20_3.RESOURCE_PACK_PUSH, ClientboundConfigurationPackets1_20_2.RESOURCE_PACK, resourcePackHandler()); } private PacketHandler resourcePackStatusHandler() { return wrapper -> { final ResourcepackIDStorage storage = wrapper.user().get(ResourcepackIDStorage.class); wrapper.write(Types.UUID, storage != null ? storage.uuid() : UUID.randomUUID()); }; } private PacketHandler resourcePackHandler() { return wrapper -> { final UUID uuid = wrapper.read(Types.UUID); wrapper.user().put(new ResourcepackIDStorage(uuid)); wrapper.passthrough(Types.STRING); // Url wrapper.passthrough(Types.STRING); // Hash wrapper.passthrough(Types.BOOLEAN); // Required convertOptionalComponent(wrapper); }; } private void convertComponent(final PacketWrapper wrapper) { final Tag tag = wrapper.read(Types.TRUSTED_TAG); translatableRewriter.processTag(wrapper.user(), tag); wrapper.write(Types.COMPONENT, ComponentUtil.tagToJson(tag)); } private void convertOptionalComponent(final PacketWrapper wrapper) { final Tag tag = wrapper.read(Types.TRUSTED_OPTIONAL_TAG); translatableRewriter.processTag(wrapper.user(), tag); wrapper.write(Types.OPTIONAL_COMPONENT, ComponentUtil.tagToJson(tag)); } @Override public void init(final UserConnection connection) { connection.put(new SpawnPositionStorage()); addEntityTracker(connection, new EntityTrackerBase(connection, EntityTypes1_20_3.PLAYER)); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public BlockItemPacketRewriter1_20_3 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public EntityPacketRewriter1_20_3 getEntityRewriter() { return entityRewriter; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } @Override protected PacketTypesProvider createPacketTypesProvider() { return new SimplePacketTypesProvider<>( packetTypeMap(unmappedClientboundPacketType, ClientboundPackets1_20_3.class, ClientboundConfigurationPackets1_20_3.class), packetTypeMap(mappedClientboundPacketType, ClientboundPackets1_20_2.class, ClientboundConfigurationPackets1_20_2.class), packetTypeMap(mappedServerboundPacketType, ServerboundPackets1_20_3.class, ServerboundConfigurationPackets1_20_2.class), packetTypeMap(unmappedServerboundPacketType, ServerboundPackets1_20_2.class, ServerboundConfigurationPackets1_20_2.class) ); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_3to1_20_2/rewriter/BlockItemPacketRewriter1_20_3.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.rewriter; import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.Protocol1_20_3To1_20_2; import com.viaversion.viaversion.api.data.ParticleMappings; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundPacket1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundPackets1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.rewriter.RecipeRewriter1_20_3; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ServerboundPacket1_20_2; public final class BlockItemPacketRewriter1_20_3 extends BackwardsItemRewriter { public BlockItemPacketRewriter1_20_3(final Protocol1_20_3To1_20_2 protocol) { super(protocol, Types.ITEM1_20_2, Types.ITEM1_20_2_ARRAY); } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_20_3.LEVEL_PARTICLES, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Particle ID map(Types.BOOLEAN); // 1 - Long Distance map(Types.DOUBLE); // 2 - X map(Types.DOUBLE); // 3 - Y map(Types.DOUBLE); // 4 - Z map(Types.FLOAT); // 5 - Offset X map(Types.FLOAT); // 6 - Offset Y map(Types.FLOAT); // 7 - Offset Z map(Types.FLOAT); // 8 - Particle Data map(Types.INT); // 9 - Particle Count handler(wrapper -> { final int id = wrapper.get(Types.VAR_INT, 0); final ParticleMappings particleMappings = protocol.getMappingData().getParticleMappings(); if (id == particleMappings.id("vibration")) { final int positionSourceType = wrapper.read(Types.VAR_INT); if (positionSourceType == 0) { wrapper.write(Types.STRING, "minecraft:block"); } else if (positionSourceType == 1) { wrapper.write(Types.STRING, "minecraft:entity"); } else { protocol.getLogger().warning("Unknown position source type: " + positionSourceType); wrapper.cancel(); } } }); handler(protocol.getParticleRewriter().levelParticlesHandler1_13(Types.VAR_INT)); } }); new RecipeRewriter1_20_3<>(protocol) { @Override public void handleCraftingShaped(final PacketWrapper wrapper) { // Move width and height up final String group = wrapper.read(Types.STRING); final int craftingBookCategory = wrapper.read(Types.VAR_INT); final int width = wrapper.passthrough(Types.VAR_INT); final int height = wrapper.passthrough(Types.VAR_INT); wrapper.write(Types.STRING, group); wrapper.write(Types.VAR_INT, craftingBookCategory); final int ingredients = height * width; for (int i = 0; i < ingredients; i++) { handleIngredient(wrapper); } rewrite(wrapper.user(), wrapper.passthrough(itemType())); // Result wrapper.passthrough(Types.BOOLEAN); // Show notification } }.register(ClientboundPackets1_20_3.UPDATE_RECIPES); protocol.registerClientbound(ClientboundPackets1_20_3.EXPLODE, wrapper -> { wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z wrapper.passthrough(Types.FLOAT); // Power final int blocks = wrapper.read(Types.VAR_INT); final byte[][] toBlow = new byte[blocks][3]; for (int i = 0; i < blocks; i++) { toBlow[i] = new byte[]{ wrapper.read(Types.BYTE), // Relative X wrapper.read(Types.BYTE), // Relative Y wrapper.read(Types.BYTE) // Relative Z }; } final float knockbackX = wrapper.read(Types.FLOAT); // Knockback X final float knockbackY = wrapper.read(Types.FLOAT); // Knockback Y final float knockbackZ = wrapper.read(Types.FLOAT); // Knockback Z final int blockInteraction = wrapper.read(Types.VAR_INT); // Block interaction type // 0 = keep, 1 = destroy, 2 = destroy_with_decay, 3 = trigger_block if (blockInteraction == 1 || blockInteraction == 2) { wrapper.write(Types.VAR_INT, blocks); for (final byte[] relativeXYZ : toBlow) { wrapper.write(Types.BYTE, relativeXYZ[0]); wrapper.write(Types.BYTE, relativeXYZ[1]); wrapper.write(Types.BYTE, relativeXYZ[2]); } } else { // Explosion doesn't destroy blocks wrapper.write(Types.VAR_INT, 0); } wrapper.write(Types.FLOAT, knockbackX); wrapper.write(Types.FLOAT, knockbackY); wrapper.write(Types.FLOAT, knockbackZ); // TODO Probably needs handling wrapper.read(Types1_20_3.PARTICLE); // Small explosion particle wrapper.read(Types1_20_3.PARTICLE); // Large explosion particle wrapper.read(Types.STRING); // Explosion sound wrapper.read(Types.OPTIONAL_FLOAT); // Sound range }); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_3to1_20_2/rewriter/BlockPacketRewriter1_20_3.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.Protocol1_20_3To1_20_2; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.blockentity.BlockEntity; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundPacket1_20_3; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.util.ComponentUtil; import com.viaversion.viaversion.util.SerializerVersion; import com.viaversion.viaversion.util.StringUtil; import java.util.logging.Level; public final class BlockPacketRewriter1_20_3 extends BlockRewriter { public BlockPacketRewriter1_20_3(final Protocol1_20_3To1_20_2 protocol) { super(protocol, Types.BLOCK_POSITION1_14, Types.COMPOUND_TAG, ChunkType1_20_2::new, null); } @Override public void handleBlockEntity(final UserConnection connection, final BlockEntity blockEntity) { final CompoundTag tag = blockEntity.tag(); if (tag == null) { return; } final StringTag customName = tag.getStringTag("CustomName"); if (customName == null) { return; } try { final JsonElement updatedComponent = ComponentUtil.convertJson(customName.getValue(), SerializerVersion.V1_20_3, SerializerVersion.V1_19_4); customName.setValue(updatedComponent.toString()); } catch (final Exception e) { if (Via.getConfig().logTextComponentConversionErrors()) { protocol.getLogger().log(Level.SEVERE, "Error during custom name conversion: " + StringUtil.forLogging(customName.getValue()), e); } } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_3to1_20_2/rewriter/EntityPacketRewriter1_20_3.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.rewriter; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.Protocol1_20_3To1_20_2; import com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.storage.SpawnPositionStorage; import com.viaversion.viaversion.api.data.ParticleMappings; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_20_3; import com.viaversion.viaversion.api.minecraft.entitydata.EntityDataType; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_20_2; import com.viaversion.viaversion.api.type.types.version.Types1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundConfigurationPackets1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundPacket1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundPackets1_20_3; import com.viaversion.viaversion.util.ComponentUtil; public final class EntityPacketRewriter1_20_3 extends EntityRewriter { public EntityPacketRewriter1_20_3(final Protocol1_20_3To1_20_2 protocol) { super(protocol, Types1_20_2.ENTITY_DATA_TYPES.optionalComponentType, Types1_20_2.ENTITY_DATA_TYPES.booleanType); } @Override public void registerPackets() { registerSetEntityData(ClientboundPackets1_20_3.SET_ENTITY_DATA, Types1_20_3.ENTITY_DATA_LIST, Types1_20_2.ENTITY_DATA_LIST); protocol.registerClientbound(ClientboundConfigurationPackets1_20_3.REGISTRY_DATA, new PacketHandlers() { @Override protected void register() { map(Types.COMPOUND_TAG); // Registry data handler(configurationDimensionDataHandler()); handler(configurationBiomeSizeTracker()); } }); protocol.registerClientbound(ClientboundPackets1_20_3.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // Entity id map(Types.BOOLEAN); // Hardcore map(Types.STRING_ARRAY); // World List map(Types.VAR_INT); // Max players map(Types.VAR_INT); // View distance map(Types.VAR_INT); // Simulation distance map(Types.BOOLEAN); // Reduced debug info map(Types.BOOLEAN); // Show death screen map(Types.BOOLEAN); // Limited crafting map(Types.STRING); // Dimension key map(Types.STRING); // World handler(spawnPositionHandler()); handler(worldDataTrackerHandlerByKey()); } }); protocol.registerClientbound(ClientboundPackets1_20_3.RESPAWN, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Dimension map(Types.STRING); // World handler(spawnPositionHandler()); handler(worldDataTrackerHandlerByKey()); } }); } private PacketHandler spawnPositionHandler() { return wrapper -> { final String world = wrapper.get(Types.STRING, 1); wrapper.user().get(SpawnPositionStorage.class).setDimension(world); }; } @Override protected void registerRewrites() { filter().handler((event, data) -> { final EntityDataType type = data.dataType(); if (type == Types1_20_3.ENTITY_DATA_TYPES.componentType) { data.setTypeAndValue(Types1_20_2.ENTITY_DATA_TYPES.componentType, ComponentUtil.tagToJson(data.value())); return; } else if (type == Types1_20_3.ENTITY_DATA_TYPES.optionalComponentType) { data.setTypeAndValue(Types1_20_2.ENTITY_DATA_TYPES.optionalComponentType, ComponentUtil.tagToJson(data.value())); return; } else if (type == Types1_20_3.ENTITY_DATA_TYPES.particleType) { final Particle particle = (Particle) data.getValue(); final ParticleMappings particleMappings = protocol.getMappingData().getParticleMappings(); if (particle.id() == particleMappings.id("vibration")) { // Change the type of the position source type argument final int positionSourceType = particle.removeArgument(0).getValue(); if (positionSourceType == 0) { particle.add(0, Types.STRING, "minecraft:block"); } else { // Entity particle.add(0, Types.STRING, "minecraft:entity"); } } } else if (type == Types1_20_3.ENTITY_DATA_TYPES.poseType) { final int pose = data.value(); if (pose >= 15) { event.cancel(); } } data.setDataType(Types1_20_2.ENTITY_DATA_TYPES.byId(type.typeId())); }); registerEntityDataTypeHandler( Types1_20_2.ENTITY_DATA_TYPES.itemType, Types1_20_2.ENTITY_DATA_TYPES.blockStateType, Types1_20_2.ENTITY_DATA_TYPES.optionalBlockStateType, Types1_20_2.ENTITY_DATA_TYPES.particleType, Types1_20_2.ENTITY_DATA_TYPES.componentType, Types1_20_2.ENTITY_DATA_TYPES.optionalComponentType ); registerBlockStateHandler(EntityTypes1_20_3.ABSTRACT_MINECART, 11); filter().type(EntityTypes1_20_3.TNT).removeIndex(9); // Block state } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); mapEntityTypeWithData(EntityTypes1_20_3.BREEZE, EntityTypes1_20_3.BLAZE).jsonName(); mapEntityTypeWithData(EntityTypes1_20_3.WIND_CHARGE, EntityTypes1_20_3.SHULKER_BULLET).jsonName(); } @Override public EntityType typeFromId(final int type) { return EntityTypes1_20_3.getTypeFromId(type); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_3to1_20_2/storage/ResourcepackIDStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.storage; import com.viaversion.viaversion.api.connection.StorableObject; import java.util.UUID; public record ResourcepackIDStorage(UUID uuid) implements StorableObject { } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_3to1_20_2/storage/SpawnPositionStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_3to1_20_2.storage; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.libs.fastutil.Pair; import java.util.Objects; public class SpawnPositionStorage implements StorableObject { public static final Pair DEFAULT_SPAWN_POSITION = Pair.of(new BlockPosition(8, 64, 8), 0.0F); // Default values copied from the original client private Pair spawnPosition; private String dimension; public Pair getSpawnPosition() { return spawnPosition; } public void setSpawnPosition(final Pair spawnPosition) { this.spawnPosition = spawnPosition; } /** * Sets the dimension and resets the spawn position to the default value if the dimension changed. * * @param dimension The new dimension */ public void setDimension(final String dimension) { final boolean changed = !Objects.equals(this.dimension, dimension); this.dimension = dimension; if (changed) { this.spawnPosition = DEFAULT_SPAWN_POSITION; } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_5to1_20_3/Protocol1_20_5To1_20_3.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_5to1_20_3; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.provider.TransferProvider; import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.rewriter.BlockItemPacketRewriter1_20_5; import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.rewriter.BlockPacketRewriter1_20_5; import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.rewriter.ComponentRewriter1_20_5; import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.rewriter.EntityPacketRewriter1_20_5; import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.storage.CookieStorage; import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.storage.RegistryDataStorage; import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.storage.SecureChatStorage; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_20_5; import com.viaversion.viaversion.api.platform.providers.ViaProviders; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.packet.ServerboundPacketType; import com.viaversion.viaversion.api.protocol.packet.State; 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; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.api.type.types.version.VersionedTypesHolder; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets; import com.viaversion.viaversion.protocols.base.ServerboundLoginPackets; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.rewriter.CommandRewriter1_19_4; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundConfigurationPackets1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundPacket1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundPackets1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ServerboundPacket1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ServerboundPackets1_20_3; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.Protocol1_20_3To1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ClientboundConfigurationPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ClientboundPacket1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ClientboundPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundConfigurationPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPacket1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.storage.ArmorTrimStorage; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.storage.BannerPatternStorage; import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ServerboundConfigurationPackets1_20_2; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap; public final class Protocol1_20_5To1_20_3 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.20.5", "1.20.3", Protocol1_20_3To1_20_5.class); private final EntityPacketRewriter1_20_5 entityRewriter = new EntityPacketRewriter1_20_5(this); private final BlockItemPacketRewriter1_20_5 itemRewriter = new BlockItemPacketRewriter1_20_5(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final JsonNBTComponentRewriter translatableRewriter = new ComponentRewriter1_20_5(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BlockPacketRewriter1_20_5 blockRewriter = new BlockPacketRewriter1_20_5(this); public Protocol1_20_5To1_20_3() { super(ClientboundPacket1_20_5.class, ClientboundPacket1_20_3.class, ServerboundPacket1_20_5.class, ServerboundPacket1_20_3.class); } @Override protected void registerPackets() { super.registerPackets(); tagRewriter.addEmptyTag(RegistryType.ITEM, "minecraft:axolotl_tempt_items"); replaceClientbound(ClientboundConfigurationPackets1_20_5.UPDATE_TAGS, wrapper -> { // Send off registry data first, needed for tags sendRegistryData(wrapper.user()); tagRewriter.handleGeneric(wrapper); }); registerClientbound(ClientboundConfigurationPackets1_20_5.FINISH_CONFIGURATION, wrapper -> { // In case the server for some reason does not send tags sendRegistryData(wrapper.user()); }); registerClientbound(ClientboundPackets1_20_5.START_CONFIGURATION, wrapper -> wrapper.user().get(RegistryDataStorage.class).clear()); registerClientbound(State.LOGIN, ClientboundLoginPackets.HELLO, wrapper -> { wrapper.passthrough(Types.STRING); // Server ID wrapper.passthrough(Types.BYTE_ARRAY_PRIMITIVE); // Public key wrapper.passthrough(Types.BYTE_ARRAY_PRIMITIVE); // Challenge wrapper.read(Types.BOOLEAN); // Authenticate }); replaceClientbound(ClientboundPackets1_20_5.SERVER_DATA, wrapper -> { translatableRewriter.passthroughAndProcess(wrapper); // MOTD wrapper.passthrough(Types.OPTIONAL_BYTE_ARRAY_PRIMITIVE); // Icon wrapper.write(Types.BOOLEAN, wrapper.user().get(SecureChatStorage.class).enforcesSecureChat()); }); // Always write as signed, even if there is 0 signatures attached, else the validation chain gets broken registerServerbound(ServerboundPackets1_20_3.CHAT_COMMAND, ServerboundPackets1_20_5.CHAT_COMMAND_SIGNED); registerClientbound(State.LOGIN, ClientboundLoginPackets.COOKIE_REQUEST.getId(), -1, wrapper -> handleCookieRequest(wrapper, ServerboundLoginPackets.COOKIE_RESPONSE)); cancelClientbound(ClientboundConfigurationPackets1_20_5.RESET_CHAT); // Old clients already reset chat when entering the configuration phase registerClientbound(ClientboundConfigurationPackets1_20_5.COOKIE_REQUEST, null, wrapper -> handleCookieRequest(wrapper, ServerboundConfigurationPackets1_20_5.COOKIE_RESPONSE)); registerClientbound(ClientboundConfigurationPackets1_20_5.STORE_COOKIE, null, this::handleStoreCookie); registerClientbound(ClientboundConfigurationPackets1_20_5.TRANSFER, null, this::handleTransfer); registerClientbound(ClientboundPackets1_20_5.COOKIE_REQUEST, null, wrapper -> handleCookieRequest(wrapper, ServerboundPackets1_20_5.COOKIE_RESPONSE)); registerClientbound(ClientboundPackets1_20_5.STORE_COOKIE, null, this::handleStoreCookie); registerClientbound(ClientboundPackets1_20_5.TRANSFER, null, this::handleTransfer); registerClientbound(ClientboundConfigurationPackets1_20_5.SELECT_KNOWN_PACKS, null, wrapper -> { wrapper.cancel(); final PacketWrapper response = wrapper.create(ServerboundConfigurationPackets1_20_5.SELECT_KNOWN_PACKS); response.write(Types.VAR_INT, 0); // Empty, we don't know anything response.sendToServer(Protocol1_20_5To1_20_3.class); }); final CommandRewriter1_19_4 commandRewriter = new CommandRewriter1_19_4<>(this) { @Override public void handleArgument(final PacketWrapper wrapper, final String argumentType) { if (argumentType.equals("minecraft:loot_table") || argumentType.equals("minecraft:loot_predicate") || argumentType.equals("minecraft:loot_modifier")) { wrapper.write(Types.VAR_INT, 0); } else { super.handleArgument(wrapper, argumentType); } } }; replaceClientbound(ClientboundPackets1_20_5.COMMANDS, commandRewriter::handle1_19); registerClientbound(State.LOGIN, ClientboundLoginPackets.LOGIN_FINISHED, wrapper -> { wrapper.passthrough(Types.UUID); // UUID wrapper.passthrough(Types.STRING); // Name wrapper.passthrough(Types.PROFILE_PROPERTY_ARRAY); wrapper.read(Types.BOOLEAN); // Strict error handling }); cancelClientbound(ClientboundPackets1_20_5.PROJECTILE_POWER); cancelClientbound(ClientboundPackets1_20_5.DEBUG_SAMPLE); } private void sendRegistryData(final UserConnection connection) { final RegistryDataStorage registryDataStorage = connection.get(RegistryDataStorage.class); final CompoundTag registryData = registryDataStorage.registryData(); if (!registryDataStorage.sentRegistryData() && !registryData.isEmpty()) { final PacketWrapper registryDataPacket = PacketWrapper.create(ClientboundConfigurationPackets1_20_3.REGISTRY_DATA, connection); registryDataPacket.write(Types.COMPOUND_TAG, registryData.copy()); registryDataPacket.send(Protocol1_20_5To1_20_3.class); registryDataStorage.setSentRegistryData(); } } private void handleStoreCookie(final PacketWrapper wrapper) { wrapper.cancel(); final String resourceLocation = wrapper.read(Types.STRING); final byte[] data = wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); if (data.length > 5120) { throw new IllegalArgumentException("Cookie data too large"); } wrapper.user().get(CookieStorage.class).cookies().put(resourceLocation, data); } private void handleCookieRequest(final PacketWrapper wrapper, final ServerboundPacketType responseType) { wrapper.cancel(); final String resourceLocation = wrapper.read(Types.STRING); final byte[] data = wrapper.user().get(CookieStorage.class).cookies().get(resourceLocation); final PacketWrapper responsePacket = wrapper.create(responseType); responsePacket.write(Types.STRING, resourceLocation); responsePacket.write(Types.OPTIONAL_BYTE_ARRAY_PRIMITIVE, data); responsePacket.sendToServer(Protocol1_20_5To1_20_3.class); } private void handleTransfer(final PacketWrapper wrapper) { wrapper.cancel(); final String host = wrapper.read(Types.STRING); final int port = wrapper.read(Types.VAR_INT); Via.getManager().getProviders().get(TransferProvider.class).connectToServer(wrapper.user(), host, port); } @Override public void init(final UserConnection user) { addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_20_5.PLAYER)); user.put(new SecureChatStorage()); user.put(new CookieStorage()); user.put(new RegistryDataStorage()); user.put(new BannerPatternStorage()); user.put(new ArmorTrimStorage()); } @Override public void register(final ViaProviders providers) { providers.register(TransferProvider.class, TransferProvider.NOOP); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_20_5 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_20_5 getItemRewriter() { return itemRewriter; } @Override public BlockPacketRewriter1_20_5 getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } @Override public VersionedTypesHolder types() { return VersionedTypes.V1_20_5; } @Override public VersionedTypesHolder mappedTypes() { return new Types1_20_3(); } @Override protected PacketTypesProvider createPacketTypesProvider() { return new SimplePacketTypesProvider<>( packetTypeMap(unmappedClientboundPacketType, ClientboundPackets1_20_5.class, ClientboundConfigurationPackets1_20_5.class), packetTypeMap(mappedClientboundPacketType, ClientboundPackets1_20_3.class, ClientboundConfigurationPackets1_20_3.class), packetTypeMap(mappedServerboundPacketType, ServerboundPackets1_20_5.class, ServerboundConfigurationPackets1_20_5.class), packetTypeMap(unmappedServerboundPacketType, ServerboundPackets1_20_3.class, ServerboundConfigurationPackets1_20_2.class) ); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_5to1_20_3/Types1_20_3.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_5to1_20_3; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.data.StructuredData; import com.viaversion.viaversion.api.minecraft.data.version.VersionedStructuredDataKeys; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.entitydata.types.AbstractEntityDataTypes; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.ArrayType; import com.viaversion.viaversion.api.type.types.item.StructuredDataType; import com.viaversion.viaversion.api.type.types.misc.ParticleType; import com.viaversion.viaversion.api.type.types.version.VersionedTypesHolder; import java.util.List; final class Types1_20_3 implements VersionedTypesHolder { @Override public Type item() { return Types.ITEM1_20_2; } @Override public Type itemArray() { return Types.ITEM1_20_2_ARRAY; } @Override public Type itemTemplate() { return item(); } @Override public Type optionalItemTemplate() { return item(); } @Override public Type itemTemplateArray() { return itemArray(); } @Override public Type itemCost() { return null; } @Override public Type optionalItemCost() { return null; } @Override public Type lengthPrefixedItem() { return null; } @Override public StructuredDataType structuredData() { return null; } @Override public Type[]> structuredDataArray() { return null; } @Override public VersionedStructuredDataKeys structuredDataKeys() { return null; } @Override public ParticleType particle() { return com.viaversion.viaversion.api.type.types.version.Types1_20_3.PARTICLE; } @Override public ArrayType particles() { return null; } @Override public AbstractEntityDataTypes entityDataTypes() { return com.viaversion.viaversion.api.type.types.version.Types1_20_3.ENTITY_DATA_TYPES; } @Override public Type> entityDataList() { return com.viaversion.viaversion.api.type.types.version.Types1_20_3.ENTITY_DATA_LIST; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_5to1_20_3/provider/NoopTransferProvider.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.provider; import com.viaversion.viaversion.api.connection.UserConnection; final class NoopTransferProvider implements TransferProvider { @Override public void connectToServer(final UserConnection connection, final String host, final int port) { } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_5to1_20_3/provider/TransferProvider.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.provider; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.platform.providers.Provider; @FunctionalInterface public interface TransferProvider extends Provider { TransferProvider NOOP = new NoopTransferProvider(); void connectToServer(UserConnection connection, String host, int port); } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_5to1_20_3/rewriter/BlockItemPacketRewriter1_20_5.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.rewriters.BackwardsStructuredItemRewriter; import com.viaversion.viabackwards.api.rewriters.StructuredEnchantmentRewriter; import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.Protocol1_20_5To1_20_3; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.item.ItemHasher; import com.viaversion.viaversion.api.minecraft.Holder; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.SoundEvent; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.item.StructuredItem; import com.viaversion.viaversion.api.minecraft.item.data.FireworkExplosion; import com.viaversion.viaversion.api.minecraft.item.data.Fireworks; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_20_3; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ServerboundPacket1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ServerboundPackets1_20_3; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.rewriter.RecipeRewriter1_20_3; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.Protocol1_20_3To1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ClientboundPacket1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ClientboundPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.rewriter.StructuredDataConverter; import com.viaversion.viaversion.util.Key; import org.checkerframework.checker.nullness.qual.Nullable; public final class BlockItemPacketRewriter1_20_5 extends BackwardsStructuredItemRewriter { private static final StructuredDataConverter DATA_CONVERTER = new StructuredDataConverter(true); private final Protocol1_20_3To1_20_5 vvProtocol = Via.getManager().getProtocolManager().getProtocol(Protocol1_20_3To1_20_5.class); private final StructuredEnchantmentRewriter enchantmentRewriter = new StructuredEnchantmentRewriter(this); public BlockItemPacketRewriter1_20_5(final Protocol1_20_5To1_20_3 protocol) { super(protocol); enchantmentRewriter.setRewriteIds(false); // Let VV handle it } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_20_5.BLOCK_ENTITY_DATA, wrapper -> { wrapper.passthrough(Types.BLOCK_POSITION1_14); // Position wrapper.passthrough(Types.VAR_INT); // Block entity type final CompoundTag tag = wrapper.passthrough(Types.TRUSTED_COMPOUND_TAG); protocol.getBlockRewriter().updateBlockEntityTag(tag); }); protocol.registerServerbound(ServerboundPackets1_20_3.CONTAINER_BUTTON_CLICK, wrapper -> { final int containerId = wrapper.read(Types.BYTE) & 0xFF; final int buttonId = wrapper.read(Types.BYTE) & 0xFF; wrapper.write(Types.VAR_INT, containerId); wrapper.write(Types.VAR_INT, buttonId); }); protocol.replaceClientbound(ClientboundPackets1_20_5.LEVEL_PARTICLES, wrapper -> { wrapper.write(Types.VAR_INT, 0); // Write dummy value, set later wrapper.passthrough(Types.BOOLEAN); // Long Distance wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z wrapper.passthrough(Types.FLOAT); // Offset X wrapper.passthrough(Types.FLOAT); // Offset Y wrapper.passthrough(Types.FLOAT); // Offset Z final float data = wrapper.passthrough(Types.FLOAT); wrapper.passthrough(Types.INT); // Particle Count // Move it to the beginning, move out arguments here final Particle particle = wrapper.read(VersionedTypes.V1_20_5.particle()); protocol.getParticleRewriter().rewriteParticle(wrapper.user(), particle); if (particle.id() == protocol.getMappingData().getParticleMappings().mappedId("entity_effect")) { // Remove color argument final int color = particle.removeArgument(0).getValue(); if (data == 0) { wrapper.set(Types.FLOAT, 3, (float) color); } } else if (particle.id() == protocol.getMappingData().getParticleMappings().mappedId("dust_color_transition")) { // fromColor, toColor, scale -> fromColor, scale, toColor particle.add(3, Types.FLOAT, particle.removeArgument(6).getValue()); } wrapper.set(Types.VAR_INT, 0, particle.id()); for (final Particle.ParticleData argument : particle.getArguments()) { argument.write(wrapper); } }); protocol.replaceClientbound(ClientboundPackets1_20_5.EXPLODE, wrapper -> { wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z wrapper.passthrough(Types.FLOAT); // Power final int blocks = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < blocks; i++) { wrapper.passthrough(Types.BYTE); // Relative X wrapper.passthrough(Types.BYTE); // Relative Y wrapper.passthrough(Types.BYTE); // Relative Z } wrapper.passthrough(Types.FLOAT); // Knockback X wrapper.passthrough(Types.FLOAT); // Knockback Y wrapper.passthrough(Types.FLOAT); // Knockback Z wrapper.passthrough(Types.VAR_INT); // Block interaction type final Particle smallExplosionParticle = wrapper.passthroughAndMap(VersionedTypes.V1_20_5.particle(), Types1_20_3.PARTICLE); final Particle largeExplosionParticle = wrapper.passthroughAndMap(VersionedTypes.V1_20_5.particle(), Types1_20_3.PARTICLE); protocol.getParticleRewriter().rewriteParticle(wrapper.user(), smallExplosionParticle); protocol.getParticleRewriter().rewriteParticle(wrapper.user(), largeExplosionParticle); final Holder soundEventHolder = wrapper.read(Types.SOUND_EVENT); if (soundEventHolder.isDirect()) { final SoundEvent soundEvent = soundEventHolder.value(); wrapper.write(Types.STRING, soundEvent.identifier()); wrapper.write(Types.OPTIONAL_FLOAT, soundEvent.fixedRange()); } else { final int soundId = protocol.getMappingData().getSoundMappings().getNewId(soundEventHolder.id()); final String soundKey = Protocol1_20_3To1_20_5.MAPPINGS.soundName(soundId); wrapper.write(Types.STRING, soundKey != null ? soundKey : "minecraft:entity.generic.explode"); wrapper.write(Types.OPTIONAL_FLOAT, null); // Fixed range } }); protocol.replaceClientbound(ClientboundPackets1_20_5.MERCHANT_OFFERS, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Container id final int size = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < size; i++) { final Item input = handleItemToClient(wrapper.user(), wrapper.read(VersionedTypes.V1_20_5.itemCost())); cleanInput(input); wrapper.write(Types.ITEM1_20_2, input); final Item result = handleItemToClient(wrapper.user(), wrapper.read(VersionedTypes.V1_20_5.item())); wrapper.write(Types.ITEM1_20_2, result); Item secondInput = wrapper.read(VersionedTypes.V1_20_5.optionalItemCost()); if (secondInput != null) { secondInput = handleItemToClient(wrapper.user(), secondInput); cleanInput(secondInput); } wrapper.write(Types.ITEM1_20_2, secondInput); wrapper.passthrough(Types.BOOLEAN); // Out of stock wrapper.passthrough(Types.INT); // Number of trade uses wrapper.passthrough(Types.INT); // Maximum number of trade uses wrapper.passthrough(Types.INT); // XP wrapper.passthrough(Types.INT); // Special price wrapper.passthrough(Types.FLOAT); // Price multiplier wrapper.passthrough(Types.INT); // Demand } }); protocol.registerClientbound(ClientboundPackets1_20_5.MAP_ITEM_DATA, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Map id wrapper.passthrough(Types.BYTE); // Scale wrapper.passthrough(Types.BOOLEAN); // Locked if (wrapper.passthrough(Types.BOOLEAN)) { final int icons = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < icons; i++) { final int decorationType = wrapper.read(Types.VAR_INT); wrapper.write(Types.VAR_INT, decorationType == 34 ? 32 : decorationType); // Trial champer to jungle temple wrapper.passthrough(Types.BYTE); // X wrapper.passthrough(Types.BYTE); // Y wrapper.passthrough(Types.BYTE); // Rotation wrapper.passthrough(Types.TRUSTED_OPTIONAL_TAG); // Display name } } }); final RecipeRewriter1_20_3 recipeRewriter = new RecipeRewriter1_20_3<>(protocol); protocol.registerClientbound(ClientboundPackets1_20_5.UPDATE_RECIPES, wrapper -> { final int size = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < size; i++) { // Change order and write the type as an int final String recipeIdentifier = wrapper.read(Types.STRING); final int serializerTypeId = wrapper.read(Types.VAR_INT); final String serializerType = protocol.getMappingData().getRecipeSerializerMappings().mappedIdentifier(serializerTypeId); wrapper.write(Types.STRING, serializerType); wrapper.write(Types.STRING, recipeIdentifier); recipeRewriter.handleRecipeType(wrapper, Key.stripMinecraftNamespace(serializerType)); } }); } private void cleanInput(@Nullable final Item item) { // Try to maybe hopefully get the tag matching to what the client will try to input by removing default data if (item == null || item.tag() == null) { return; } final CompoundTag tag = item.tag(); StructuredDataConverter.removeBackupTag(tag); final CompoundTag display = tag.getCompoundTag("display"); if (display != null) { removeEmptyList(display, "Lore"); if (display.isEmpty()) { tag.remove("display"); } } removeEmptyList(tag, "Enchantments"); removeEmptyList(tag, "AttributeModifiers"); if (tag.getInt("RepairCost", -1) == 0) { tag.remove("RepairCost"); } if (tag.isEmpty()) { item.setTag(null); } } private void removeEmptyList(final CompoundTag tag, final String key) { final ListTag list = tag.getListTag(key); if (list != null && list.isEmpty()) { tag.remove(key); } } @Override public @Nullable Item handleItemToClient(final UserConnection connection, Item item) { if (item.isEmpty()) { // Back to null for the older protocols return null; } final StructuredDataContainer data = item.dataContainer(); item.dataContainer().setIdLookup(protocol, true); enchantmentRewriter.handleToClient(item); item = super.handleItemToClient(connection, item); // Text components since we skip the usual rewrite method updateTextComponent(connection, item, StructuredDataKey.ITEM_NAME, "item_name"); updateTextComponent(connection, item, StructuredDataKey.CUSTOM_NAME, "custom_name"); final Tag[] lore = data.get(StructuredDataKey.LORE); if (lore != null) { for (final Tag tag : lore) { protocol.getComponentRewriter().processTag(connection, tag); } } // In 1.20.6, some items have default values which are not written into the components if (item.identifier() == 1105 && !data.has(StructuredDataKey.FIREWORKS)) { data.set(StructuredDataKey.FIREWORKS, new Fireworks(1, new FireworkExplosion[0])); } CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA); if (customData != null) { // Copy original custom data before changes customData = customData.copy(); } final Item oldItem = vvProtocol.getItemRewriter().toOldItem(connection, item, DATA_CONVERTER); if (customData != null) { // We later don't know which tags are custom data and which are not because the VV conversion // keeps converted data, so we backup the original custom data and restore it later if (oldItem.tag() == null) { oldItem.setTag(new CompoundTag()); } oldItem.tag().put(nbtTagName(), customData); } else if (oldItem.tag() != null && oldItem.tag().isEmpty()) { // Improve item equality checks by removing empty tags oldItem.setTag(null); } return oldItem; } // Items and data within components are handled in this protocol @Override protected void handleItemDataComponentsToClient(final UserConnection connection, final Item item, final StructuredDataContainer container) { } @Override protected void handleItemDataComponentsToServer(final UserConnection connection, final Item item, final StructuredDataContainer container) { } @Override protected void handleRewritablesToClient(final UserConnection connection, final StructuredDataContainer container, @Nullable final ItemHasher itemHasher) { } @Override protected void handleRewritablesToServer(final UserConnection connection, final StructuredDataContainer container) { } @Override public Item handleItemToServer(final UserConnection connection, @Nullable final Item item) { if (item == null) { // Unify as empty going forward return StructuredItem.empty(); } // Convert to structured item first final Item structuredItem = vvProtocol.getItemRewriter().toStructuredItem(connection, item); if (item.tag() != null && item.tag().get(nbtTagName()) instanceof final CompoundTag tag) { // Set original custom data from backup structuredItem.dataContainer().set(StructuredDataKey.CUSTOM_DATA, tag); } structuredItem.dataContainer().setIdLookup(protocol, false); enchantmentRewriter.handleToServer(structuredItem); return super.handleItemToServer(connection, structuredItem); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_5to1_20_3/rewriter/BlockPacketRewriter1_20_5.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntArrayTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.Protocol1_20_5To1_20_3; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.blockentity.BlockEntity; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.data.BannerPatterns1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ClientboundPacket1_20_5; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.util.Key; import org.checkerframework.checker.nullness.qual.Nullable; public final class BlockPacketRewriter1_20_5 extends BlockRewriter { public BlockPacketRewriter1_20_5(final Protocol1_20_5To1_20_3 protocol) { super(protocol, Types.BLOCK_POSITION1_14, Types.COMPOUND_TAG, ChunkType1_20_2::new, null); } @Override public void handleBlockEntity(final UserConnection connection, final BlockEntity blockEntity) { updateBlockEntityTag(blockEntity.tag()); } public void updateBlockEntityTag(@Nullable final CompoundTag tag) { if (tag == null) { return; } final Tag profileTag = tag.remove("profile"); if (profileTag instanceof StringTag) { tag.put("SkullOwner", profileTag); } else if (profileTag instanceof CompoundTag) { updateProfileTag(tag, (CompoundTag) profileTag); } final ListTag patternsTag = tag.getListTag("patterns", CompoundTag.class); if (patternsTag != null) { for (final CompoundTag patternTag : patternsTag) { final String pattern = patternTag.getString("pattern", ""); final String color = patternTag.getString("color"); final String compactIdentifier = BannerPatterns1_20_5.fullIdToCompact(Key.stripMinecraftNamespace(pattern)); if (compactIdentifier == null || color == null) { continue; } patternTag.remove("pattern"); patternTag.remove("color"); patternTag.putString("Pattern", compactIdentifier); patternTag.putInt("Color", colorId(color)); } tag.remove("patterns"); tag.put("Patterns", patternsTag); } } private void updateProfileTag(final CompoundTag tag, final CompoundTag profileTag) { final CompoundTag skullOwnerTag = new CompoundTag(); tag.put("SkullOwner", skullOwnerTag); final String name = profileTag.getString("name"); if (name != null) { skullOwnerTag.putString("Name", name); } final IntArrayTag idTag = profileTag.getIntArrayTag("id"); if (idTag != null) { skullOwnerTag.put("Id", idTag); } final ListTag propertiesListTag = profileTag.getListTag("properties", CompoundTag.class); if (propertiesListTag == null) { return; } final CompoundTag propertiesTag = new CompoundTag(); for (final CompoundTag propertyTag : propertiesListTag) { final String property = propertyTag.getString("name", ""); final String value = propertyTag.getString("value", ""); final String signature = propertyTag.getString("signature"); final ListTag list = new ListTag<>(CompoundTag.class); final CompoundTag updatedPropertyTag = new CompoundTag(); updatedPropertyTag.putString("Value", value); if (signature != null) { updatedPropertyTag.putString("Signature", signature); } list.add(updatedPropertyTag); propertiesTag.put(property, list); } skullOwnerTag.put("Properties", propertiesTag); } private static int colorId(final String color) { return switch (color) { case "orange" -> 1; case "magenta" -> 2; case "light_blue" -> 3; case "yellow" -> 4; case "lime" -> 5; case "pink" -> 6; case "gray" -> 7; case "light_gray" -> 8; case "cyan" -> 9; case "purple" -> 10; case "blue" -> 11; case "brown" -> 12; case "green" -> 13; case "red" -> 14; case "black" -> 15; default -> 0; }; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_5to1_20_3/rewriter/ComponentRewriter1_20_5.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.data.StructuredData; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.item.StructuredItem; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ClientboundPacket1_20_5; import com.viaversion.viaversion.util.ComponentUtil; import com.viaversion.viaversion.util.SerializerVersion; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; public final class ComponentRewriter1_20_5 extends JsonNBTComponentRewriter { private final com.viaversion.viaversion.protocols.v1_20_3to1_20_5.rewriter.ComponentRewriter1_20_5 vvRewriter; public ComponentRewriter1_20_5(final BackwardsProtocol protocol) { super(protocol, ReadType.NBT); vvRewriter = new com.viaversion.viaversion.protocols.v1_20_3to1_20_5.rewriter.ComponentRewriter1_20_5<>(protocol, VersionedTypes.V1_20_5.structuredData()); } @Override protected void handleShowItem(final UserConnection connection, final CompoundTag itemTag, @Nullable final CompoundTag componentsTag) { super.handleShowItem(connection, itemTag, componentsTag); if (componentsTag == null) { return; } final StringTag idTag = itemTag.getStringTag("id"); if (idTag == null) { return; } final List> data = vvRewriter.toData(connection, componentsTag); if (data.isEmpty()) { return; } final int identifier = this.protocol.getMappingData().getFullItemMappings().id(idTag.getValue()); final StructuredItem structuredItem = new StructuredItem(identifier, 1, new StructuredDataContainer(data.toArray(StructuredData[]::new))); final Item dataItem = protocol.getItemRewriter().handleItemToClient(connection, structuredItem); if (dataItem.tag() == null) { return; } itemTag.remove("components"); final StringTag tag = new StringTag(outputSerializerVersion().toSNBT(dataItem.tag())); itemTag.put("tag", ComponentUtil.trimStrings(tag)); } @Override protected SerializerVersion inputSerializerVersion() { return SerializerVersion.V1_20_5; } @Override protected SerializerVersion outputSerializerVersion() { return SerializerVersion.V1_20_3; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_5to1_20_3/rewriter/EntityPacketRewriter1_20_5.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.rewriter; import com.google.common.base.Preconditions; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.FloatTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.Protocol1_20_5To1_20_3; import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.storage.RegistryDataStorage; import com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.storage.SecureChatStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.entity.DimensionData; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.RegistryEntry; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_20_5; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_20_3; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.data.entity.DimensionDataImpl; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundPackets1_20_3; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.Protocol1_20_3To1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.data.Attributes1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ClientboundConfigurationPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ClientboundPacket1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ClientboundPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.storage.ArmorTrimStorage; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.storage.BannerPatternStorage; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.KeyMappings; import com.viaversion.viaversion.util.MathUtil; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public final class EntityPacketRewriter1_20_5 extends EntityRewriter { public EntityPacketRewriter1_20_5(final Protocol1_20_5To1_20_3 protocol) { super(protocol, Types1_20_3.ENTITY_DATA_TYPES.optionalComponentType, Types1_20_3.ENTITY_DATA_TYPES.booleanType); } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_20_5.SET_EQUIPMENT, wrapper -> { final int entityId = wrapper.passthrough(Types.VAR_INT); // Entity id final EntityType type = tracker(wrapper.user()).entityType(entityId); byte slot; do { slot = wrapper.read(Types.BYTE); final Item item = protocol.getItemRewriter().handleItemToClient(wrapper.user(), wrapper.read(VersionedTypes.V1_20_5.item())); final int rawSlot = slot & 0x7F; if (rawSlot == 6) { final boolean lastSlot = (slot & 0xFFFFFF80) == 0; slot = (byte) (lastSlot ? 4 : 4 | 0xFFFFFF80); // Map body slot index to chest slot index for horses, also wolves if (type != null && type.isOrHasParent(EntityTypes1_20_5.LLAMA)) { // Cancel equipment and set correct entity data instead wrapper.cancel(); sendCarpetColorUpdate(wrapper.user(), entityId, item); } } wrapper.write(Types.BYTE, slot); wrapper.write(Types.ITEM1_20_2, item); } while ((slot & 0xFFFFFF80) != 0); }); protocol.registerClientbound(ClientboundPackets1_20_5.HORSE_SCREEN_OPEN, wrapper -> { wrapper.passthrough(Types.UNSIGNED_BYTE); // Container id // The body armor slot was moved to equipment final int size = wrapper.read(Types.VAR_INT); wrapper.write(Types.VAR_INT, size + 1); }); protocol.registerClientbound(ClientboundConfigurationPackets1_20_5.REGISTRY_DATA, wrapper -> { wrapper.cancel(); final String registryKey = Key.stripMinecraftNamespace(wrapper.read(Types.STRING)); if (registryKey.equals("wolf_variant")) { // There's only one wolf variant now return; } final RegistryDataStorage registryDataStorage = wrapper.user().get(RegistryDataStorage.class); final RegistryEntry[] entries = wrapper.read(Types.REGISTRY_ENTRY_ARRAY); // Track trim patterns and armor trims for conversion in items if (registryKey.equals("banner_pattern")) { // Don't send it wrapper.user().get(BannerPatternStorage.class).setBannerPatterns(toMappings(entries)); return; } final boolean isTrimPattern = registryKey.equals("trim_pattern"); if (isTrimPattern) { wrapper.user().get(ArmorTrimStorage.class).setTrimPatterns(toMappings(entries)); } else if (registryKey.equals("trim_material")) { wrapper.user().get(ArmorTrimStorage.class).setTrimMaterials(toMappings(entries)); } // Track biome and dimension data if (registryKey.equals("worldgen/biome")) { tracker(wrapper.user()).setBiomesSent(entries.length); // Update format of particles for (final RegistryEntry entry : entries) { if (entry.tag() == null) { continue; } final CompoundTag effects = ((CompoundTag) entry.tag()).getCompoundTag("effects"); final CompoundTag particle = effects.getCompoundTag("particle"); if (particle != null) { final CompoundTag particleOptions = particle.getCompoundTag("options"); final String particleType = particleOptions.getString("type"); updateParticleFormat(particleOptions, Key.stripMinecraftNamespace(particleType)); } } } else if (registryKey.equals("dimension_type")) { final Map dimensionDataMap = new HashMap<>(entries.length); final String[] keys = new String[entries.length]; for (int i = 0; i < entries.length; i++) { final RegistryEntry entry = entries[i]; Preconditions.checkNotNull(entry.tag(), "Server unexpectedly sent null dimension data for " + entry.key()); final String dimensionKey = Key.stripMinecraftNamespace(entry.key()); final CompoundTag tag = (CompoundTag) entry.tag(); updateDimensionTypeData(tag); dimensionDataMap.put(dimensionKey, new DimensionDataImpl(i, tag)); keys[i] = dimensionKey; } registryDataStorage.setDimensionKeys(keys); tracker(wrapper.user()).setDimensions(dimensionDataMap); } // Write to old format final CompoundTag registryTag = new CompoundTag(); final ListTag entriesTag = new ListTag<>(CompoundTag.class); registryTag.putString("type", registryKey); registryTag.put("value", entriesTag); for (int i = 0; i < entries.length; i++) { final RegistryEntry entry = entries[i]; Preconditions.checkNotNull(entry.tag(), "Server unexpectedly sent null registry data entry for " + entry.key()); if (isTrimPattern) { final CompoundTag patternTag = (CompoundTag) entry.tag(); final StringTag templateItem = patternTag.getStringTag("template_item"); if (Protocol1_20_3To1_20_5.MAPPINGS.getFullItemMappings().id(templateItem.getValue()) == -1) { // Skip new items continue; } } final CompoundTag entryCompoundTag = new CompoundTag(); entryCompoundTag.putString("name", entry.key()); entryCompoundTag.putInt("id", i); entryCompoundTag.put("element", entry.tag()); entriesTag.add(entryCompoundTag); } // Store and send together with the rest later registryDataStorage.registryData().put(registryKey, registryTag); }); protocol.replaceClientbound(ClientboundPackets1_20_5.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // Entity id map(Types.BOOLEAN); // Hardcore map(Types.STRING_ARRAY); // World List map(Types.VAR_INT); // Max players map(Types.VAR_INT); // View distance map(Types.VAR_INT); // Simulation distance map(Types.BOOLEAN); // Reduced debug info map(Types.BOOLEAN); // Show death screen map(Types.BOOLEAN); // Limited crafting handler(wrapper -> { final int dimensionId = wrapper.read(Types.VAR_INT); final RegistryDataStorage storage = wrapper.user().get(RegistryDataStorage.class); wrapper.write(Types.STRING, storage.dimensionKeys()[dimensionId]); }); map(Types.STRING); // World map(Types.LONG); // Seed map(Types.BYTE); // Gamemode map(Types.BYTE); // Previous gamemode map(Types.BOOLEAN); // Debug map(Types.BOOLEAN); // Flat map(Types.OPTIONAL_GLOBAL_POSITION); // Last death location map(Types.VAR_INT); // Portal cooldown handler(wrapper -> { // Moved to server data final boolean enforcesSecureChat = wrapper.read(Types.BOOLEAN); wrapper.user().get(SecureChatStorage.class).setEnforcesSecureChat(enforcesSecureChat); }); handler(worldDataTrackerHandlerByKey()); // Tracks world height and name for chunk data and entity (un)tracking handler(playerTrackerHandler()); } }); protocol.replaceClientbound(ClientboundPackets1_20_5.RESPAWN, new PacketHandlers() { @Override public void register() { handler(wrapper -> { final int dimensionId = wrapper.read(Types.VAR_INT); final RegistryDataStorage storage = wrapper.user().get(RegistryDataStorage.class); wrapper.write(Types.STRING, storage.dimensionKeys()[dimensionId]); }); map(Types.STRING); // World handler(worldDataTrackerHandlerByKey()); // Tracks world height and name for chunk data and entity (un)tracking } }); protocol.registerClientbound(ClientboundPackets1_20_5.UPDATE_MOB_EFFECT, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Entity ID final int effectId = wrapper.passthrough(Types.VAR_INT); final int amplifier = wrapper.read(Types.VAR_INT); wrapper.write(Types.BYTE, (byte) MathUtil.clamp(amplifier, Byte.MIN_VALUE, Byte.MAX_VALUE)); wrapper.passthrough(Types.VAR_INT); // Duration wrapper.passthrough(Types.BYTE); // Flags if (effectId == 32) { // Darkness, keep a stable effect final CompoundTag factorData = new CompoundTag(); factorData.putInt("padding_duration", 22); factorData.putBoolean("had_effect_last_tick", true); factorData.putFloat("factor_previous_frame", 0); factorData.putFloat("factor_start", 1); factorData.putFloat("factor_target", 1); factorData.putFloat("factor_current", 1); wrapper.write(Types.OPTIONAL_COMPOUND_TAG, factorData); } else { wrapper.write(Types.OPTIONAL_COMPOUND_TAG, null); } }); protocol.registerClientbound(ClientboundPackets1_20_5.UPDATE_ATTRIBUTES, wrapper -> { final int entityId = wrapper.passthrough(Types.VAR_INT); final int size = wrapper.passthrough(Types.VAR_INT); int newSize = size; for (int i = 0; i < size; i++) { // From a registry int ID to a string final int attributeId = wrapper.read(Types.VAR_INT); final String attribute = Attributes1_20_5.idToKey(attributeId); int mappedId = protocol.getMappingData().getAttributeMappings().getNewId(attributeId); if ("generic.jump_strength".equals(attribute)) { final EntityType type = tracker(wrapper.user()).entityType(entityId); if (type == null || !type.isOrHasParent(EntityTypes1_20_5.HORSE)) { // Jump strength only applies to horses in old versions mappedId = -1; } } if (mappedId == -1) { // Remove new attributes from the list newSize--; wrapper.read(Types.DOUBLE); // Base final int modifierSize = wrapper.read(Types.VAR_INT); for (int j = 0; j < modifierSize; j++) { wrapper.read(Types.UUID); // ID wrapper.read(Types.DOUBLE); // Amount wrapper.read(Types.BYTE); // Operation } continue; } wrapper.write(Types.STRING, attribute); wrapper.passthrough(Types.DOUBLE); // Base final int modifierSize = wrapper.passthrough(Types.VAR_INT); for (int j = 0; j < modifierSize; j++) { wrapper.passthrough(Types.UUID); // ID wrapper.passthrough(Types.DOUBLE); // Amount wrapper.passthrough(Types.BYTE); // Operation } } wrapper.set(Types.VAR_INT, 1, newSize); }); } private KeyMappings toMappings(final RegistryEntry[] entries) { final String[] keys = new String[entries.length]; for (int i = 0; i < entries.length; i++) { keys[i] = Key.stripMinecraftNamespace(entries[i].key()); } return new KeyMappings(keys); } private void updateParticleFormat(final CompoundTag options, final String particleType) { if ("block".equals(particleType) || "block_marker".equals(particleType) || "falling_dust".equals(particleType) || "dust_pillar".equals(particleType)) { Tag blockState = options.remove("block_state"); if (blockState instanceof StringTag) { final CompoundTag compoundTag = new CompoundTag(); compoundTag.put("Name", blockState); blockState = compoundTag; } options.put("value", blockState); } else if ("item".equals(particleType)) { Tag item = options.remove("item"); if (item instanceof StringTag) { final CompoundTag compoundTag = new CompoundTag(); compoundTag.put("id", item); item = compoundTag; } options.put("value", item); } else if ("dust_color_transition".equals(particleType)) { moveTag(options, "from_color", "fromColor"); moveTag(options, "to_color", "toColor"); } else if ("entity_effect".equals(particleType)) { Tag color = options.remove("color"); if (color instanceof ListTag) { //noinspection unchecked ListTag colorParts = (ListTag) color; color = new FloatTag(encodeARGB( colorParts.get(0).getValue().floatValue(), colorParts.get(1).getValue().floatValue(), colorParts.get(2).getValue().floatValue(), colorParts.get(3).getValue().floatValue() )); } options.put("value", color); } } private int encodeARGB(final float a, final float r, final float g, final float b) { final int encodedAlpha = encodeColorPart(a); final int encodedRed = encodeColorPart(r); final int encodedGreen = encodeColorPart(g); final int encodedBlue = encodeColorPart(b); return encodedAlpha << 24 | encodedRed << 16 | encodedGreen << 8 | encodedBlue; } private int encodeColorPart(final float part) { return (int) Math.floor(part * 255); } private int removeAlpha(final int argb) { return argb & 0x00FFFFFF; } private void moveTag(final CompoundTag compoundTag, final String from, final String to) { final Tag tag = compoundTag.remove(from); if (tag != null) { compoundTag.put(to, tag); } } private void updateDimensionTypeData(final CompoundTag elementTag) { final CompoundTag monsterSpawnLightLevel = elementTag.getCompoundTag("monster_spawn_light_level"); if (monsterSpawnLightLevel != null) { final CompoundTag value = new CompoundTag(); monsterSpawnLightLevel.put("value", value); value.putInt("min_inclusive", monsterSpawnLightLevel.getInt("min_inclusive")); value.putInt("max_inclusive", monsterSpawnLightLevel.getInt("max_inclusive")); } } private void sendCarpetColorUpdate(final UserConnection connection, final int entityId, final Item item) { final PacketWrapper setEntityData = PacketWrapper.create(ClientboundPackets1_20_3.SET_ENTITY_DATA, connection); setEntityData.write(Types.VAR_INT, entityId); // Entity id int color = -1; if (item != null) { // Convert carpet item id to dyed color id if (item.identifier() >= 445 && item.identifier() <= 460) { color = item.identifier() - 445; } } final List entityDataList = new ArrayList<>(); entityDataList.add(new EntityData(20, Types1_20_3.ENTITY_DATA_TYPES.varIntType, color)); setEntityData.write(Types1_20_3.ENTITY_DATA_LIST, entityDataList); setEntityData.send(Protocol1_20_5To1_20_3.class); } @Override protected void registerRewrites() { filter().handler((event, data) -> { final int typeId = data.dataType().typeId(); if (typeId == VersionedTypes.V1_20_5.entityDataTypes.particlesType.typeId()) { final Particle[] particles = data.value(); int color = 0; for (final Particle particle : particles) { if (particle.id() == protocol.getMappingData().getParticleMappings().id("entity_effect")) { // Remove color argument, use one of them for the ambient particle color color = particle.removeArgument(0).getValue(); } } data.setTypeAndValue(Types1_20_3.ENTITY_DATA_TYPES.varIntType, removeAlpha(color)); return; } else if (typeId == VersionedTypes.V1_20_5.entityDataTypes.armadilloState.typeId() || typeId == VersionedTypes.V1_20_5.entityDataTypes.wolfVariantType.typeId()) { event.cancel(); return; } int id = typeId; if (typeId >= VersionedTypes.V1_20_5.entityDataTypes.armadilloState.typeId()) { id--; } if (typeId >= VersionedTypes.V1_20_5.entityDataTypes.wolfVariantType.typeId()) { id--; } if (typeId >= VersionedTypes.V1_20_5.entityDataTypes.particlesType.typeId()) { id--; } data.setDataType(Types1_20_3.ENTITY_DATA_TYPES.byId(id)); }); registerEntityDataTypeHandler1_20_3( Types1_20_3.ENTITY_DATA_TYPES.itemType, Types1_20_3.ENTITY_DATA_TYPES.blockStateType, Types1_20_3.ENTITY_DATA_TYPES.optionalBlockStateType, Types1_20_3.ENTITY_DATA_TYPES.particleType, null, Types1_20_3.ENTITY_DATA_TYPES.componentType, Types1_20_3.ENTITY_DATA_TYPES.optionalComponentType ); registerBlockStateHandler(EntityTypes1_20_5.ABSTRACT_MINECART, 11); filter().type(EntityTypes1_20_5.AREA_EFFECT_CLOUD).addIndex(9); // Color filter().type(EntityTypes1_20_5.AREA_EFFECT_CLOUD).index(11).handler((event, data) -> { final Particle particle = data.value(); if (particle.id() == protocol.getMappingData().getParticleMappings().mappedId("entity_effect")) { // Move color to its own entity data final int color = particle.removeArgument(0).getValue(); event.createExtraData(new EntityData(9, Types1_20_3.ENTITY_DATA_TYPES.varIntType, removeAlpha(color))); } }); filter().type(EntityTypes1_20_5.LLAMA).addIndex(20); // Carpet color filter().type(EntityTypes1_20_5.ARMADILLO).removeIndex(17); // State filter().type(EntityTypes1_20_5.WOLF).removeIndex(22); // Wolf variant filter().type(EntityTypes1_20_5.OMINOUS_ITEM_SPAWNER).removeIndex(8); // Item } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); mapEntityTypeWithData(EntityTypes1_20_5.ARMADILLO, EntityTypes1_20_5.COW).tagName(); mapEntityTypeWithData(EntityTypes1_20_5.BOGGED, EntityTypes1_20_5.STRAY).tagName(); mapEntityTypeWithData(EntityTypes1_20_5.BREEZE_WIND_CHARGE, EntityTypes1_20_5.WIND_CHARGE); mapEntityTypeWithData(EntityTypes1_20_5.OMINOUS_ITEM_SPAWNER, EntityTypes1_20_5.TEXT_DISPLAY); } @Override public EntityType typeFromId(final int type) { return EntityTypes1_20_5.getTypeFromId(type); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_5to1_20_3/storage/CookieStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.storage; import com.viaversion.viaversion.api.connection.StorableObject; import java.util.HashMap; import java.util.Map; public final class CookieStorage implements StorableObject { private final Map cookies = new HashMap<>(); public Map cookies() { return cookies; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_5to1_20_3/storage/RegistryDataStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.storage; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viaversion.api.connection.StorableObject; import org.checkerframework.checker.nullness.qual.Nullable; public final class RegistryDataStorage implements StorableObject { private final CompoundTag registryData = new CompoundTag(); private String[] dimensionKeys; private boolean sentRegistryData; public CompoundTag registryData() { return registryData; } public boolean sentRegistryData() { return sentRegistryData; } public void setSentRegistryData() { this.sentRegistryData = true; } public String @Nullable [] dimensionKeys() { return dimensionKeys; } public void setDimensionKeys(final String[] dimensionKeys) { this.dimensionKeys = dimensionKeys; } public void clear() { registryData.clear(); dimensionKeys = null; sentRegistryData = false; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20_5to1_20_3/storage/SecureChatStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20_5to1_20_3.storage; import com.viaversion.viaversion.api.connection.StorableObject; public final class SecureChatStorage implements StorableObject { private boolean enforcesSecureChat; public void setEnforcesSecureChat(final boolean enforcesSecureChat) { this.enforcesSecureChat = enforcesSecureChat; } public boolean enforcesSecureChat() { return enforcesSecureChat; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20to1_19_4/Protocol1_20To1_19_4.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20to1_19_4; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_20to1_19_4.data.BackwardsMappingData1_20; import com.viaversion.viabackwards.protocol.v1_20to1_19_4.rewriter.BlockItemPacketRewriter1_20; import com.viaversion.viabackwards.protocol.v1_20to1_19_4.rewriter.BlockPacketRewriter1_20; import com.viaversion.viabackwards.protocol.v1_20to1_19_4.rewriter.EntityPacketRewriter1_20; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_4; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ClientboundPackets1_19_4; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ServerboundPackets1_19_4; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; import com.viaversion.viaversion.util.ArrayUtil; public final class Protocol1_20To1_19_4 extends BackwardsProtocol { public static final BackwardsMappingData1_20 MAPPINGS = new BackwardsMappingData1_20(); private final JsonNBTComponentRewriter translatableRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); private final EntityPacketRewriter1_20 entityRewriter = new EntityPacketRewriter1_20(this); private final BlockItemPacketRewriter1_20 itemRewriter = new BlockItemPacketRewriter1_20(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BlockRewriter blockRewriter = new BlockPacketRewriter1_20(this); public Protocol1_20To1_19_4() { super(ClientboundPackets1_19_4.class, ClientboundPackets1_19_4.class, ServerboundPackets1_19_4.class, ServerboundPackets1_19_4.class); } @Override protected void registerPackets() { super.registerPackets(); tagRewriter.addEmptyTag(RegistryType.BLOCK, "minecraft:replaceable_plants"); registerClientbound(ClientboundPackets1_19_4.UPDATE_ENABLED_FEATURES, wrapper -> { String[] enabledFeatures = wrapper.read(Types.STRING_ARRAY); wrapper.write(Types.STRING_ARRAY, ArrayUtil.add(enabledFeatures, "minecraft:update_1_20")); }); registerClientbound(ClientboundPackets1_19_4.PLAYER_COMBAT_END, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Duration wrapper.write(Types.INT, -1); // Killer ID - unused (who knows for how long?) }); replaceClientbound(ClientboundPackets1_19_4.PLAYER_COMBAT_KILL, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Player ID wrapper.write(Types.INT, -1); // Killer ID - unused (who knows for how long?) translatableRewriter.processText(wrapper.user(), wrapper.passthrough(Types.COMPONENT)); }); } @Override public void init(final UserConnection user) { addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_19_4.PLAYER)); } @Override public BackwardsMappingData1_20 getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_20 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_20 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20to1_19_4/data/BackwardsMappingData1_20.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20to1_19_4.data; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.data.BackwardsMappingDataLoader; import com.viaversion.viaversion.protocols.v1_19_4to1_20.Protocol1_19_4To1_20; public class BackwardsMappingData1_20 extends BackwardsMappingData { private CompoundTag trimPatternRegistry; public BackwardsMappingData1_20() { super("1.20", "1.19.4", Protocol1_19_4To1_20.class); } @Override protected void loadExtras(CompoundTag data) { super.loadExtras(data); trimPatternRegistry = BackwardsMappingDataLoader.INSTANCE.loadNBT("trim_pattern-1.19.4.nbt"); } public CompoundTag getTrimPatternRegistry() { return trimPatternRegistry.copy(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20to1_19_4/rewriter/BlockItemPacketRewriter1_20.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20to1_19_4.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.protocol.v1_20to1_19_4.Protocol1_20To1_19_4; import com.viaversion.viabackwards.protocol.v1_20to1_19_4.storage.BackSignEditStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.BlockChangeRecord; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ClientboundPackets1_19_4; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ServerboundPackets1_19_4; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.rewriter.RecipeRewriter1_19_4; import com.viaversion.viaversion.util.Key; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import org.checkerframework.checker.nullness.qual.Nullable; public final class BlockItemPacketRewriter1_20 extends BackwardsItemRewriter { private static final Set NEW_TRIM_PATTERNS = new HashSet<>(Arrays.asList("host", "raiser", "shaper", "silence", "wayfinder")); public BlockItemPacketRewriter1_20(final Protocol1_20To1_19_4 protocol) { super(protocol, Types.ITEM1_13_2, Types.ITEM1_13_2_ARRAY); } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_19_4.LEVEL_CHUNK_WITH_LIGHT, new PacketHandlers() { @Override protected void register() { handler(wrapper -> { final Chunk chunk = protocol.getBlockRewriter().handleChunk1_18(wrapper); protocol.getBlockRewriter().handleBlockEntities(chunk, wrapper.user()); }); create(Types.BOOLEAN, true); // Trust edges } }); protocol.registerClientbound(ClientboundPackets1_19_4.LIGHT_UPDATE, wrapper -> { wrapper.passthrough(Types.VAR_INT); // X wrapper.passthrough(Types.VAR_INT); // Y wrapper.write(Types.BOOLEAN, true); // Trust edges }); protocol.replaceClientbound(ClientboundPackets1_19_4.SECTION_BLOCKS_UPDATE, new PacketHandlers() { @Override public void register() { map(Types.LONG); // Chunk position create(Types.BOOLEAN, false); // Suppress light updates handler(wrapper -> { for (final BlockChangeRecord record : wrapper.passthrough(Types.VAR_LONG_BLOCK_CHANGE_ARRAY)) { record.setBlockId(protocol.getMappingData().getNewBlockStateId(record.getBlockId())); } }); } }); protocol.replaceClientbound(ClientboundPackets1_19_4.UPDATE_ADVANCEMENTS, wrapper -> { wrapper.passthrough(Types.BOOLEAN); // Reset/clear int size = wrapper.passthrough(Types.VAR_INT); // Mapping size for (int i = 0; i < size; i++) { wrapper.passthrough(Types.STRING); // Identifier wrapper.passthrough(Types.OPTIONAL_STRING); // Parent // Display data if (wrapper.passthrough(Types.BOOLEAN)) { wrapper.passthrough(Types.COMPONENT); // Title wrapper.passthrough(Types.COMPONENT); // Description passthroughClientboundItem(wrapper); // Icon wrapper.passthrough(Types.VAR_INT); // Frame type int flags = wrapper.passthrough(Types.INT); // Flags if ((flags & 1) != 0) { wrapper.passthrough(Types.STRING); // Background texture } wrapper.passthrough(Types.FLOAT); // X wrapper.passthrough(Types.FLOAT); // Y } wrapper.passthrough(Types.STRING_ARRAY); // Criteria int arrayLength = wrapper.passthrough(Types.VAR_INT); for (int array = 0; array < arrayLength; array++) { wrapper.passthrough(Types.STRING_ARRAY); // String array } wrapper.read(Types.BOOLEAN); // Sends telemetry } }); protocol.registerClientbound(ClientboundPackets1_19_4.OPEN_SIGN_EDITOR, wrapper -> { final BlockPosition position = wrapper.passthrough(Types.BLOCK_POSITION1_14); final boolean frontSide = wrapper.read(Types.BOOLEAN); if (frontSide) { wrapper.user().remove(BackSignEditStorage.class); } else { wrapper.user().put(new BackSignEditStorage(position)); } }); protocol.registerServerbound(ServerboundPackets1_19_4.SIGN_UPDATE, wrapper -> { final BlockPosition position = wrapper.passthrough(Types.BLOCK_POSITION1_14); final BackSignEditStorage backSignEditStorage = wrapper.user().remove(BackSignEditStorage.class); final boolean frontSide = backSignEditStorage == null || !backSignEditStorage.position().equals(position); wrapper.write(Types.BOOLEAN, frontSide); }); new RecipeRewriter1_19_4<>(protocol).register(ClientboundPackets1_19_4.UPDATE_RECIPES); } @Override public @Nullable Item handleItemToClient(UserConnection connection, @Nullable Item item) { if (item == null) { return null; } item = super.handleItemToClient(connection, item); // Remove new trim tags final CompoundTag trimTag; final CompoundTag tag = item.tag(); if (tag != null && (trimTag = tag.getCompoundTag("Trim")) != null) { final StringTag patternTag = trimTag.getStringTag("pattern"); if (patternTag != null) { final String pattern = Key.stripMinecraftNamespace(patternTag.getValue()); if (NEW_TRIM_PATTERNS.contains(pattern)) { tag.remove("Trim"); tag.put(nbtTagName("Trim"), trimTag); } } } return item; } @Override public @Nullable Item handleItemToServer(UserConnection connection, @Nullable Item item) { if (item == null) { return null; } item = super.handleItemToServer(connection, item); // Add back original trim tag final Tag trimTag; final CompoundTag tag = item.tag(); if (tag != null && (trimTag = tag.remove(nbtTagName("Trim"))) != null) { tag.put("Trim", trimTag); } return item; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20to1_19_4/rewriter/BlockPacketRewriter1_20.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20to1_19_4.rewriter; 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_20to1_19_4.Protocol1_20To1_19_4; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.blockentity.BlockEntity; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_18; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ClientboundPackets1_19_4; import com.viaversion.viaversion.rewriter.BlockRewriter; public final class BlockPacketRewriter1_20 extends BlockRewriter { public BlockPacketRewriter1_20(final Protocol1_20To1_19_4 protocol) { super(protocol, Types.BLOCK_POSITION1_14, Types.NAMED_COMPOUND_TAG, ChunkType1_18::new, null); } @Override public void handleBlockEntity(final UserConnection connection, final BlockEntity blockEntity) { final CompoundTag tag = blockEntity.tag(); if (tag == null || (blockEntity.typeId() != 7 && blockEntity.typeId() != 8)) { return; } final Tag frontText = tag.remove("front_text"); tag.remove("back_text"); if (frontText instanceof CompoundTag frontTextTag) { writeMessages(frontTextTag, tag, false); writeMessages(frontTextTag, tag, true); final Tag color = frontTextTag.remove("color"); if (color != null) { tag.put("Color", color); } final Tag glowing = frontTextTag.remove("has_glowing_text"); if (glowing != null) { tag.put("GlowingText", glowing); } } } private void writeMessages(final CompoundTag frontText, final CompoundTag tag, final boolean filtered) { final ListTag messages = frontText.getListTag(filtered ? "filtered_messages" : "messages", StringTag.class); if (messages == null) { return; } int i = 0; for (final StringTag message : messages) { tag.put((filtered ? "FilteredText" : "Text") + ++i, message); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20to1_19_4/rewriter/EntityPacketRewriter1_20.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20to1_19_4.rewriter; import com.google.common.collect.Sets; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_20to1_19_4.Protocol1_20To1_19_4; import com.viaversion.viaversion.api.minecraft.Quaternion; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_4; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.Types1_19_4; import com.viaversion.viaversion.api.type.types.version.Types1_20; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.packet.ClientboundPackets1_19_4; import com.viaversion.viaversion.util.Key; import java.util.Set; public final class EntityPacketRewriter1_20 extends EntityRewriter { private final Set newTrimPatterns = Sets.newHashSet("host_armor_trim_smithing_template", "raiser_armor_trim_smithing_template", "silence_armor_trim_smithing_template", "shaper_armor_trim_smithing_template", "wayfinder_armor_trim_smithing_template"); private static final Quaternion Y_FLIPPED_ROTATION = new Quaternion(0, 1, 0, 0); public EntityPacketRewriter1_20(final Protocol1_20To1_19_4 protocol) { super(protocol, Types1_19_4.ENTITY_DATA_TYPES.optionalComponentType, Types1_19_4.ENTITY_DATA_TYPES.booleanType); } @Override public void registerPackets() { registerSetEntityData(ClientboundPackets1_19_4.SET_ENTITY_DATA, Types1_20.ENTITY_DATA_LIST, Types1_19_4.ENTITY_DATA_LIST); protocol.registerClientbound(ClientboundPackets1_19_4.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // Entity id map(Types.BOOLEAN); // Hardcore map(Types.BYTE); // Gamemode map(Types.BYTE); // Previous Gamemode map(Types.STRING_ARRAY); // World List map(Types.NAMED_COMPOUND_TAG); // Dimension registry map(Types.STRING); // Dimension key map(Types.STRING); // World map(Types.LONG); // Seed map(Types.VAR_INT); // Max players map(Types.VAR_INT); // Chunk radius map(Types.VAR_INT); // Simulation distance map(Types.BOOLEAN); // Reduced debug info map(Types.BOOLEAN); // Show death screen map(Types.BOOLEAN); // Debug map(Types.BOOLEAN); // Flat map(Types.OPTIONAL_GLOBAL_POSITION); // Last death location read(Types.VAR_INT); // Portal cooldown handler(dimensionDataHandler()); // Caches dimensions to access data like height later handler(biomeSizeTracker()); // Tracks the amount of biomes sent for chunk data handler(worldDataTrackerHandlerByKey()); // Tracks world height and name for chunk data and entity (un)tracking handler(wrapper -> { final CompoundTag registry = wrapper.get(Types.NAMED_COMPOUND_TAG, 0); final ListTag values; // A 1.20 server can't send this element, and the 1.20 client still works, if the element is missing // on a 1.19.4 client there is an exception, so in case the 1.20 server doesn't send the element we put in an original 1.20 element CompoundTag trimPatternTag = registry.getCompoundTag("minecraft:trim_pattern"); if (trimPatternTag != null || (trimPatternTag = registry.getCompoundTag("trim_pattern")) != null) { values = trimPatternTag.getListTag("value", CompoundTag.class); } else { final CompoundTag trimPatternRegistry = Protocol1_20To1_19_4.MAPPINGS.getTrimPatternRegistry().copy(); registry.put("minecraft:trim_pattern", trimPatternRegistry); values = trimPatternRegistry.getListTag("value", CompoundTag.class); } for (final CompoundTag entry : values) { final CompoundTag element = entry.getCompoundTag("element"); final StringTag templateItem = element.getStringTag("template_item"); if (newTrimPatterns.contains(Key.stripMinecraftNamespace(templateItem.getValue()))) { templateItem.setValue("minecraft:spire_armor_trim_smithing_template"); } } }); } }); protocol.registerClientbound(ClientboundPackets1_19_4.RESPAWN, new PacketHandlers() { @Override public void register() { map(Types.STRING); // Dimension map(Types.STRING); // World map(Types.LONG); // Seed map(Types.UNSIGNED_BYTE); // Gamemode map(Types.BYTE); // Previous gamemode map(Types.BOOLEAN); // Debug map(Types.BOOLEAN); // Flat map(Types.BYTE); // Data to keep map(Types.OPTIONAL_GLOBAL_POSITION); // Last death location read(Types.VAR_INT); // Portal cooldown handler(worldDataTrackerHandlerByKey()); // Tracks world height and name for chunk data and entity (un)tracking } }); } @Override protected void registerRewrites() { filter().mapDataType(Types1_19_4.ENTITY_DATA_TYPES::byId); registerEntityDataTypeHandler(Types1_19_4.ENTITY_DATA_TYPES.itemType, Types1_19_4.ENTITY_DATA_TYPES.blockStateType, Types1_19_4.ENTITY_DATA_TYPES.optionalBlockStateType, Types1_19_4.ENTITY_DATA_TYPES.particleType, Types1_19_4.ENTITY_DATA_TYPES.componentType, Types1_19_4.ENTITY_DATA_TYPES.optionalComponentType); registerBlockStateHandler(EntityTypes1_19_4.ABSTRACT_MINECART, 11); // Rotate item display by 180 degrees around the Y axis filter().type(EntityTypes1_19_4.ITEM_DISPLAY).handler((event, data) -> { if (event.trackedEntity().hasSentEntityData() || event.hasExtraData()) { return; } if (event.dataAtIndex(12) == null) { event.createExtraData(new EntityData(12, Types1_19_4.ENTITY_DATA_TYPES.quaternionType, Y_FLIPPED_ROTATION)); } }); filter().type(EntityTypes1_19_4.ITEM_DISPLAY).index(12).handler((event, data) -> { final Quaternion quaternion = data.value(); data.setValue(rotateY180(quaternion)); }); } @Override public EntityType typeFromId(final int type) { return EntityTypes1_19_4.getTypeFromId(type); } private Quaternion rotateY180(final Quaternion quaternion) { return new Quaternion(-quaternion.z(), quaternion.w(), quaternion.x(), -quaternion.y()); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_20to1_19_4/storage/BackSignEditStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_20to1_19_4.storage; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.minecraft.BlockPosition; public record BackSignEditStorage(BlockPosition position) implements StorableObject { } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_11to1_21_9/Protocol1_21_11To1_21_9.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_11to1_21_9; import com.viaversion.nbt.tag.ByteTag; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter; import com.viaversion.viabackwards.api.rewriters.text.NBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.rewriter.BlockItemPacketRewriter1_21_11; import com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.rewriter.ComponentRewriter1_21_11; import com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.rewriter.EntityPacketRewriter1_21_11; import com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.storage.GameTimeStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.FullMappings; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_11; 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.chunk.ChunkType1_21_5; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.api.type.types.version.VersionedTypesHolder; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.data.item.ItemHasherBase; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.rewriter.RecipeDisplayRewriter1_21_5; import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPackets1_21_6; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundConfigurationPackets1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundPacket1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundPackets1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundConfigurationPackets1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundPacket1_21_9; import com.viaversion.viaversion.protocols.v1_21_9to1_21_11.Protocol1_21_9To1_21_11; import com.viaversion.viaversion.protocols.v1_21_9to1_21_11.packet.ClientboundPacket1_21_11; import com.viaversion.viaversion.protocols.v1_21_9to1_21_11.packet.ClientboundPackets1_21_11; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.RecipeDisplayRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.TagUtil; import java.util.function.Function; import org.checkerframework.checker.nullness.qual.Nullable; import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap; import static com.viaversion.viaversion.util.TagUtil.getNamespacedTag; public final class Protocol1_21_11To1_21_9 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.21.11", "1.21.9", Protocol1_21_9To1_21_11.class); private static final int END_FOG_COLOR = 10518688; private static final int OVERWORLD_FOG_COLOR = -4138753; private final EntityPacketRewriter1_21_11 entityRewriter = new EntityPacketRewriter1_21_11(this); private final BlockItemPacketRewriter1_21_11 itemRewriter = new BlockItemPacketRewriter1_21_11(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final NBTComponentRewriter translatableRewriter = new ComponentRewriter1_21_11(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final RecipeDisplayRewriter recipeRewriter = new RecipeDisplayRewriter1_21_5<>(this); private final BlockRewriter blockRewriter = BlockRewriter.for1_20_2(this, ChunkType1_21_5::new); private final BackwardsRegistryRewriter registryDataRewriter = new BackwardsRegistryRewriter(this) { @Override protected void updateType(final CompoundTag tag, final String key, final FullMappings mappings) { super.updateType(tag, key, mappings); if (key.equals("sound") && tag.get(key) instanceof ListTag listTag) { // From a compact list to a single value final Tag first; if (listTag.isEmpty()) { first = new StringTag(mappings.mappedIdentifier(0)); // Dummy } else { first = listTag.get(0); } tag.put(key, first); } } }; public Protocol1_21_11To1_21_9() { super(ClientboundPacket1_21_11.class, ClientboundPacket1_21_9.class, ServerboundPacket1_21_9.class, ServerboundPacket1_21_9.class); } private void moveAttribute(final CompoundTag baseTag, @Nullable final CompoundTag attributes, final String key, final String mappedKey, final Function tagMapper, @Nullable final Tag defaultTag) { final Tag attributeTag; if (attributes != null && (attributeTag = getNamespacedTag(attributes, key)) != null) { baseTag.put(mappedKey, tagMapper.apply(attributeTag)); } else if (defaultTag != null) { baseTag.put(mappedKey, defaultTag); } } private int floatsToARGB(final float r, final float g, final float b) { return 255 << 24 | (int) (r * 255) << 16 | (int) (g * 255) << 8 | (int) (b * 255); } @Override protected void registerPackets() { super.registerPackets(); // Add back mandatory fields from attributes, though most don't have any use in the client registryDataRewriter.addHandler("dimension_type", (key, tag) -> { if (Key.equals(key, "the_nether")) { tag.putString("effects", "minecraft:the_nether"); tag.putBoolean("natural", false); } else if (Key.equals(key, "the_end")) { tag.putString("effects", "minecraft:the_end"); tag.putBoolean("natural", false); } else { tag.putString("effects", "minecraft:overworld"); tag.putBoolean("natural", true); } final ByteTag trueTag = new ByteTag((byte) 1); final CompoundTag attributes = tag.getCompoundTag("attributes"); moveAttribute(tag, attributes, "visual/cloud_height", "cloud_height", Function.identity(), null); moveAttribute(tag, attributes, "gameplay/can_start_raid", "has_raids", Function.identity(), trueTag); moveAttribute(tag, attributes, "gameplay/piglins_zombify", "piglin_safe", attributeTag -> ((NumberTag) attributeTag).asBoolean() ? ByteTag.ZERO : trueTag, ByteTag.ZERO); moveAttribute(tag, attributes, "gameplay/respawn_anchor_works", "respawn_anchor_works", Function.identity(), trueTag); moveAttribute(tag, attributes, "gameplay/bed_rule", "bed_works", attributeTag -> { final CompoundTag bedRule = (CompoundTag) attributeTag; return bedRule.getBoolean("can_sleep") || bedRule.getBoolean("can_set_spawn") ? trueTag : ByteTag.ZERO; }, trueTag); // Many different functions back into one, all have different effects on the client, so pick the most important one... moveAttribute(tag, attributes, "gameplay/fast_lava", "ultrawarm", Function.identity(), ByteTag.ZERO); }); registryDataRewriter.addHandler("worldgen/biome", (key, tag) -> { final CompoundTag effects = tag.getCompoundTag("effects"); // Colors moveAttribute(effects, effects, "water_color", "water_color", this::mapColor, new IntTag(4159204)); moveAttribute(effects, effects, "foliage_color", "foliage_color", this::mapColor, null); moveAttribute(effects, effects, "dry_foliage_color", "dry_foliage_color", this::mapColor, null); moveAttribute(effects, effects, "grass_color", "grass_color", this::mapColor, null); final CompoundTag attributes = tag.removeUnchecked("attributes"); moveAttribute(effects, attributes, "visual/sky_color", "sky_color", this::mapColor, new IntTag(0)); moveAttribute(effects, attributes, "visual/water_fog_color", "water_fog_color", this::mapColor, new IntTag(-16448205)); moveAttribute(effects, attributes, "visual/fog_color", "fog_color", this::mapColor, new IntTag(Key.equals(key, "the_end") ? END_FOG_COLOR : OVERWORLD_FOG_COLOR)); // overworld fog color as default if (attributes == null) { return; } // Music and sounds final CompoundTag backgroundMusic = TagUtil.getNamespacedCompoundTag(attributes, "audio/background_music"); if (backgroundMusic != null) { final CompoundTag def = backgroundMusic.getCompoundTag("default"); if (def != null) { final CompoundTag data = new CompoundTag(); final Tag maxDelay = def.get("max_delay"); final Tag minDelay = def.get("min_delay"); final Tag sound = def.get("sound"); if (maxDelay != null) { data.put("max_delay", maxDelay); } if (minDelay != null) { data.put("min_delay", minDelay); } if (sound != null) { data.put("sound", sound); } // Assume this by default if (!data.contains("replace_current_music")) { data.putBoolean("replace_current_music", false); } final CompoundTag entry = new CompoundTag(); entry.put("data", data); entry.putInt("weight", 1); final ListTag musicList = new ListTag<>(CompoundTag.class); musicList.add(entry); effects.put("music", musicList); } } final CompoundTag ambientSounds = TagUtil.getNamespacedCompoundTag(attributes, "audio/ambient_sounds"); if (ambientSounds != null) { final Tag loop = ambientSounds.get("loop"); if (loop != null) { effects.put("ambient_sound", loop); } final Tag mood = ambientSounds.get("mood"); if (mood != null) { effects.put("mood_sound", mood); } final Tag additions = ambientSounds.get("additions"); if (additions != null) { effects.put("additions_sound", additions); } } // Particles final ListTag ambientParticles = TagUtil.getNamespacedCompoundTagList(attributes, "visual/ambient_particles"); if (ambientParticles != null && !ambientParticles.isEmpty()) { final CompoundTag first = ambientParticles.get(0); final Tag probability = first.get("probability"); final CompoundTag particle = first.getCompoundTag("particle"); // Single particle final CompoundTag options = new CompoundTag(); options.put("probability", probability); options.put("options", particle); effects.put("particle", options); } }); registryDataRewriter.addHandler("enchantment", (key, tag) -> { final CompoundTag effects = tag.getCompoundTag("effects"); if (effects != null) { TagUtil.removeNamespaced(effects, "post_piercing_attack"); } }); registryDataRewriter.remove("zombie_nautilus_variant"); registryDataRewriter.remove("timeline"); tagRewriter.removeTags("timeline"); } @Override public void init(final UserConnection connection) { addEntityTracker(connection, new EntityTrackerBase(connection, EntityTypes1_21_11.PLAYER)); addItemHasher(connection, new ItemHasherBase(this, connection)); connection.put(new GameTimeStorage()); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_21_11 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_21_11 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public BackwardsRegistryRewriter getRegistryDataRewriter() { return registryDataRewriter; } @Override public RecipeDisplayRewriter getRecipeRewriter() { return recipeRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public NBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } @Override public VersionedTypesHolder types() { return VersionedTypes.V1_21_11; } @Override public VersionedTypesHolder mappedTypes() { return VersionedTypes.V1_21_9; } @Override protected PacketTypesProvider createPacketTypesProvider() { return new SimplePacketTypesProvider<>( packetTypeMap(unmappedClientboundPacketType, ClientboundPackets1_21_11.class, ClientboundConfigurationPackets1_21_9.class), packetTypeMap(mappedClientboundPacketType, ClientboundPackets1_21_9.class, ClientboundConfigurationPackets1_21_9.class), packetTypeMap(mappedServerboundPacketType, ServerboundPackets1_21_6.class, ServerboundConfigurationPackets1_21_9.class), packetTypeMap(unmappedServerboundPacketType, ServerboundPackets1_21_6.class, ServerboundConfigurationPackets1_21_9.class) ); } private Tag mapColor(final Tag attributeTag) { if (attributeTag instanceof ListTag listTag) { final NumberTag r = ((NumberTag) listTag.get(0)); final NumberTag g = ((NumberTag) listTag.get(1)); final NumberTag b = ((NumberTag) listTag.get(2)); return new IntTag(floatsToARGB(r.asFloat(), g.asFloat(), b.asFloat())); } else if (attributeTag instanceof StringTag stringTag) { // Remove '#' and parse hex string return new IntTag(Integer.parseInt(stringTag.getValue().substring(1), 16)); } return attributeTag; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_11to1_21_9/rewriter/BlockItemPacketRewriter1_21_11.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.rewriters.BackwardsStructuredItemRewriter; import com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.Protocol1_21_11To1_21_9; import com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.storage.GameTimeStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.Holder; import com.viaversion.viaversion.api.minecraft.SoundEvent; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.item.data.AttackRange; import com.viaversion.viaversion.api.minecraft.item.data.DamageType; import com.viaversion.viaversion.api.minecraft.item.data.KineticWeapon; import com.viaversion.viaversion.api.minecraft.item.data.PiercingWeapon; import com.viaversion.viaversion.api.minecraft.item.data.SwingAnimation; import com.viaversion.viaversion.api.minecraft.item.data.UseEffects; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPackets1_21_6; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundPacket1_21_9; import com.viaversion.viaversion.protocols.v1_21_9to1_21_11.packet.ClientboundPacket1_21_11; import com.viaversion.viaversion.protocols.v1_21_9to1_21_11.packet.ClientboundPackets1_21_11; import com.viaversion.viaversion.util.Either; import org.checkerframework.checker.nullness.qual.Nullable; import static com.viaversion.viaversion.protocols.v1_21_9to1_21_11.rewriter.BlockItemPacketRewriter1_21_11.downgradeData; import static com.viaversion.viaversion.protocols.v1_21_9to1_21_11.rewriter.BlockItemPacketRewriter1_21_11.upgradeData; public final class BlockItemPacketRewriter1_21_11 extends BackwardsStructuredItemRewriter { public BlockItemPacketRewriter1_21_11(final Protocol1_21_11To1_21_9 protocol) { super(protocol); } @Override public void registerPackets() { protocol.registerClientbound(ClientboundPackets1_21_11.SET_BORDER_LERP_SIZE, wrapper -> { wrapper.passthrough(Types.DOUBLE); // oldSize wrapper.passthrough(Types.DOUBLE); // newSize wrapper.write(Types.VAR_LONG, wrapper.read(Types.VAR_LONG) * 50); // lerpTime }); protocol.registerClientbound(ClientboundPackets1_21_11.INITIALIZE_BORDER, wrapper -> { wrapper.passthrough(Types.DOUBLE); // newCenterX wrapper.passthrough(Types.DOUBLE); // newCenterZ wrapper.passthrough(Types.DOUBLE); // oldSize wrapper.passthrough(Types.DOUBLE); // newSize wrapper.write(Types.VAR_LONG, wrapper.read(Types.VAR_LONG) * 50); // lerpTime }); protocol.registerClientbound(ClientboundPackets1_21_11.SET_TIME, wrapper -> { final long gameTime = wrapper.passthrough(Types.LONG); wrapper.user().get(GameTimeStorage.class).setGameTime(gameTime); }); protocol.registerServerbound(ServerboundPackets1_21_6.CLIENT_TICK_END, wrapper -> { wrapper.user().get(GameTimeStorage.class).incrementGameTime(); }); } @Override protected void handleItemDataComponentsToClient(final UserConnection connection, final Item item, final StructuredDataContainer container) { super.handleItemDataComponentsToClient(connection, item, container); downgradeData(item, container); } @Override protected void handleItemDataComponentsToServer(final UserConnection connection, final Item item, final StructuredDataContainer container) { super.handleItemDataComponentsToServer(connection, item, container); upgradeData(item, container); } @Override protected void restoreBackupData(final Item item, final StructuredDataContainer container, final CompoundTag customData) { super.restoreBackupData(item, container, customData); if (!(customData.remove(nbtTagName("backup")) instanceof final CompoundTag backupTag)) { return; } final CompoundTag swingTag = backupTag.getCompoundTag("swing_animation"); if (swingTag != null) { final int type = swingTag.getInt("type"); final int duration = swingTag.getInt("duration"); container.set(StructuredDataKey.SWING_ANIMATION, new SwingAnimation(type, duration)); } final CompoundTag kineticTag = backupTag.getCompoundTag("kinetic_weapon"); if (kineticTag != null) { final int contactCooldownTicks = kineticTag.getInt("contact_cooldown_ticks"); final int delayTicks = kineticTag.getInt("delay_ticks"); final KineticWeapon.Condition damageConditions = loadDamageCondition(kineticTag, "damage_conditions"); final KineticWeapon.Condition dismountConditions = loadDamageCondition(kineticTag, "dismount_conditions"); final KineticWeapon.Condition knockbackConditions = loadDamageCondition(kineticTag, "knockback_conditions"); final float forwardMovement = kineticTag.getFloat("forward_movement"); final float damageMultiplier = kineticTag.getFloat("damage_multiplier"); final Holder sound = kineticTag.contains("sound") ? restoreSoundEventHolder(kineticTag, "sound") : null; final Holder hitSound = kineticTag.contains("hit_sound") ? restoreSoundEventHolder(kineticTag, "hit_sound") : null; container.set(StructuredDataKey.KINETIC_WEAPON, new KineticWeapon(contactCooldownTicks, delayTicks, damageConditions, dismountConditions, knockbackConditions, forwardMovement, damageMultiplier, sound, hitSound)); } final CompoundTag piercingTag = backupTag.getCompoundTag("piercing_weapon"); if (piercingTag != null) { final boolean dealsKnockback = piercingTag.getBoolean("deals_knockback"); final boolean dismounts = piercingTag.getBoolean("dismounts"); final Holder sound = piercingTag.contains("sound") ? restoreSoundEventHolder(piercingTag, "sound") : null; final Holder hitSound = piercingTag.contains("hit_sound") ? restoreSoundEventHolder(piercingTag, "hit_sound") : null; container.set(StructuredDataKey.PIERCING_WEAPON, new PiercingWeapon(dealsKnockback, dismounts, sound, hitSound)); } final Tag damageTypeId = backupTag.get("damage_type_id"); if (damageTypeId != null) { if (damageTypeId instanceof StringTag stringTag) { container.set(StructuredDataKey.DAMAGE_TYPE1_21_11, new DamageType(Either.right(stringTag.getValue()))); } else if (damageTypeId instanceof IntTag intTag) { container.set(StructuredDataKey.DAMAGE_TYPE1_21_11, new DamageType(Either.left(intTag.asInt()))); } } final CompoundTag useEffectsTag = backupTag.getCompoundTag("use_effects"); if (useEffectsTag != null) { final boolean canSprint = useEffectsTag.getBoolean("can_sprint"); final boolean interactVibrations = useEffectsTag.getBoolean("interact_vibrations"); final float speedMultiplier = useEffectsTag.getFloat("speed_multiplier"); container.set(StructuredDataKey.USE_EFFECTS, new UseEffects(canSprint, interactVibrations, speedMultiplier)); } final CompoundTag attackRangeTag = backupTag.getCompoundTag("attack_range"); if (attackRangeTag != null) { final float minRange = attackRangeTag.getFloat("min_range"); final float maxRange = attackRangeTag.getFloat("max_range"); final float minCreativeRange = attackRangeTag.getFloat("min_creative_reach"); final float maxCreativeRange = attackRangeTag.getFloat("max_creative_reach"); final float hitboxMargin = attackRangeTag.getFloat("hitbox_margin"); final float mobFactor = attackRangeTag.getFloat("mob_factor"); container.set(StructuredDataKey.ATTACK_RANGE, new AttackRange(minRange, maxRange, minCreativeRange, maxCreativeRange, hitboxMargin, mobFactor)); } final Tag zombieNautilusVariantTag = backupTag.get("zombie_nautilus_variant"); if (zombieNautilusVariantTag != null) { if (zombieNautilusVariantTag instanceof StringTag stringTag) { container.set(StructuredDataKey.ZOMBIE_NAUTILUS_VARIANT1_21_11, Either.right(stringTag.getValue())); } else if (zombieNautilusVariantTag instanceof IntTag intTag) { container.set(StructuredDataKey.ZOMBIE_NAUTILUS_VARIANT1_21_11, Either.left(intTag.asInt())); } } restoreFloatData(StructuredDataKey.MINIMUM_ATTACK_CHARGE, container, backupTag); } private KineticWeapon.Condition loadDamageCondition(final CompoundTag tag, final String key) { final CompoundTag conditionTag = tag.getCompoundTag(key); if (conditionTag == null) { return null; } final int maxDurationTicks = conditionTag.getInt("max_duration_ticks"); final float minSpeed = conditionTag.getFloat("min_speed"); final float minRelativeSpeed = conditionTag.getFloat("min_relative_speed"); return new KineticWeapon.Condition(maxDurationTicks, minSpeed, minRelativeSpeed); } @Override protected void backupInconvertibleData(final UserConnection connection, final Item item, final StructuredDataContainer dataContainer, final CompoundTag backupTag) { super.backupInconvertibleData(connection, item, dataContainer, backupTag); final SwingAnimation swingAnimation = dataContainer.get(StructuredDataKey.SWING_ANIMATION); if (swingAnimation != null) { final CompoundTag swingTag = new CompoundTag(); swingTag.putInt("type", swingAnimation.type()); swingTag.putInt("duration", swingAnimation.duration()); backupTag.put("swing_animation", swingTag); } final KineticWeapon kineticWeapon = dataContainer.get(StructuredDataKey.KINETIC_WEAPON); if (kineticWeapon != null) { final CompoundTag kineticTag = new CompoundTag(); kineticTag.putInt("contact_cooldown_ticks", kineticWeapon.contactCooldownTicks()); kineticTag.putInt("delay_ticks", kineticWeapon.delayTicks()); saveDamageCondition(kineticTag, "damage_conditions", kineticWeapon.damageConditions()); saveDamageCondition(kineticTag, "dismount_conditions", kineticWeapon.dismountConditions()); saveDamageCondition(kineticTag, "knockback_conditions", kineticWeapon.knockbackConditions()); kineticTag.putFloat("forward_movement", kineticWeapon.forwardMovement()); kineticTag.putFloat("damage_multiplier", kineticWeapon.damageMultiplier()); if (kineticWeapon.sound() != null) { kineticTag.put("sound", holderToTag(kineticWeapon.sound(), this::saveSoundEvent)); } if (kineticWeapon.hitSound() != null) { kineticTag.put("hit_sound", holderToTag(kineticWeapon.hitSound(), this::saveSoundEvent)); } backupTag.put("kinetic_weapon", kineticTag); } final PiercingWeapon piercingWeapon = dataContainer.get(StructuredDataKey.PIERCING_WEAPON); if (piercingWeapon != null) { final CompoundTag piercingTag = new CompoundTag(); piercingTag.putBoolean("deals_knockback", piercingWeapon.dealsKnockback()); piercingTag.putBoolean("dismounts", piercingWeapon.dismounts()); if (piercingWeapon.sound() != null) { piercingTag.put("sound", holderToTag(piercingWeapon.sound(), this::saveSoundEvent)); } if (piercingWeapon.hitSound() != null) { piercingTag.put("hit_sound", holderToTag(piercingWeapon.hitSound(), this::saveSoundEvent)); } backupTag.put("piercing_weapon", piercingTag); } final DamageType damageType = dataContainer.get(StructuredDataKey.DAMAGE_TYPE1_21_11); if (damageType != null) { if (damageType.id().isLeft()) { backupTag.putInt("damage_type_id", damageType.id().left()); } else { backupTag.putString("damage_type_id", damageType.id().right()); } } final UseEffects useEffects = dataContainer.get(StructuredDataKey.USE_EFFECTS); if (useEffects != null) { final CompoundTag useEffectsTag = new CompoundTag(); useEffectsTag.putBoolean("can_sprint", useEffects.canSprint()); useEffectsTag.putBoolean("interact_vibrations", useEffects.interactVibrations()); useEffectsTag.putFloat("speed_multiplier", useEffects.speedMultiplier()); backupTag.put("use_effects", useEffectsTag); } final AttackRange attackRange = dataContainer.get(StructuredDataKey.ATTACK_RANGE); if (attackRange != null) { final CompoundTag attackRangeTag = new CompoundTag(); attackRangeTag.putFloat("min_range", attackRange.minRange()); attackRangeTag.putFloat("max_range", attackRange.maxRange()); attackRangeTag.putFloat("min_creative_reach", attackRange.minCreativeRange()); attackRangeTag.putFloat("max_creative_reach", attackRange.maxCreativeRange()); attackRangeTag.putFloat("hitbox_margin", attackRange.hitboxMargin()); attackRangeTag.putFloat("mob_factor", attackRange.mobFactor()); backupTag.put("attack_range", attackRangeTag); } final Either zombieNautilusVariant = dataContainer.get(StructuredDataKey.ZOMBIE_NAUTILUS_VARIANT1_21_11); if (zombieNautilusVariant != null) { if (zombieNautilusVariant.isLeft()) { backupTag.putInt("zombie_nautilus_variant", zombieNautilusVariant.left()); } else { backupTag.putString("zombie_nautilus_variant", zombieNautilusVariant.right()); } } saveFloatData(StructuredDataKey.MINIMUM_ATTACK_CHARGE, dataContainer, backupTag); } private void saveDamageCondition(final CompoundTag tag, final String key, final KineticWeapon.@Nullable Condition condition) { if (condition == null) { return; } final CompoundTag conditionTag = new CompoundTag(); conditionTag.putInt("max_duration_ticks", condition.maxDurationTicks()); conditionTag.putFloat("min_speed", condition.minSpeed()); conditionTag.putFloat("min_relative_speed", condition.minRelativeSpeed()); tag.put(key, conditionTag); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_11to1_21_9/rewriter/ComponentRewriter1_21_11.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.text.NBTComponentRewriter; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.protocols.v1_21_9to1_21_11.packet.ClientboundPacket1_21_11; public final class ComponentRewriter1_21_11 extends NBTComponentRewriter { public ComponentRewriter1_21_11(final BackwardsProtocol protocol) { super(protocol); } @Override protected void handleShowItem(final UserConnection connection, final CompoundTag itemTag, final CompoundTag componentsTag) { super.handleShowItem(connection, itemTag, componentsTag); if (componentsTag == null) { return; } removeDataComponents(componentsTag, StructuredDataKey.SWING_ANIMATION, StructuredDataKey.KINETIC_WEAPON, StructuredDataKey.PIERCING_WEAPON, StructuredDataKey.DAMAGE_TYPE26_1, StructuredDataKey.MINIMUM_ATTACK_CHARGE, StructuredDataKey.USE_EFFECTS, StructuredDataKey.ZOMBIE_NAUTILUS_VARIANT1_21_11, StructuredDataKey.ATTACK_RANGE); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_11to1_21_9/rewriter/EntityPacketRewriter1_21_11.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.rewriter; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.Protocol1_21_11To1_21_9; import com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.storage.GameTimeStorage; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_11; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_11; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_9; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundPackets1_21_9; import com.viaversion.viaversion.protocols.v1_21_9to1_21_11.packet.ClientboundPacket1_21_11; import com.viaversion.viaversion.protocols.v1_21_9to1_21_11.packet.ClientboundPackets1_21_11; import com.viaversion.viaversion.rewriter.entitydata.EntityDataHandlerEvent; import com.viaversion.viaversion.util.MathUtil; public final class EntityPacketRewriter1_21_11 extends EntityRewriter { public EntityPacketRewriter1_21_11(final Protocol1_21_11To1_21_9 protocol) { super(protocol, VersionedTypes.V1_21_9.entityDataTypes.optionalComponentType, VersionedTypes.V1_21_9.entityDataTypes.booleanType); } @Override public void registerPackets() { protocol.registerClientbound(ClientboundPackets1_21_11.MOUNT_SCREEN_OPEN, ClientboundPackets1_21_9.HORSE_SCREEN_OPEN); protocol.registerClientbound(ClientboundPackets1_21_11.UPDATE_MOB_EFFECT, wrapper -> { wrapper.passthrough(Types.VAR_INT); // entity id final int effectId = wrapper.passthrough(Types.VAR_INT); if (effectId == 39) { // breath_of_the_nautilus wrapper.cancel(); } }); protocol.registerClientbound(ClientboundPackets1_21_11.REMOVE_MOB_EFFECT, wrapper -> { wrapper.passthrough(Types.VAR_INT); // entity id final int effectId = wrapper.passthrough(Types.VAR_INT); if (effectId == 39) { // breath_of_the_nautilus wrapper.cancel(); } }); } @Override protected void registerRewrites() { final EntityDataTypes1_21_11 unmappedDataTypes = VersionedTypes.V1_21_11.entityDataTypes; final EntityDataTypes1_21_9 entityDataTypes = VersionedTypes.V1_21_9.entityDataTypes; dataTypeMapper() .removed(unmappedDataTypes.zombieNautilusVariantType) .skip(unmappedDataTypes.humanoidArmType) .register(); filter().dataType(unmappedDataTypes.humanoidArmType).handler((event, data) -> { final int arm = data.value(); data.setTypeAndValue(entityDataTypes.byteType, (byte) arm); }); registerEntityDataTypeHandler1_20_3( entityDataTypes.itemType, entityDataTypes.blockStateType, entityDataTypes.optionalBlockStateType, entityDataTypes.particleType, entityDataTypes.particlesType, entityDataTypes.componentType, entityDataTypes.optionalComponentType ); filter().type(EntityTypes1_21_11.WOLF).index(21).handler(this::absoluteToRelativeTicks); filter().type(EntityTypes1_21_11.BEE).index(18).handler(this::absoluteToRelativeTicks); filter().type(EntityTypes1_21_11.ABSTRACT_NAUTILUS).cancel(17); // Tamable flags filter().type(EntityTypes1_21_11.ABSTRACT_NAUTILUS).cancel(18); // Owner UUID filter().type(EntityTypes1_21_11.ABSTRACT_NAUTILUS).cancel(19); // Dashing filter().type(EntityTypes1_21_11.ZOMBIE_NAUTILUS).cancel(20); // Variant } private void absoluteToRelativeTicks(final EntityDataHandlerEvent event, final EntityData data) { final long currentGameTime = event.user().get(GameTimeStorage.class).gameTime(); final long angerEndTime = data.value(); final int angerEndIn = (int) MathUtil.clamp(angerEndTime - currentGameTime, Integer.MIN_VALUE, Integer.MAX_VALUE); data.setTypeAndValue(VersionedTypes.V1_21_9.entityDataTypes.varIntType, Math.max(angerEndIn, 0)); } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); mapEntityTypeWithData(EntityTypes1_21_11.NAUTILUS, EntityTypes1_21_11.SQUID).tagName(); mapEntityTypeWithData(EntityTypes1_21_11.ZOMBIE_NAUTILUS, EntityTypes1_21_11.GLOW_SQUID).tagName(); mapEntityTypeWithData(EntityTypes1_21_11.CAMEL_HUSK, EntityTypes1_21_11.CAMEL).tagName(); mapEntityTypeWithData(EntityTypes1_21_11.PARCHED, EntityTypes1_21_11.SKELETON).tagName(); } @Override public EntityType typeFromId(final int type) { return EntityTypes1_21_11.getTypeFromId(type); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_11to1_21_9/storage/GameTimeStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_11to1_21_9.storage; import com.viaversion.viaversion.api.connection.StorableObject; public final class GameTimeStorage implements StorableObject { private long gameTime; public long gameTime() { return gameTime; } public void setGameTime(final long gameTime) { this.gameTime = gameTime; } public void incrementGameTime() { gameTime++; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/Protocol1_21_2To1_21.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_2to1_21; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.rewriter.BlockItemPacketRewriter1_21_2; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.rewriter.ComponentRewriter1_21_2; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.rewriter.EntityPacketRewriter1_21_2; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.rewriter.ParticleRewriter1_21_2; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.InventoryStateIdStorage; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.ItemTagStorage; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.PlayerStorage; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.RecipeStorage; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.SignStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_20_5; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.packet.State; 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; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.api.type.types.version.VersionedTypesHolder; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundConfigurationPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPacket1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundConfigurationPackets1_21; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPacket1_21; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21; import com.viaversion.viaversion.protocols.v1_21to1_21_2.Protocol1_21To1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ServerboundPacket1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ServerboundPackets1_21_2; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.util.ArrayUtil; import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap; public final class Protocol1_21_2To1_21 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.21.2", "1.21", Protocol1_21To1_21_2.class); private final EntityPacketRewriter1_21_2 entityRewriter = new EntityPacketRewriter1_21_2(this); private final BlockItemPacketRewriter1_21_2 itemRewriter = new BlockItemPacketRewriter1_21_2(this); private final ParticleRewriter1_21_2 particleRewriter = new ParticleRewriter1_21_2(this); private final JsonNBTComponentRewriter translatableRewriter = new ComponentRewriter1_21_2(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BackwardsRegistryRewriter registryDataRewriter = new BackwardsRegistryRewriter(this); private final BlockRewriter blockRewriter = BlockRewriter.for1_20_2(this, ChunkType1_20_2::new); public Protocol1_21_2To1_21() { super(ClientboundPacket1_21_2.class, ClientboundPacket1_21.class, ServerboundPacket1_21_2.class, ServerboundPacket1_20_5.class); } @Override protected void registerPackets() { super.registerPackets(); replaceClientbound(ClientboundPackets1_21_2.UPDATE_TAGS, this::storeTags); replaceClientbound(ClientboundConfigurationPackets1_21.UPDATE_TAGS, this::storeTags); registerServerbound(ServerboundPackets1_20_5.CLIENT_INFORMATION, this::clientInformation); registerServerbound(ServerboundConfigurationPackets1_20_5.CLIENT_INFORMATION, this::clientInformation); registerClientbound(ClientboundConfigurationPackets1_21.UPDATE_ENABLED_FEATURES, wrapper -> { final String[] enabledFeatures = wrapper.read(Types.STRING_ARRAY); wrapper.write(Types.STRING_ARRAY, ArrayUtil.add(enabledFeatures, "bundle")); }); cancelClientbound(ClientboundPackets1_21_2.MOVE_MINECART_ALONG_TRACK); // TODO registerClientbound(State.LOGIN, ClientboundLoginPackets.LOGIN_FINISHED, wrapper -> { wrapper.passthrough(Types.UUID); // UUID wrapper.passthrough(Types.STRING); // Name wrapper.passthrough(Types.PROFILE_PROPERTY_ARRAY); wrapper.write(Types.BOOLEAN, true); // Strict error handling. Always enabled for newer clients, so mimic that behavior }); registerClientbound(ClientboundPackets1_21_2.SET_TIME, wrapper -> { wrapper.passthrough(Types.LONG); // Game time long dayTime = wrapper.read(Types.LONG); final boolean daylightCycle = wrapper.read(Types.BOOLEAN); if (!daylightCycle) { if (dayTime == 0) { dayTime = -1; } else { dayTime = -dayTime; } } wrapper.write(Types.LONG, dayTime); }); } private void storeTags(final PacketWrapper wrapper) { tagRewriter.handleGeneric(wrapper); wrapper.resetReader(); wrapper.user().get(ItemTagStorage.class).readItemTags(wrapper); } private void clientInformation(final PacketWrapper wrapper) { wrapper.passthrough(Types.STRING); // Locale wrapper.passthrough(Types.BYTE); // View distance wrapper.passthrough(Types.VAR_INT); // Chat visibility wrapper.passthrough(Types.BOOLEAN); // Chat colors wrapper.passthrough(Types.UNSIGNED_BYTE); // Skin parts wrapper.passthrough(Types.VAR_INT); // Main hand wrapper.passthrough(Types.BOOLEAN); // Text filtering enabled wrapper.passthrough(Types.BOOLEAN); // Allow listing wrapper.write(Types.VAR_INT, 0); // Particle status, assume 'all' } @Override public void init(final UserConnection user) { addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_20_5.PLAYER)); user.put(new InventoryStateIdStorage()); user.put(new ItemTagStorage()); user.put(new RecipeStorage(this)); user.put(new PlayerStorage()); user.put(new SignStorage()); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_21_2 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_21_2 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter1_21_2 getParticleRewriter() { return particleRewriter; } @Override public BackwardsRegistryRewriter getRegistryDataRewriter() { return registryDataRewriter; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } @Override public VersionedTypesHolder types() { return VersionedTypes.V1_21_2; } @Override public VersionedTypesHolder mappedTypes() { return VersionedTypes.V1_21; } @Override protected PacketTypesProvider createPacketTypesProvider() { return new SimplePacketTypesProvider<>( packetTypeMap(unmappedClientboundPacketType, ClientboundPackets1_21_2.class, ClientboundConfigurationPackets1_21.class), packetTypeMap(mappedClientboundPacketType, ClientboundPackets1_21.class, ClientboundConfigurationPackets1_21.class), packetTypeMap(mappedServerboundPacketType, ServerboundPackets1_21_2.class, ServerboundConfigurationPackets1_20_5.class), packetTypeMap(unmappedServerboundPacketType, ServerboundPackets1_20_5.class, ServerboundConfigurationPackets1_20_5.class) ); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/rewriter/BlockItemPacketRewriter1_21_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_2to1_21.rewriter; import com.viaversion.nbt.tag.ByteTag; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.rewriters.BackwardsStructuredItemRewriter; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.Protocol1_21_2To1_21; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.InventoryStateIdStorage; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.RecipeStorage; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.SignStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.MappingData; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.minecraft.BlockChangeRecord; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.Holder; import com.viaversion.viaversion.api.minecraft.HolderSet; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.SoundEvent; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; import com.viaversion.viaversion.api.minecraft.chunks.DataPalette; import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.item.data.Consumable1_21_2; import com.viaversion.viaversion.api.minecraft.item.data.DeathProtection; import com.viaversion.viaversion.api.minecraft.item.data.Enchantable; import com.viaversion.viaversion.api.minecraft.item.data.Enchantments; import com.viaversion.viaversion.api.minecraft.item.data.Equippable; import com.viaversion.viaversion.api.minecraft.item.data.FoodProperties1_20_5; import com.viaversion.viaversion.api.minecraft.item.data.Instrument1_21_2; import com.viaversion.viaversion.api.minecraft.item.data.ItemModel; import com.viaversion.viaversion.api.minecraft.item.data.PotionEffect; import com.viaversion.viaversion.api.minecraft.item.data.PotionEffectData; import com.viaversion.viaversion.api.minecraft.item.data.Repairable; import com.viaversion.viaversion.api.minecraft.item.data.UseCooldown; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPacket1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2; import com.viaversion.viaversion.rewriter.SoundRewriter; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.Limit; import com.viaversion.viaversion.util.Unit; import static com.viaversion.viaversion.protocols.v1_21to1_21_2.rewriter.BlockItemPacketRewriter1_21_2.downgradeItemData; import static com.viaversion.viaversion.protocols.v1_21to1_21_2.rewriter.BlockItemPacketRewriter1_21_2.updateItemData; public final class BlockItemPacketRewriter1_21_2 extends BackwardsStructuredItemRewriter { public BlockItemPacketRewriter1_21_2(final Protocol1_21_2To1_21 protocol) { super(protocol); } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_21_2.LEVEL_CHUNK_WITH_LIGHT, wrapper -> { final Chunk chunk = protocol.getBlockRewriter().handleChunk1_18(wrapper); protocol.getBlockRewriter().handleBlockEntities(chunk, wrapper.user()); if (!wrapper.user().getProtocolInfo().protocolVersion().equalTo(ProtocolVersion.v1_21)) { return; } final EntityTracker tracker = wrapper.user().getEntityTracker(Protocol1_21_2To1_21.class); final SignStorage storage = wrapper.user().get(SignStorage.class); storage.removeSigns(chunk.getX(), chunk.getZ()); for (int i = 0; i < chunk.getSections().length; i++) { final ChunkSection section = chunk.getSections()[i]; final DataPalette blockPalette = section.palette(PaletteType.BLOCKS); boolean containsSign = false; for (int idx = 0; idx < blockPalette.size(); idx++) { if (signBlockState(blockPalette.idByIndex(idx))) { containsSign = true; break; } } if (!containsSign) { continue; } for (int idx = 0; idx < ChunkSection.SIZE; idx++) { if (!signBlockState(blockPalette.idAt(idx))) { continue; } storage.addSign(new BlockPosition( ChunkSection.xFromIndex(idx) + (chunk.getX() << 4), ChunkSection.yFromIndex(idx) + tracker.currentMinY() + (i << 4), ChunkSection.zFromIndex(idx) + (chunk.getZ() << 4) )); } } }); protocol.replaceClientbound(ClientboundPackets1_21_2.BLOCK_UPDATE, wrapper -> { final BlockPosition position = wrapper.passthrough(Types.BLOCK_POSITION1_14); final int blockId = wrapper.read(Types.VAR_INT); final int mappedBlockId = protocol.getMappingData().getNewBlockStateId(blockId); wrapper.write(Types.VAR_INT, mappedBlockId); if (!wrapper.user().getProtocolInfo().protocolVersion().equalTo(ProtocolVersion.v1_21)) { return; } final SignStorage storage = wrapper.user().get(SignStorage.class); storage.removeSign(position); if (signBlockState(mappedBlockId)) { storage.addSign(position); } }); protocol.replaceClientbound(ClientboundPackets1_21_2.SECTION_BLOCKS_UPDATE, wrapper -> { final long position = wrapper.passthrough(Types.LONG); final int chunkX = (int) (position >> 42); final int chunkY = (int) (position << 44 >> 44); final int chunkZ = (int) (position << 22 >> 42); final SignStorage signStorage = wrapper.user().get(SignStorage.class); final boolean equalToV1_21 = wrapper.user().getProtocolInfo().protocolVersion().equalTo(ProtocolVersion.v1_21); for (final BlockChangeRecord record : wrapper.passthrough(Types.VAR_LONG_BLOCK_CHANGE_ARRAY)) { record.setBlockId(protocol.getMappingData().getNewBlockStateId(record.getBlockId())); if (!equalToV1_21) { continue; } final int x = record.getSectionX() + (chunkX << 4); final int y = record.getSectionY() + (chunkY << 4); final int z = record.getSectionZ() + (chunkZ << 4); final BlockPosition pos = new BlockPosition(x, y, z); if (signBlockState(record.getBlockId())) { signStorage.addSign(pos); } else { signStorage.removeSign(pos); } } }); protocol.registerClientbound(ClientboundPackets1_21_2.OPEN_SIGN_EDITOR, wrapper -> { if (!wrapper.user().getProtocolInfo().protocolVersion().equalTo(ProtocolVersion.v1_21)) { return; } final BlockPosition position = wrapper.passthrough(Types.BLOCK_POSITION1_14); if (!wrapper.user().get(SignStorage.class).isSign(position)) { wrapper.cancel(); } }); protocol.replaceClientbound(ClientboundPackets1_21_2.COOLDOWN, wrapper -> { final MappingData mappingData = protocol.getMappingData(); final String itemIdentifier = wrapper.read(Types.STRING); final int id = mappingData.getFullItemMappings().id(itemIdentifier); if (id != -1) { final int mappedId = mappingData.getFullItemMappings().getNewId(id); wrapper.write(Types.VAR_INT, mappedId); } else { wrapper.cancel(); } }); protocol.registerClientbound(ClientboundPackets1_21_2.SET_CURSOR_ITEM, ClientboundPackets1_21.CONTAINER_SET_SLOT, wrapper -> { wrapper.write(Types.BYTE, (byte) -1); // Player inventory wrapper.write(Types.VAR_INT, wrapper.user().get(InventoryStateIdStorage.class).stateId()); // State id; re-use the last known one wrapper.write(Types.SHORT, (short) -1); // Cursor passthroughClientboundItem(wrapper); }); protocol.replaceClientbound(ClientboundPackets1_21_2.OPEN_SCREEN, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Id final int containerType = wrapper.passthrough(Types.VAR_INT); if (containerType == 21) { // Track smithing table to remove new data wrapper.user().get(InventoryStateIdStorage.class).setSmithingTableOpen(true); } protocol.getComponentRewriter().passthroughAndProcess(wrapper); }); protocol.replaceClientbound(ClientboundPackets1_21_2.CONTAINER_SET_CONTENT, wrapper -> { varIntToUnsignedByte(wrapper); final int stateId = wrapper.passthrough(Types.VAR_INT); wrapper.user().get(InventoryStateIdStorage.class).setStateId(stateId); final Item[] items = wrapper.read(itemArrayType()); wrapper.write(mappedItemArrayType(), items); for (int i = 0; i < items.length; i++) { items[i] = handleItemToClient(wrapper.user(), items[i]); } passthroughClientboundItem(wrapper); }); protocol.replaceClientbound(ClientboundPackets1_21_2.CONTAINER_SET_SLOT, wrapper -> { varIntToByte(wrapper); final int stateId = wrapper.passthrough(Types.VAR_INT); wrapper.user().get(InventoryStateIdStorage.class).setStateId(stateId); wrapper.passthrough(Types.SHORT); // Slot id passthroughClientboundItem(wrapper); }); protocol.registerClientbound(ClientboundPackets1_21_2.CONTAINER_SET_DATA, wrapper -> { varIntToUnsignedByte(wrapper); if (wrapper.user().get(InventoryStateIdStorage.class).smithingTableOpen()) { // Cancel new data for smithing table wrapper.cancel(); } }); protocol.registerClientbound(ClientboundPackets1_21_2.CONTAINER_CLOSE, wrapper -> { varIntToUnsignedByte(wrapper); wrapper.user().get(InventoryStateIdStorage.class).setSmithingTableOpen(false); }); protocol.registerClientbound(ClientboundPackets1_21_2.SET_HELD_SLOT, ClientboundPackets1_21.SET_CARRIED_ITEM); protocol.registerClientbound(ClientboundPackets1_21_2.HORSE_SCREEN_OPEN, this::varIntToUnsignedByte); protocol.registerServerbound(ServerboundPackets1_20_5.CONTAINER_CLOSE, wrapper -> { byteToVarInt(wrapper); wrapper.user().get(InventoryStateIdStorage.class).setSmithingTableOpen(false); }); protocol.replaceServerbound(ServerboundPackets1_20_5.CONTAINER_CLICK, wrapper -> { byteToVarInt(wrapper); wrapper.passthrough(Types.VAR_INT); // State id wrapper.passthrough(Types.SHORT); // Slot wrapper.passthrough(Types.BYTE); // Button wrapper.passthrough(Types.VAR_INT); // Mode final int length = Limit.max(wrapper.passthrough(Types.VAR_INT), 128); for (int i = 0; i < length; i++) { wrapper.passthrough(Types.SHORT); // Slot wrapper.write(itemType(), handleItemToServer(wrapper.user(), wrapper.read(mappedItemType()))); } wrapper.write(itemType(), handleItemToServer(wrapper.user(), wrapper.read(mappedItemType()))); }); protocol.registerServerbound(ServerboundPackets1_20_5.USE_ITEM_ON, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Hand wrapper.passthrough(Types.BLOCK_POSITION1_14); // Block position wrapper.passthrough(Types.VAR_INT); // Direction wrapper.passthrough(Types.FLOAT); // X wrapper.passthrough(Types.FLOAT); // Y wrapper.passthrough(Types.FLOAT); // Z wrapper.passthrough(Types.BOOLEAN); // Inside wrapper.write(Types.BOOLEAN, false); // World border hit }); protocol.replaceClientbound(ClientboundPackets1_21_2.EXPLODE, wrapper -> { wrapper.passthrough(Types.DOUBLE); // Center X wrapper.passthrough(Types.DOUBLE); // Center Y wrapper.passthrough(Types.DOUBLE); // Center Z // The server will already send block changes separately wrapper.write(Types.FLOAT, 0F); // Power wrapper.write(Types.VAR_INT, 0); // No blocks affected double knockbackX = 0; double knockbackY = 0; double knockbackZ = 0; if (wrapper.read(Types.BOOLEAN)) { knockbackX = wrapper.read(Types.DOUBLE); knockbackY = wrapper.read(Types.DOUBLE); knockbackZ = wrapper.read(Types.DOUBLE); } wrapper.write(Types.FLOAT, (float) knockbackX); wrapper.write(Types.FLOAT, (float) knockbackY); wrapper.write(Types.FLOAT, (float) knockbackZ); wrapper.write(Types.VAR_INT, 0); // Block interaction type final Particle explosionParticle = wrapper.read(VersionedTypes.V1_21.particle()); protocol.getParticleRewriter().rewriteParticle(wrapper.user(), explosionParticle); // As small and large explosion particle wrapper.write(VersionedTypes.V1_21_2.particle(), explosionParticle); wrapper.write(VersionedTypes.V1_21_2.particle(), explosionParticle); new SoundRewriter<>(protocol).soundHolderHandler().handle(wrapper); }); protocol.registerClientbound(ClientboundPackets1_21_2.RECIPE_BOOK_ADD, null, wrapper -> { final RecipeStorage recipeStorage = wrapper.user().get(RecipeStorage.class); final int size = wrapper.read(Types.VAR_INT); for (int i = 0; i < size; i++) { recipeStorage.readRecipe(wrapper); } final boolean replace = wrapper.read(Types.BOOLEAN); if (replace) { recipeStorage.clearRecipes(); } recipeStorage.sendRecipes(wrapper.user()); wrapper.cancel(); }); protocol.registerClientbound(ClientboundPackets1_21_2.RECIPE_BOOK_REMOVE, ClientboundPackets1_21.RECIPE, wrapper -> { final RecipeStorage recipeStorage = wrapper.user().get(RecipeStorage.class); final int[] ids = wrapper.read(Types.VAR_INT_ARRAY_PRIMITIVE); recipeStorage.lockRecipes(wrapper, ids); }); protocol.registerClientbound(ClientboundPackets1_21_2.RECIPE_BOOK_SETTINGS, null, wrapper -> { final RecipeStorage recipeStorage = wrapper.user().get(RecipeStorage.class); final boolean[] settings = new boolean[RecipeStorage.RECIPE_BOOK_SETTINGS]; for (int i = 0; i < RecipeStorage.RECIPE_BOOK_SETTINGS; i++) { settings[i] = wrapper.read(Types.BOOLEAN); } recipeStorage.setRecipeBookSettings(settings); wrapper.cancel(); }); protocol.registerClientbound(ClientboundPackets1_21_2.UPDATE_RECIPES, wrapper -> { // Inputs for furnaces etc. Old clients get these from the full recipes final int size = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < size; i++) { wrapper.read(Types.STRING); // Recipe group wrapper.read(Types.VAR_INT_ARRAY_PRIMITIVE); // Items } final RecipeStorage recipeStorage = wrapper.user().get(RecipeStorage.class); recipeStorage.readStoneCutterRecipes(wrapper); // Send later with the recipe book init wrapper.cancel(); }); protocol.registerClientbound(ClientboundPackets1_21_2.PLACE_GHOST_RECIPE, wrapper -> { this.varIntToByte(wrapper); wrapper.cancel(); // Full recipe display, this doesn't look mappable }); protocol.registerServerbound(ServerboundPackets1_20_5.PLACE_RECIPE, wrapper -> { this.byteToVarInt(wrapper); final String recipe = Key.stripMinecraftNamespace(wrapper.read(Types.STRING)); wrapper.write(Types.VAR_INT, Integer.parseInt(recipe)); }); protocol.registerServerbound(ServerboundPackets1_20_5.RECIPE_BOOK_SEEN_RECIPE, wrapper -> { final String recipe = Key.stripMinecraftNamespace(wrapper.read(Types.STRING)); wrapper.write(Types.VAR_INT, Integer.parseInt(recipe)); }); protocol.registerClientbound(ClientboundPackets1_21_2.SET_PLAYER_INVENTORY, ClientboundPackets1_21.CONTAINER_SET_SLOT, wrapper -> { wrapper.write(Types.BYTE, (byte) -2); // Player inventory wrapper.write(Types.VAR_INT, 0); // 0 state id final int slot = wrapper.read(Types.VAR_INT); wrapper.write(Types.SHORT, (short) slot); passthroughClientboundItem(wrapper); }); } private void varIntToUnsignedByte(final PacketWrapper wrapper) { final int containerId = wrapper.read(Types.VAR_INT); wrapper.write(Types.UNSIGNED_BYTE, (short) containerId); } private void varIntToByte(final PacketWrapper wrapper) { final int containerId = wrapper.read(Types.VAR_INT); wrapper.write(Types.BYTE, (byte) containerId); } private void byteToVarInt(final PacketWrapper wrapper) { final byte containerId = wrapper.read(Types.BYTE); wrapper.write(Types.VAR_INT, (int) containerId); } private boolean signBlockState(final int blockStateId) { return (blockStateId >= 4302 && blockStateId <= 4589) // Normal signs || (blockStateId >= 4762 && blockStateId <= 5625) // Wall & Hanging signs || (blockStateId >= 19276 && blockStateId <= 19355); // Warped & Crimson signs } @Override public Item handleItemToClient(final UserConnection connection, Item item) { backupInconvertibleData(item); item = super.handleItemToClient(connection, item); downgradeItemData(item); return item; } @Override public Item handleItemToServer(final UserConnection connection, Item item) { item = super.handleItemToServer(connection, item); // Handle food properties item manually here - the only protocol that has it // The other way around it's handled by the super handleItemToClient method final StructuredDataContainer data = item.dataContainer(); final FoodProperties1_20_5 food = data.get(StructuredDataKey.FOOD1_21); if (food != null && food.usingConvertsTo() != null) { this.handleItemToServer(connection, food.usingConvertsTo()); } updateItemData(item); restoreInconvertibleData(item); // Enchantments with level 0 are not supported anymore, if an older client for some reason sends it (for example // using stored items in the hotbar lists), we need to remove it. final Enchantments enchantments = data.get(StructuredDataKey.ENCHANTMENTS1_20_5); if (enchantments != null) { // The enchantments might be used to create a glint, set glint override to true if the enchantments are empty after filtering final boolean removed = enchantments.enchantments().values().removeIf(level -> level == 0); if (removed && enchantments.size() == 0) { data.set(StructuredDataKey.ENCHANTMENT_GLINT_OVERRIDE, true); } } final Enchantments storedEnchantments = data.get(StructuredDataKey.STORED_ENCHANTMENTS1_20_5); if (storedEnchantments != null) { storedEnchantments.enchantments().values().removeIf(level -> level == 0); } return item; } // Backup inconvertible data and later restore to prevent data loss for creative mode clients private void backupInconvertibleData(final Item item) { final StructuredDataContainer data = item.dataContainer(); data.setIdLookup(protocol, true); final CompoundTag backupTag = new CompoundTag(); final Holder instrument = data.get(StructuredDataKey.INSTRUMENT1_21_2); if (instrument != null && instrument.isDirect()) { backupTag.put("instrument_description", instrument.value().description()); } final Repairable repairable = data.get(StructuredDataKey.REPAIRABLE); if (repairable != null) { backupTag.put("repairable", holderSetToTag(repairable.items())); } final Enchantable enchantable = data.get(StructuredDataKey.ENCHANTABLE); if (enchantable != null) { backupTag.putInt("enchantable", enchantable.value()); } final UseCooldown useCooldown = data.get(StructuredDataKey.USE_COOLDOWN); if (useCooldown != null) { final CompoundTag tag = new CompoundTag(); tag.putFloat("seconds", useCooldown.seconds()); if (useCooldown.cooldownGroup() != null) { tag.putString("cooldown_group", useCooldown.cooldownGroup()); } backupTag.put("use_cooldown", tag); } final ItemModel itemModel = data.get(StructuredDataKey.ITEM_MODEL); if (itemModel != null) { backupTag.putString("item_model", itemModel.key().original()); } final Equippable equippable = data.get(StructuredDataKey.EQUIPPABLE1_21_2); if (equippable != null) { final CompoundTag tag = new CompoundTag(); tag.putInt("equipment_slot", equippable.equipmentSlot()); saveSoundEventHolder(tag, equippable.soundEvent()); final String model = equippable.model(); if (model != null) { tag.putString("model", model); } final String cameraOverlay = equippable.cameraOverlay(); if (cameraOverlay != null) { tag.putString("camera_overlay", cameraOverlay); } if (equippable.allowedEntities() != null) { tag.put("allowed_entities", holderSetToTag(equippable.allowedEntities())); } tag.putBoolean("dispensable", equippable.dispensable()); tag.putBoolean("swappable", equippable.swappable()); tag.putBoolean("damage_on_hurt", equippable.damageOnHurt()); backupTag.put("equippable", tag); } final Unit glider = data.get(StructuredDataKey.GLIDER); if (glider != null) { backupTag.putBoolean("glider", true); } final Key tooltipStyle = data.get(StructuredDataKey.TOOLTIP_STYLE); if (tooltipStyle != null) { backupTag.putString("tooltip_style", tooltipStyle.original()); } final DeathProtection deathProtection = data.get(StructuredDataKey.DEATH_PROTECTION); if (deathProtection != null) { final ListTag tag = new ListTag<>(CompoundTag.class); for (final Consumable1_21_2.ConsumeEffect effect : deathProtection.deathEffects()) { final CompoundTag effectTag = new CompoundTag(); convertConsumableEffect(effectTag, effect); tag.add(effectTag); } backupTag.put("death_protection", tag); } if (!backupTag.isEmpty()) { saveTag(createCustomTag(item), backupTag, "inconvertible_data"); } } private void convertConsumableEffect(final CompoundTag tag, Consumable1_21_2.ConsumeEffect effect) { tag.putInt("id", effect.id()); if (effect.type() == Consumable1_21_2.ApplyStatusEffects.TYPE && effect.value() instanceof Consumable1_21_2.ApplyStatusEffects value) { tag.putString("type", "apply_effects"); final ListTag effects = new ListTag<>(CompoundTag.class); for (final PotionEffect potionEffect : value.effects()) { final CompoundTag effectTag = new CompoundTag(); effectTag.putInt("effect", potionEffect.effect()); convertPotionEffectData(effectTag, potionEffect.effectData()); effects.add(effectTag); } tag.put("effects", effects); tag.putFloat("probability", value.probability()); } else if (effect.type() == Types.HOLDER_SET && effect.value() instanceof HolderSet set) { tag.putString("type", "remove_effects"); tag.put("remove_effects", holderSetToTag(set)); } else if (effect.type() == Types.EMPTY) { tag.putString("type", "clear_all_effects"); } else if (effect.type() == Types.FLOAT) { tag.putString("type", "teleport_randomly"); tag.putFloat("probability", (Float) effect.value()); } else if (effect.type() == Types.SOUND_EVENT && effect.value() instanceof Holder sound) { tag.putString("type", "play_sound"); saveSoundEventHolder(tag, sound); } } private void convertPotionEffectData(final CompoundTag tag, final PotionEffectData data) { tag.putInt("amplifier", data.amplifier()); tag.putInt("duration", data.duration()); tag.putBoolean("ambient", data.ambient()); tag.putBoolean("show_particles", data.showParticles()); tag.putBoolean("show_icon", data.showIcon()); if (data.hiddenEffect() != null) { final CompoundTag hiddenEffect = new CompoundTag(); convertPotionEffectData(hiddenEffect, data.hiddenEffect()); tag.put("hidden_effect", hiddenEffect); } } private Consumable1_21_2.ConsumeEffect convertConsumableEffect(final CompoundTag tag) { final int id = tag.getInt("id"); final String type = tag.getString("type"); if ("apply_effects".equals(type)) { final ListTag effects = tag.getListTag("effects", CompoundTag.class); final PotionEffect[] potionEffects = new PotionEffect[effects.size()]; for (int i = 0; i < effects.size(); i++) { final CompoundTag effectTag = effects.get(i); final int effect = effectTag.getInt("effect"); final PotionEffectData data = convertPotionEffectData(effectTag); potionEffects[i] = new PotionEffect(effect, data); } final float probability = tag.getFloat("probability"); return new Consumable1_21_2.ConsumeEffect<>(id, Consumable1_21_2.ApplyStatusEffects.TYPE, new Consumable1_21_2.ApplyStatusEffects(potionEffects, probability)); } else if ("remove_effects".equals(type)) { final HolderSet set = restoreHolderSet(tag, "remove_effects"); return new Consumable1_21_2.ConsumeEffect<>(id, Types.HOLDER_SET, set); } else if ("clear_all_effects".equals(type)) { return new Consumable1_21_2.ConsumeEffect<>(id, Types.EMPTY, Unit.INSTANCE); } else if ("teleport_randomly".equals(type)) { final float probability = tag.getFloat("probability"); return new Consumable1_21_2.ConsumeEffect<>(id, Types.FLOAT, probability); } else if ("play_sound".equals(type)) { final Holder sound = restoreSoundEventHolder(tag); return new Consumable1_21_2.ConsumeEffect<>(id, Types.SOUND_EVENT, sound); } return null; } private PotionEffectData convertPotionEffectData(final CompoundTag tag) { final int amplifier = tag.getInt("amplifier"); final int duration = tag.getInt("duration"); final boolean ambient = tag.getBoolean("ambient"); final boolean showParticles = tag.getBoolean("show_particles"); final boolean showIcon = tag.getBoolean("show_icon"); final CompoundTag hiddenEffect = tag.getCompoundTag("hidden_effect"); return new PotionEffectData(amplifier, duration, ambient, showParticles, showIcon, hiddenEffect != null ? convertPotionEffectData(hiddenEffect) : null); } private void restoreInconvertibleData(final Item item) { final StructuredDataContainer data = item.dataContainer(); final CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA); if (customData == null || !(customData.remove(nbtTagName("inconvertible_data")) instanceof CompoundTag backupTag)) { return; } final Holder instrument = data.get(StructuredDataKey.INSTRUMENT1_21_2); if (instrument != null && instrument.isDirect()) { final Tag description = backupTag.get("instrument_description"); if (description != null) { final Instrument1_21_2 delegate = instrument.value(); data.set(StructuredDataKey.INSTRUMENT1_21_2, Holder.of(new Instrument1_21_2(delegate.soundEvent(), delegate.useDuration(), delegate.range(), description))); } } if (backupTag.contains("repairable")) { data.set(StructuredDataKey.REPAIRABLE, new Repairable(restoreHolderSet(backupTag, "repairable"))); } final IntTag enchantable = backupTag.getIntTag("enchantable"); if (enchantable != null) { data.set(StructuredDataKey.ENCHANTABLE, new Enchantable(enchantable.asInt())); } final CompoundTag useCooldown = backupTag.getCompoundTag("use_cooldown"); if (useCooldown != null) { final float seconds = useCooldown.getFloat("seconds"); final String cooldownGroup = useCooldown.getString("cooldown_group"); data.set(StructuredDataKey.USE_COOLDOWN, new UseCooldown(seconds, cooldownGroup)); } final String itemModel = backupTag.getString("item_model"); if (itemModel != null) { data.set(StructuredDataKey.ITEM_MODEL, new ItemModel(Key.of(itemModel))); } final CompoundTag equippable = backupTag.getCompoundTag("equippable"); if (equippable != null) { final int equipmentSlot = equippable.getInt("equipment_slot"); final Holder soundEvent = restoreSoundEventHolder(equippable); final String model = equippable.getString("model"); final String cameraOverlay = equippable.getString("camera_overlay"); final HolderSet allowedEntities = equippable.contains("allowed_entities") ? restoreHolderSet(equippable, "allowed_entities") : null; final boolean dispensable = equippable.getBoolean("dispensable"); final boolean swappable = equippable.getBoolean("swappable"); final boolean damageOnHurt = equippable.getBoolean("damage_on_hurt"); data.set(StructuredDataKey.EQUIPPABLE1_21_2, new Equippable(equipmentSlot, soundEvent, model, cameraOverlay, allowedEntities, dispensable, swappable, damageOnHurt)); } final ByteTag glider = backupTag.getByteTag("glider"); if (glider != null) { data.set(StructuredDataKey.GLIDER, Unit.INSTANCE); } final String tooltipStyle = backupTag.getString("tooltip_style"); if (tooltipStyle != null) { data.set(StructuredDataKey.TOOLTIP_STYLE, Key.of(tooltipStyle)); } final ListTag deathProtection = backupTag.getListTag("death_protection", CompoundTag.class); if (deathProtection != null) { final Consumable1_21_2.ConsumeEffect[] effects = new Consumable1_21_2.ConsumeEffect[deathProtection.size()]; for (int i = 0; i < deathProtection.size(); i++) { effects[i] = convertConsumableEffect(deathProtection.get(i)); } data.set(StructuredDataKey.DEATH_PROTECTION, new DeathProtection(effects)); } removeCustomTag(data, customData); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/rewriter/ComponentRewriter1_21_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_2to1_21.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.Protocol1_21_2To1_21; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.rewriter.BlockItemPacketRewriter1_21_2; import com.viaversion.viaversion.util.SerializerVersion; import com.viaversion.viaversion.util.TagUtil; public final class ComponentRewriter1_21_2 extends JsonNBTComponentRewriter { public ComponentRewriter1_21_2(final Protocol1_21_2To1_21 protocol) { super(protocol, ReadType.NBT); } @Override protected void handleShowItem(final UserConnection connection, final CompoundTag itemTag, final CompoundTag componentsTag) { super.handleShowItem(connection, itemTag, componentsTag); if (componentsTag == null) { return; } com.viaversion.viaversion.protocols.v1_21to1_21_2.rewriter.ComponentRewriter1_21_2.convertAttributes(componentsTag, protocol.getMappingData().getAttributeMappings()); final CompoundTag instrument = TagUtil.getNamespacedCompoundTag(componentsTag, "instrument"); if (instrument != null) { instrument.remove("description"); } final CompoundTag useRemainder = TagUtil.getNamespacedCompoundTag(componentsTag, "use_remainder"); final CompoundTag food = TagUtil.getNamespacedCompoundTag(componentsTag, "food"); if (food != null) { if (useRemainder != null) { food.put("using_converts_to", useRemainder); } food.putFloat("eat_seconds", 1.6F); } removeDataComponents(componentsTag, BlockItemPacketRewriter1_21_2.NEW_DATA_TO_REMOVE); removeDataComponents(componentsTag, StructuredDataKey.DAMAGE_RESISTANT1_21_2, StructuredDataKey.LOCK1_21_2); // No point in updating these } @Override protected SerializerVersion inputSerializerVersion() { return SerializerVersion.V1_20_5; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/rewriter/EntityPacketRewriter1_21_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_2to1_21.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.FloatTag; import com.viaversion.nbt.tag.IntTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.Protocol1_21_2To1_21; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.PlayerStorage; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.SignStorage; import com.viaversion.viabackwards.utils.VelocityUtil; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.minecraft.Holder; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.RegistryEntry; import com.viaversion.viaversion.api.minecraft.SoundEvent; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_2; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundConfigurationPackets1_21; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ServerboundPackets1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.storage.ClientVehicleStorage; import com.viaversion.viaversion.util.Key; import java.util.ArrayList; import java.util.BitSet; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; public final class EntityPacketRewriter1_21_2 extends EntityRewriter { private static final int REL_X = 0; private static final int REL_Y = 1; private static final int REL_Z = 2; private static final int REL_Y_ROT = 3; private static final int REL_X_ROT = 4; private static final int REL_DELTA_X = 5; private static final int REL_DELTA_Y = 6; private static final int REL_DELTA_Z = 7; private static final int REL_ROTATE_DELTA = 8; private boolean warned = ViaBackwards.getConfig().suppressEmulationWarnings(); public EntityPacketRewriter1_21_2(final Protocol1_21_2To1_21 protocol) { super(protocol, VersionedTypes.V1_21.entityDataTypes.optionalComponentType, VersionedTypes.V1_21.entityDataTypes.booleanType); } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_21_2.ADD_ENTITY, wrapper -> { final int entityId = wrapper.passthrough(Types.VAR_INT); wrapper.passthrough(Types.UUID); // Entity UUID final int entityTypeId = wrapper.passthrough(Types.VAR_INT); wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z wrapper.passthrough(Types.BYTE); // Pitch wrapper.passthrough(Types.BYTE); // Yaw wrapper.passthrough(Types.BYTE); // Head yaw wrapper.passthrough(Types.VAR_INT); // Data getSpawnTrackerWithDataHandler1_19().handle(wrapper); final EntityType type = EntityTypes1_21_2.getTypeFromId(entityTypeId); if (type.isOrHasParent(EntityTypes1_21_2.ABSTRACT_BOAT)) { wrapper.send(Protocol1_21_2To1_21.class); wrapper.cancel(); // Add boat type to entity data final List data = new ArrayList<>(); final int boatType = type.isOrHasParent(EntityTypes1_21_2.ABSTRACT_CHEST_BOAT) ? chestBoatTypeFromEntityType(type) : boatTypeFromEntityType(type); data.add(new EntityData(11, VersionedTypes.V1_21.entityDataTypes.varIntType, boatType)); final PacketWrapper entityDataPacket = wrapper.create(ClientboundPackets1_21.SET_ENTITY_DATA); entityDataPacket.write(Types.VAR_INT, entityId); entityDataPacket.write(VersionedTypes.V1_21.entityDataList, data); entityDataPacket.send(Protocol1_21_2To1_21.class); } }); protocol.getRegistryDataRewriter().addEnchantmentEffectRewriter("change_item_damage", tag -> tag.putString("type", "damage_item")); protocol.replaceClientbound(ClientboundConfigurationPackets1_21.REGISTRY_DATA, wrapper -> { final String registryKey = Key.stripMinecraftNamespace(wrapper.passthrough(Types.STRING)); final RegistryEntry[] entries = wrapper.read(Types.REGISTRY_ENTRY_ARRAY); if (registryKey.equals("instrument")) { wrapper.cancel(); return; } if (registryKey.equals("worldgen/biome")) { for (final RegistryEntry entry : entries) { if (entry.tag() == null) { continue; } final CompoundTag effects = ((CompoundTag) entry.tag()).getCompoundTag("effects"); final CompoundTag particle = effects.getCompoundTag("particle"); if (particle == null) { continue; } final CompoundTag particleOptions = particle.getCompoundTag("options"); final String particleType = particleOptions.getString("type"); updateParticleFormat(particleOptions, Key.stripMinecraftNamespace(particleType)); } } wrapper.write(Types.REGISTRY_ENTRY_ARRAY, protocol.getRegistryDataRewriter().handle(wrapper.user(), registryKey, entries)); }); protocol.replaceClientbound(ClientboundPackets1_21_2.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // Entity id map(Types.BOOLEAN); // Hardcore map(Types.STRING_ARRAY); // World List map(Types.VAR_INT); // Max players map(Types.VAR_INT); // View distance map(Types.VAR_INT); // Simulation distance map(Types.BOOLEAN); // Reduced debug info map(Types.BOOLEAN); // Show death screen map(Types.BOOLEAN); // Limited crafting map(Types.VAR_INT); // Dimension key map(Types.STRING); // World map(Types.LONG); // Seed map(Types.BYTE); // Gamemode map(Types.BYTE); // Previous gamemode map(Types.BOOLEAN); // Debug map(Types.BOOLEAN); // Flat map(Types.OPTIONAL_GLOBAL_POSITION); // Last death location map(Types.VAR_INT); // Portal cooldown handler(worldDataTrackerHandlerByKey1_20_5(3)); handler(playerTrackerHandler()); read(Types.VAR_INT); // Sea level } }); protocol.replaceClientbound(ClientboundPackets1_21_2.RESPAWN, wrapper -> { final int dimensionId = wrapper.passthrough(Types.VAR_INT); final String world = wrapper.passthrough(Types.STRING); wrapper.passthrough(Types.LONG); // Seed wrapper.passthrough(Types.BYTE); // Gamemode wrapper.passthrough(Types.BYTE); // Previous gamemode wrapper.passthrough(Types.BOOLEAN); // Debug wrapper.passthrough(Types.BOOLEAN); // Flat wrapper.passthrough(Types.OPTIONAL_GLOBAL_POSITION); // Last death location wrapper.passthrough(Types.VAR_INT); // Portal cooldown wrapper.read(Types.VAR_INT); // Sea level final EntityTracker tracker = tracker(wrapper.user()); if (tracker.currentWorld() != null && !tracker.currentWorld().equals(world)) { wrapper.user().put(new SignStorage()); } trackWorldDataByKey1_20_5(wrapper.user(), dimensionId, world); }); protocol.registerClientbound(ClientboundPackets1_21_2.ENTITY_POSITION_SYNC, ClientboundPackets1_21.TELEPORT_ENTITY, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Entity ID wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z // Unused wrapper.read(Types.DOUBLE); // Delta movement X wrapper.read(Types.DOUBLE); // Delta movement Y wrapper.read(Types.DOUBLE); // Delta movement Z final float yaw = wrapper.read(Types.FLOAT); final float pitch = wrapper.read(Types.FLOAT); writePackedRotation(wrapper, yaw, pitch); }); protocol.registerClientbound(ClientboundPackets1_21_2.PLAYER_ROTATION, ClientboundPackets1_21.PLAYER_LOOK_AT, wrapper -> { final float yaw = wrapper.read(Types.FLOAT); final float pitch = wrapper.read(Types.FLOAT); final double yRadians = Math.toRadians(yaw); final double xRadians = Math.toRadians(pitch); final double factor = -Math.cos(-xRadians); final double deltaX = Math.sin(-yRadians - (float) Math.PI) * factor; final double deltaY = Math.sin(-xRadians); final double deltaZ = Math.cos(-yRadians - (float) Math.PI) * factor; final PlayerStorage storage = wrapper.user().get(PlayerStorage.class); wrapper.write(Types.VAR_INT, 0); // From anchor wrapper.write(Types.DOUBLE, storage.x() + deltaX); // X wrapper.write(Types.DOUBLE, storage.y() + deltaY); // Y wrapper.write(Types.DOUBLE, storage.z() + deltaZ); // Z wrapper.write(Types.BOOLEAN, false); // At entity final PacketWrapper entityMotionPacket = PacketWrapper.create(ServerboundPackets1_21_2.MOVE_PLAYER_ROT, wrapper.user()); entityMotionPacket.write(Types.FLOAT, yaw); entityMotionPacket.write(Types.FLOAT, pitch); entityMotionPacket.write(Types.UNSIGNED_BYTE, (short) 0); // On ground and horizontal collision entityMotionPacket.sendToServer(Protocol1_21_2To1_21.class); }); protocol.registerClientbound(ClientboundPackets1_21_2.TELEPORT_ENTITY, wrapper -> { final int entityId = wrapper.passthrough(Types.VAR_INT); final double x = wrapper.passthrough(Types.DOUBLE); final double y = wrapper.passthrough(Types.DOUBLE); final double z = wrapper.passthrough(Types.DOUBLE); final double movementX = wrapper.read(Types.DOUBLE); final double movementY = wrapper.read(Types.DOUBLE); final double movementZ = wrapper.read(Types.DOUBLE); final float yaw = wrapper.read(Types.FLOAT); final float pitch = wrapper.read(Types.FLOAT); writePackedRotation(wrapper, yaw, pitch); final int relativeArguments = wrapper.read(Types.INT); // Send alongside separate entity motion wrapper.send(Protocol1_21_2To1_21.class); wrapper.cancel(); handleRelativeArguments(wrapper, x, y, z, yaw, pitch, relativeArguments, movementX, movementY, movementZ, entityId); }); protocol.registerClientbound(ClientboundPackets1_21_2.PLAYER_POSITION, wrapper -> { final int teleportId = wrapper.read(Types.VAR_INT); final double x = wrapper.passthrough(Types.DOUBLE); final double y = wrapper.passthrough(Types.DOUBLE); final double z = wrapper.passthrough(Types.DOUBLE); final double movementX = wrapper.read(Types.DOUBLE); final double movementY = wrapper.read(Types.DOUBLE); final double movementZ = wrapper.read(Types.DOUBLE); final float yaw = wrapper.passthrough(Types.FLOAT); final float pitch = wrapper.passthrough(Types.FLOAT); // Just keep the new values in there final int relativeArguments = wrapper.read(Types.INT); wrapper.write(Types.BYTE, (byte) relativeArguments); wrapper.write(Types.VAR_INT, teleportId); // Send alongside separate entity motion wrapper.send(Protocol1_21_2To1_21.class); wrapper.cancel(); handleRelativeArguments(wrapper, x, y, z, yaw, pitch, relativeArguments, movementX, movementY, movementZ, null); }); protocol.registerServerbound(ServerboundPackets1_20_5.PLAYER_COMMAND, wrapper -> { wrapper.passthrough(Types.VAR_INT); final int action = wrapper.passthrough(Types.VAR_INT); final PlayerStorage storage = wrapper.user().get(PlayerStorage.class); if (action == 0) { storage.setPlayerCommandTrackedSneaking(true); } else if (action == 1) { storage.setPlayerCommandTrackedSneaking(false); } else if (action == 3) { storage.setPlayerCommandTrackedSprinting(true); } else if (action == 4) { storage.setPlayerCommandTrackedSprinting(false); } }); // Now also sent by the player if not in a vehicle, but we can't emulate that here, and otherwise only used in predicates protocol.registerServerbound(ServerboundPackets1_20_5.PLAYER_INPUT, wrapper -> { final float sideways = wrapper.read(Types.FLOAT); final float forward = wrapper.read(Types.FLOAT); final byte flags = wrapper.read(Types.BYTE); byte updatedFlags = 0; if (forward > 0) { updatedFlags |= 1; // Forward } else if (forward < 0) { updatedFlags |= 1 << 1; // Backward } if (sideways > 0) { updatedFlags |= 1 << 2; // Left } else if (sideways < 0) { updatedFlags |= 1 << 3; // Right } if ((flags & 1) != 0) { // Jumping updatedFlags |= 1 << 4; } final boolean sneaking = (flags & 2) != 0; if (sneaking) { updatedFlags |= 1 << 5; } // Sprinting we don't know... wrapper.write(Types.BYTE, updatedFlags); // 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) sendSneakingPlayerCommand(wrapper, sneaking); }); protocol.registerServerbound(ServerboundPackets1_20_5.MOVE_PLAYER_POS, wrapper -> { final double x = wrapper.passthrough(Types.DOUBLE); final double y = wrapper.passthrough(Types.DOUBLE); final double z = wrapper.passthrough(Types.DOUBLE); fixOnGround(wrapper); final PlayerStorage storage = wrapper.user().get(PlayerStorage.class); storage.setPosition(x, y, z); }); protocol.registerServerbound(ServerboundPackets1_20_5.MOVE_PLAYER_POS_ROT, wrapper -> { final double x = wrapper.passthrough(Types.DOUBLE); final double y = wrapper.passthrough(Types.DOUBLE); final double z = wrapper.passthrough(Types.DOUBLE); final float yaw = wrapper.passthrough(Types.FLOAT); final float pitch = wrapper.passthrough(Types.FLOAT); fixOnGround(wrapper); final PlayerStorage storage = wrapper.user().get(PlayerStorage.class); storage.setPosition(x, y, z); storage.setRotation(yaw, pitch); }); protocol.registerServerbound(ServerboundPackets1_20_5.MOVE_PLAYER_ROT, wrapper -> { final float yaw = wrapper.passthrough(Types.FLOAT); final float pitch = wrapper.passthrough(Types.FLOAT); fixOnGround(wrapper); final PlayerStorage storage = wrapper.user().get(PlayerStorage.class); storage.setRotation(yaw, pitch); }); protocol.registerServerbound(ServerboundPackets1_20_5.MOVE_PLAYER_STATUS_ONLY, this::fixOnGround); protocol.registerServerbound(ServerboundPackets1_20_5.MOVE_VEHICLE, wrapper -> { final double x = wrapper.passthrough(Types.DOUBLE); final double y = wrapper.passthrough(Types.DOUBLE); final double z = wrapper.passthrough(Types.DOUBLE); final float yaw = wrapper.passthrough(Types.FLOAT); final float pitch = wrapper.passthrough(Types.FLOAT); final PlayerStorage storage = wrapper.user().get(PlayerStorage.class); storage.setPosition(x, y, z); storage.setRotation(yaw, pitch); }); protocol.replaceClientbound(ClientboundPackets1_21_2.PLAYER_INFO_UPDATE, wrapper -> { final BitSet actions = wrapper.read(Types.PROFILE_ACTIONS_ENUM1_21_2); // We need to recreate the BitSet field itself to remove the new action final BitSet updatedActions = new BitSet(6); for (int i = 0; i < 6; i++) { if (actions.get(i)) { updatedActions.set(i); } } wrapper.write(Types.PROFILE_ACTIONS_ENUM1_19_3, updatedActions); final int entries = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < entries; i++) { wrapper.passthrough(Types.UUID); if (actions.get(0)) { wrapper.passthrough(Types.STRING); // Player Name wrapper.passthrough(Types.PROFILE_PROPERTY_ARRAY); } if (actions.get(1) && wrapper.passthrough(Types.BOOLEAN)) { wrapper.passthrough(Types.UUID); // Session UUID wrapper.passthrough(Types.PROFILE_KEY); } if (actions.get(2)) { wrapper.passthrough(Types.VAR_INT); // Gamemode } if (actions.get(3)) { wrapper.passthrough(Types.BOOLEAN); // Listed } if (actions.get(4)) { wrapper.passthrough(Types.VAR_INT); // Latency } if (actions.get(5)) { final Tag displayName = wrapper.passthrough(Types.TRUSTED_OPTIONAL_TAG); protocol.getComponentRewriter().processTag(wrapper.user(), displayName); } // New one if (actions.get(6)) { wrapper.read(Types.VAR_INT); // List order } } }); protocol.registerClientbound(ClientboundPackets1_21_2.SET_PASSENGERS, wrapper -> { final int vehicleId = wrapper.passthrough(Types.VAR_INT); final int[] passengerIds = wrapper.passthrough(Types.VAR_INT_ARRAY_PRIMITIVE); 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(); for (final int passenger : passengerIds) { if (passenger == clientEntityId) { wrapper.user().put(new ClientVehicleStorage(vehicleId)); break; } } }); protocol.appendClientbound(ClientboundPackets1_21_2.REMOVE_ENTITIES, wrapper -> { final ClientVehicleStorage vehicleStorage = wrapper.user().get(ClientVehicleStorage.class); if (vehicleStorage == null) { return; } final int[] entityIds = wrapper.get(Types.VAR_INT_ARRAY_PRIMITIVE, 0); for (final int entityId : entityIds) { if (entityId == vehicleStorage.vehicleId()) { wrapper.user().remove(ClientVehicleStorage.class); sendSneakingPlayerCommand(wrapper, false); break; } } }); } private void updateParticleFormat(final CompoundTag options, final String particleType) { // Have to be float lists in older versions if ("dust_color_transition".equals(particleType)) { replaceColor(options, "from_color"); replaceColor(options, "to_color"); } else if ("dust".equals(particleType)) { replaceColor(options, "color"); } } private void replaceColor(final CompoundTag options, final String to_color) { final IntTag toColorTag = options.getIntTag(to_color); if (toColorTag == null) { return; } final int rgb = toColorTag.asInt(); final float r = ((rgb >> 16) & 0xFF) / 255F; final float g = ((rgb >> 8) & 0xFF) / 255F; final float b = (rgb & 0xFF) / 255F; options.put(to_color, new ListTag<>(List.of(new FloatTag(r), new FloatTag(g), new FloatTag(b)))); } private void writePackedRotation(final PacketWrapper wrapper, final float yaw, final float pitch) { // Pack y and x rot wrapper.write(Types.BYTE, (byte) Math.floor(yaw * 256F / 360F)); wrapper.write(Types.BYTE, (byte) Math.floor(pitch * 256F / 360F)); } private void handleRelativeArguments( final PacketWrapper wrapper, double x, double y, double z, float yaw, float pitch, final int relativeArguments, double movementX, double movementY, double movementZ, @Nullable final Integer entityId ) { // Position and rotation final PlayerStorage storage = wrapper.user().get(PlayerStorage.class); if ((relativeArguments & 1 << REL_X) != 0) { x += storage.x(); } if ((relativeArguments & 1 << REL_Y) != 0) { y += storage.y(); } if ((relativeArguments & 1 << REL_Z) != 0) { z += storage.z(); } if ((relativeArguments & 1 << REL_Y_ROT) != 0) { yaw += storage.yaw(); } if ((relativeArguments & 1 << REL_X_ROT) != 0) { pitch += storage.pitch(); } // Movement rotation if ((relativeArguments & 1 << REL_ROTATE_DELTA) != 0) { final double deltaYaw = Math.toRadians(storage.yaw() - yaw); final double deltaYawCos = Math.cos(deltaYaw); final double deltaYawSin = Math.sin(deltaYaw); movementX = movementX * deltaYawCos + movementZ * deltaYawSin; movementZ = movementZ * deltaYawCos - movementX * deltaYawSin; final double deltaPitch = Math.toRadians(storage.pitch() - pitch); final double deltaPitchCos = Math.cos(deltaPitch); final double deltaPitchSin = Math.sin(deltaPitch); movementY = movementY * deltaPitchCos + movementZ * deltaPitchSin; movementZ = movementZ * deltaPitchCos - movementY * deltaPitchSin; } final boolean relativeDeltaX = (relativeArguments & 1 << REL_DELTA_X) != 0; final boolean relativeDeltaY = (relativeArguments & 1 << REL_DELTA_Y) != 0; final boolean relativeDeltaZ = (relativeArguments & 1 << REL_DELTA_Z) != 0; // Update after having used its previous data storage.setPosition(x, y, z); storage.setRotation(yaw, pitch); // Movement if (relativeDeltaX && relativeDeltaY && relativeDeltaZ) { if (entityId != null && entityId != tracker(wrapper.user()).clientEntityId()) { // Can only handle the client entity return; } final PacketWrapper explosionPacket = wrapper.create(ClientboundPackets1_21.EXPLODE); explosionPacket.write(Types.DOUBLE, 0.0); // Center X explosionPacket.write(Types.DOUBLE, 0.0); // Center Y explosionPacket.write(Types.DOUBLE, 0.0); // Center Z explosionPacket.write(Types.FLOAT, 0F); // Power explosionPacket.write(Types.VAR_INT, 0); // Blocks affected explosionPacket.write(Types.FLOAT, (float) movementX); explosionPacket.write(Types.FLOAT, (float) movementY); explosionPacket.write(Types.FLOAT, (float) movementZ); explosionPacket.write(Types.VAR_INT, 0); // Block interaction explosionPacket.write(VersionedTypes.V1_21.particle(), new Particle(0)); // Small explosion explosionPacket.write(VersionedTypes.V1_21.particle(), new Particle(0)); // Large explosion explosionPacket.write(Types.SOUND_EVENT, Holder.of(new SoundEvent("", null))); // Explosion sound explosionPacket.send(Protocol1_21_2To1_21.class); } else if (!relativeDeltaX && !relativeDeltaY && !relativeDeltaZ) { final PacketWrapper entityMotionPacket = wrapper.create(ClientboundPackets1_21.SET_ENTITY_MOTION); entityMotionPacket.write(Types.VAR_INT, entityId != null ? entityId : tracker(wrapper.user()).clientEntityId()); entityMotionPacket.write(Types.SHORT, VelocityUtil.toLegacyVelocity(movementX)); entityMotionPacket.write(Types.SHORT, VelocityUtil.toLegacyVelocity(movementY)); entityMotionPacket.write(Types.SHORT, VelocityUtil.toLegacyVelocity(movementZ)); entityMotionPacket.send(Protocol1_21_2To1_21.class); } else if (!warned) { // Mixed combinations of relative and absolute would require tracking the previous delta movement // which is quite impossible without doing massive player simulation on protocol level. // This is bad but so is life. protocol.getLogger().warning("Mixed combinations of relative and absolute delta movements are not supported for 1.21.1 players. " + "This will result in incorrect movement for the player. "); warned = true; } } 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; } else if (type == EntityTypes1_21_2.SPRUCE_BOAT) { return 1; } else if (type == EntityTypes1_21_2.BIRCH_BOAT) { return 2; } else if (type == EntityTypes1_21_2.JUNGLE_BOAT) { return 3; } else if (type == EntityTypes1_21_2.ACACIA_BOAT) { return 4; } else if (type == EntityTypes1_21_2.CHERRY_BOAT) { return 5; } else if (type == EntityTypes1_21_2.DARK_OAK_BOAT) { return 6; } else if (type == EntityTypes1_21_2.MANGROVE_BOAT) { return 7; } else if (type == EntityTypes1_21_2.BAMBOO_RAFT) { return 8; } else { return 0; } } private int chestBoatTypeFromEntityType(final EntityType type) { if (type == EntityTypes1_21_2.OAK_CHEST_BOAT) { return 0; } else if (type == EntityTypes1_21_2.SPRUCE_CHEST_BOAT) { return 1; } else if (type == EntityTypes1_21_2.BIRCH_CHEST_BOAT) { return 2; } else if (type == EntityTypes1_21_2.JUNGLE_CHEST_BOAT) { return 3; } else if (type == EntityTypes1_21_2.ACACIA_CHEST_BOAT) { return 4; } else if (type == EntityTypes1_21_2.CHERRY_CHEST_BOAT) { return 5; } else if (type == EntityTypes1_21_2.DARK_OAK_CHEST_BOAT) { return 6; } else if (type == EntityTypes1_21_2.MANGROVE_CHEST_BOAT) { return 7; } else if (type == EntityTypes1_21_2.BAMBOO_CHEST_RAFT) { return 8; } else { return 0; } } private void fixOnGround(final PacketWrapper wrapper) { final boolean data = wrapper.read(Types.BOOLEAN); wrapper.write(Types.UNSIGNED_BYTE, data ? (short) 1 : 0); // Carries more data now } @Override protected void registerRewrites() { dataTypeMapper().register(); registerEntityDataTypeHandler1_20_3( VersionedTypes.V1_21.entityDataTypes.itemType, VersionedTypes.V1_21.entityDataTypes.blockStateType, VersionedTypes.V1_21.entityDataTypes.optionalBlockStateType, VersionedTypes.V1_21.entityDataTypes.particleType, VersionedTypes.V1_21.entityDataTypes.particlesType, VersionedTypes.V1_21.entityDataTypes.componentType, VersionedTypes.V1_21.entityDataTypes.optionalComponentType ); registerBlockStateHandler(EntityTypes1_21_2.ABSTRACT_MINECART, 11); filter().type(EntityTypes1_21_2.CREAKING).cancel(17); // Active filter().type(EntityTypes1_21_2.CREAKING).cancel(16); // Can move filter().type(EntityTypes1_21_2.ABSTRACT_BOAT).addIndex(11); // Boat type filter().type(EntityTypes1_21_2.SALMON).removeIndex(17); // Data type filter().type(EntityTypes1_21_2.AGEABLE_WATER_CREATURE).removeIndex(16); // Baby filter().type(EntityTypes1_21_2.ABSTRACT_ARROW).removeIndex(10); // In ground } @Override public EntityType typeFromId(final int type) { return EntityTypes1_21_2.getTypeFromId(type); } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); mapEntityTypeWithData(EntityTypes1_21_2.CREAKING, EntityTypes1_21_2.WARDEN).tagName(); mapEntityTypeWithData(EntityTypes1_21_2.CREAKING_TRANSIENT, EntityTypes1_21_2.WARDEN).tagName(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/rewriter/ParticleRewriter1_21_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_2to1_21.rewriter; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.protocol.Protocol; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2; import com.viaversion.viaversion.rewriter.ParticleRewriter; public final class ParticleRewriter1_21_2 extends ParticleRewriter { public ParticleRewriter1_21_2(final Protocol protocol) { super(protocol); } @Override public void rewriteParticle(final UserConnection connection, final Particle particle) { final String identifier = protocol.getMappingData().getParticleMappings().identifier(particle.id()); super.rewriteParticle(connection, particle); if (identifier.equals("minecraft:dust_color_transition")) { argbToVector(particle, 0); argbToVector(particle, 3); } else if (identifier.equals("minecraft:dust")) { argbToVector(particle, 0); } else if (identifier.equals("minecraft:trail")) { // Remove target particle.removeArgument(2); particle.removeArgument(1); particle.removeArgument(0); } } private void argbToVector(final Particle particle, final int index) { final int argb = particle.removeArgument(index).getValue(); final float r = ((argb >> 16) & 0xFF) / 255F; final float g = ((argb >> 8) & 0xFF) / 255F; final float b = (argb & 0xFF) / 255F; particle.add(index, Types.FLOAT, r); particle.add(index + 1, Types.FLOAT, g); particle.add(index + 2, Types.FLOAT, b); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/storage/InventoryStateIdStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage; import com.viaversion.viaversion.api.connection.StorableObject; public final class InventoryStateIdStorage implements StorableObject { private boolean smithingTableOpen; private int stateId = -1; public int stateId() { return stateId; } public void setStateId(final int stateId) { this.stateId = stateId; } public boolean smithingTableOpen() { return smithingTableOpen; } public void setSmithingTableOpen(final boolean smithingTableOpen) { this.smithingTableOpen = smithingTableOpen; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/storage/ItemTagStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.util.Key; import java.util.HashMap; import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; public final class ItemTagStorage implements StorableObject { private Map itemTags = new HashMap<>(); public int @Nullable [] itemTag(final String key) { return itemTags.get(Key.stripMinecraftNamespace(key)); } public void readItemTags(final PacketWrapper wrapper) { final int length = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < length; i++) { final String registryKey = wrapper.passthrough(Types.STRING); final int tagsSize = wrapper.passthrough(Types.VAR_INT); final boolean itemRegistry = Key.stripMinecraftNamespace(registryKey).equals("item"); if (itemRegistry) { this.itemTags = new HashMap<>(tagsSize); } for (int j = 0; j < tagsSize; j++) { final String key = wrapper.passthrough(Types.STRING); final int[] ids = wrapper.passthrough(Types.VAR_INT_ARRAY_PRIMITIVE); if (itemRegistry) { this.itemTags.put(Key.stripMinecraftNamespace(key), ids); } } } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/storage/PlayerStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage; import com.viaversion.viabackwards.api.entities.storage.PlayerPositionStorage; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.Protocol1_21_2To1_21; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ServerboundPackets1_21_2; public final class PlayerStorage extends PlayerPositionStorage { private static final PlayerInput EMPTY = new PlayerInput(false, false, false, false, false, false, false); private static final float PLAYER_JUMP_HEIGHT = 0.42F; private float yaw; private float pitch; private boolean playerCommandTrackedSneaking; private boolean playerCommandTrackedSprinting; private PlayerInput lastInput = EMPTY; private double prevX; private double prevY; private double prevZ; public void tick(final UserConnection user) { final double deltaX = x() - prevX; final double deltaY = y() - prevY; final double deltaZ = z() - prevZ; final double magnitude = Math.sqrt(deltaX * deltaX + deltaZ * deltaZ); double directionX = magnitude > 0 ? deltaX / magnitude : 0; double directionZ = magnitude > 0 ? deltaZ / magnitude : 0; directionX = Math.max(-1, Math.min(1, directionX)); directionZ = Math.max(-1, Math.min(1, directionZ)); final double angle = Math.toRadians(-yaw); final double newDirectionX = directionX * Math.cos(angle) - directionZ * Math.sin(angle); final double newDirectionZ = directionX * Math.sin(angle) + directionZ * Math.cos(angle); final boolean forward = newDirectionZ >= 0.65F; final boolean backwards = newDirectionZ <= -0.65F; final boolean left = newDirectionX >= 0.65F; final boolean right = newDirectionX <= -0.65F; final boolean jump = Math.abs(deltaY - PLAYER_JUMP_HEIGHT) <= 1E-4F; final PlayerInput input = new PlayerInput(forward, backwards, left, right, jump, playerCommandTrackedSneaking, playerCommandTrackedSprinting); if (!lastInput.equals(input)) { final PacketWrapper playerInputPacket = PacketWrapper.create(ServerboundPackets1_21_2.PLAYER_INPUT, user); byte flags = 0; flags = (byte) (flags | (input.forward() ? 1 : 0)); flags = (byte) (flags | (input.backward() ? 2 : 0)); flags = (byte) (flags | (input.left() ? 4 : 0)); flags = (byte) (flags | (input.right() ? 8 : 0)); flags = (byte) (flags | (input.jump() ? 16 : 0)); flags = (byte) (flags | (input.sneak() ? 32 : 0)); flags = (byte) (flags | (input.sprint() ? 64 : 0)); playerInputPacket.write(Types.BYTE, flags); playerInputPacket.sendToServer(Protocol1_21_2To1_21.class); lastInput = input; } this.prevX = x(); this.prevY = y(); this.prevZ = z(); } public float yaw() { return yaw; } public float pitch() { return pitch; } public void setRotation(final float yaw, final float pitch) { this.yaw = yaw; this.pitch = pitch; } public void setPlayerCommandTrackedSneaking(final boolean playerCommandTrackedSneaking) { this.playerCommandTrackedSneaking = playerCommandTrackedSneaking; } public void setPlayerCommandTrackedSprinting(final boolean playerCommandTrackedSprinting) { this.playerCommandTrackedSprinting = playerCommandTrackedSprinting; } public boolean setSneaking(final boolean sneaking) { final boolean changed = this.playerCommandTrackedSneaking != sneaking; this.playerCommandTrackedSneaking = sneaking; return changed; } public record PlayerInput(boolean forward, boolean backward, boolean left, boolean right, boolean jump, boolean sneak, boolean sprint) { } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/storage/RecipeStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage; import com.google.common.base.Preconditions; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.Protocol1_21_2To1_21; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.HolderSet; 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.type.Types; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21; import java.util.ArrayList; import java.util.Comparator; import java.util.List; // Mostly a lost cause as the server will not send all the necessary data. // Recipe displays can also be different from the actual recipe - at the end of the same, // the server will still properly handle inputs, but we can't fully reconstruct the recipe book. public final class RecipeStorage implements StorableObject { // Pairs of open + filtering for: Crafting, furnace, blast furnace, smoker public static final int RECIPE_BOOK_SETTINGS = 4 * 2; private static final String[] EMPTY_STRINGS = new String[0]; private final List recipes = new ArrayList<>(); // Recipes, excluding stone cutter recipes, in the order they were received private final List tempRecipes = new ArrayList<>(); // Recipes received in the current batch private final List stoneCutterRecipes = new ArrayList<>(); private boolean[] recipeBookSettings = new boolean[RECIPE_BOOK_SETTINGS]; private final Protocol1_21_2To1_21 protocol; public RecipeStorage(final Protocol1_21_2To1_21 protocol) { this.protocol = protocol; } abstract static class Recipe { private static final int FOOD_CRAFTING_BOOK_CATEGORY = 0; private static final int BLOCKS_CRAFTING_BOOK_CATEGORY = 1; private static final int MISC_CRAFTING_BOOK_CATEGORY = 2; protected int index; private Integer group; private int category; private boolean highlight; abstract void write(PacketWrapper wrapper); void writeGroup(final PacketWrapper wrapper) { wrapper.write(Types.STRING, group != null ? Integer.toString(group) : ""); } void writeIngredients(final PacketWrapper wrapper, final Item[][] ingredients) { wrapper.write(Types.VAR_INT, ingredients.length); for (final Item[] ingredient : ingredients) { writeIngredient(wrapper, ingredient); } } void writeIngredient(final PacketWrapper wrapper, final Item[] ingredient) { final Item[] copy = new Item[ingredient.length]; for (int i = 0; i < ingredient.length; i++) { copy[i] = ingredient[i].copy(); } wrapper.write(VersionedTypes.V1_21_2.itemArray(), copy); } void writeResult(final PacketWrapper wrapper, final Item result) { wrapper.write(VersionedTypes.V1_21_2.item(), result.copy()); } void writeCategory(final PacketWrapper wrapper) { // TODO Doesn't translate exactly final int craftingBookCategory = switch (category) { case 4, 9, 12 -> FOOD_CRAFTING_BOOK_CATEGORY; case 0, 5, 7, 10 -> BLOCKS_CRAFTING_BOOK_CATEGORY; case 1, 2, 3, 6, 8, 11 -> MISC_CRAFTING_BOOK_CATEGORY; default -> MISC_CRAFTING_BOOK_CATEGORY; }; wrapper.write(Types.VAR_INT, craftingBookCategory); } int category() { return category; } } public void sendRecipes(final UserConnection connection) { // Fill from temp recipes so we can clear before if needed if (!tempRecipes.isEmpty()) { this.recipes.addAll(tempRecipes); tempRecipes.clear(); } int highestIndex = -1; for (final Recipe recipe : recipes) { highestIndex = Math.max(highestIndex, recipe.index); } // Add stonecutter recipes from update_recipes final List recipes = new ArrayList<>(this.recipes); for (final StoneCutterRecipe recipe : stoneCutterRecipes) { recipe.index = ++highestIndex; recipes.add(recipe); } // Sort by id recipes.sort(Comparator.comparingInt(a -> a.index)); // Since the server only sends unlocked recipes, we need to re-send all recipes in UPDATE_RECIPES final PacketWrapper updateRecipesPacket = PacketWrapper.create(ClientboundPackets1_21.UPDATE_RECIPES, connection); updateRecipesPacket.write(Types.VAR_INT, recipes.size()); for (final Recipe recipe : recipes) { updateRecipesPacket.write(Types.STRING, identifier(recipe.index)); recipe.write(updateRecipesPacket); } updateRecipesPacket.send(Protocol1_21_2To1_21.class); sendUnlockedRecipes(connection, recipes); } private static String identifier(final int recipeIndex) { // Use index as the recipe identifier, add leading zeros to keept it sorted return String.format("%06d", recipeIndex); } public void lockRecipes(final PacketWrapper wrapper, final int[] ids) { for (final int id : ids) { recipes.removeIf(recipe -> recipe.index == id); } wrapper.write(Types.VAR_INT, 2); // Remove recipes for (final boolean recipeBookSetting : recipeBookSettings) { wrapper.write(Types.BOOLEAN, recipeBookSetting); } final String[] recipeKeys = new String[ids.length]; for (int i = 0; i < ids.length; i++) { recipeKeys[i] = identifier(ids[i]); } wrapper.write(Types.STRING_ARRAY, recipeKeys); } private void sendUnlockedRecipes(final UserConnection connection, final List recipes) { final PacketWrapper wrapper = PacketWrapper.create(ClientboundPackets1_21.RECIPE, connection); wrapper.write(Types.VAR_INT, 0); // Init recipes for (final boolean recipeBookSetting : recipeBookSettings) { wrapper.write(Types.BOOLEAN, recipeBookSetting); } // Use index as the recipe identifier. We only know the unlocked ones, so send all final String[] recipeKeys = new String[recipes.size()]; final List highlightRecipes = new ArrayList<>(); for (int i = 0; i < recipes.size(); i++) { recipeKeys[i] = identifier(recipes.get(i).index); if (recipes.get(i).highlight) { highlightRecipes.add(recipeKeys[i]); } } wrapper.write(Types.STRING_ARRAY, recipeKeys); wrapper.write(Types.STRING_ARRAY, highlightRecipes.toArray(EMPTY_STRINGS)); wrapper.send(Protocol1_21_2To1_21.class); } public void readRecipe(final PacketWrapper wrapper) { final int id = wrapper.read(Types.VAR_INT); final int type = wrapper.passthrough(Types.VAR_INT); final Recipe recipe = switch (type) { case 0 -> readShapeless(wrapper); case 1 -> readShaped(wrapper); case 2 -> readFurnace(wrapper); case 3 -> readStoneCutter(wrapper); case 4 -> readSmithing(wrapper); default -> null; }; final Integer group = wrapper.read(Types.OPTIONAL_VAR_INT); final int category = wrapper.read(Types.VAR_INT); if (wrapper.read(Types.BOOLEAN)) { final int ingredientsSize = wrapper.read(Types.VAR_INT); for (int j = 0; j < ingredientsSize; j++) { //handleIngredient(wrapper); // Items //TODO ? wrapper.read(Types.HOLDER_SET); } } final byte flags = wrapper.read(Types.BYTE); if (recipe != null) { recipe.index = id; recipe.group = group; recipe.category = category; recipe.highlight = (flags & 2) != 0; } } private Recipe readShapeless(final PacketWrapper wrapper) { final Item[][] ingredients = readSlotDisplayList(wrapper); final Item result = readSingleSlotDisplay(wrapper); readSlotDisplay(wrapper); // Crafting station return add(new ShapelessRecipe(ingredients, result)); } private Recipe readShaped(final PacketWrapper wrapper) { final int width = wrapper.passthrough(Types.VAR_INT); final int height = wrapper.passthrough(Types.VAR_INT); final Item[][] ingredients = readSlotDisplayList(wrapper); final Item result = readSingleSlotDisplay(wrapper); readSlotDisplay(wrapper); // Crafting station return add(new ShapedRecipe(width, height, ingredients, result)); } private Recipe readFurnace(final PacketWrapper wrapper) { final Item[] ingredient = readSlotDisplay(wrapper); readSlotDisplay(wrapper); // Fuel final Item result = readSingleSlotDisplay(wrapper); readSlotDisplay(wrapper); // Crafting station final int duration = wrapper.read(Types.VAR_INT); final float experience = wrapper.read(Types.FLOAT); return add(new FurnaceRecipe(ingredient, result, duration, experience)); } private Recipe readStoneCutter(final PacketWrapper wrapper) { // Use values from UPDATE_RECIPES instead readSlotDisplay(wrapper); // Input readSlotDisplay(wrapper); // Result readSlotDisplay(wrapper); // Crafting station return null; } private Recipe readSmithing(final PacketWrapper wrapper) { // TODO Combine with update_recipes? readSlotDisplay(wrapper); // Template readSlotDisplay(wrapper); // Base readSlotDisplay(wrapper); // Addition readSlotDisplay(wrapper); // Result readSlotDisplay(wrapper); // Crafting station return null; } private Recipe add(final Recipe recipe) { tempRecipes.add(recipe); return recipe; } private Item[][] readSlotDisplayList(final PacketWrapper wrapper) { final int size = wrapper.passthrough(Types.VAR_INT); final Item[][] ingredients = new Item[size][]; for (int i = 0; i < size; i++) { ingredients[i] = readSlotDisplay(wrapper); } return ingredients; } private Item readSingleSlotDisplay(final PacketWrapper wrapper) { final Item[] items = readSlotDisplay(wrapper); return items.length == 0 ? new StructuredItem(1, 1) : items[0]; } private Item[] readSlotDisplay(final PacketWrapper wrapper) { // empty, any_fuel, smithing_trim are empty final int type = wrapper.read(Types.VAR_INT); return switch (type) { case 2 -> { final int id = wrapper.read(Types.VAR_INT); if (id == 0) { protocol.getLogger().warning("Empty item id in recipe"); yield new Item[0]; } yield new Item[]{new StructuredItem(rewriteItemId(id), 1)}; } case 3 -> { final Item item = protocol.getItemRewriter().handleItemToClient(wrapper.user(), wrapper.read(VersionedTypes.V1_21_2.item())); if (item.isEmpty()) { protocol.getLogger().warning("Empty item in recipe"); yield new Item[0]; } yield new Item[]{item}; } case 4 -> { wrapper.read(Types.STRING); // Tag key // TODO Probably not even worth the effort yield new Item[0]; } case 5 -> { readSlotDisplay(wrapper); // Base readSlotDisplay(wrapper); // Material readSlotDisplay(wrapper); // Pattern yield new Item[0]; } case 6 -> { readSlotDisplay(wrapper); // Input readSlotDisplay(wrapper); // Remainder yield new Item[0]; } case 7 -> readSlotDisplayList(wrapper)[0]; // Composite default -> new Item[0]; }; } private int rewriteItemId(final int id) { return protocol.getMappingData().getNewItemId(id); } public void readStoneCutterRecipes(final PacketWrapper wrapper) { stoneCutterRecipes.clear(); final int stonecutterRecipesSize = wrapper.read(Types.VAR_INT); for (int i = 0; i < stonecutterRecipesSize; i++) { // The ingredients are what's actually used in client prediction, they're the important part final Item[] ingredient = readHolderSet(wrapper); // TODO Probably not actually the result, might have to combine with update_recipes final Item result = readSingleSlotDisplay(wrapper); stoneCutterRecipes.add(new StoneCutterRecipe(ingredient, result)); } } private Item[] readHolderSet(final PacketWrapper wrapper) { final HolderSet holderSet = wrapper.read(Types.HOLDER_SET); if (holderSet.hasTagKey()) { return new Item[]{new StructuredItem(1, 1)}; // TODO Probably not even worth the effort } final int[] ids = holderSet.ids(); for (int i = 0; i < ids.length; i++) { ids[i] = rewriteItemId(ids[i]); } final Item[] ingredient = new Item[ids.length]; for (int i = 0; i < ingredient.length; i++) { ingredient[i] = new StructuredItem(ids[i], 1); } return ingredient; } private static final class ShapelessRecipe extends Recipe { private static final int SERIALIZER_ID = 1; private final Item[][] ingredients; private final Item result; private ShapelessRecipe(final Item[][] ingredients, final Item result) { this.ingredients = ingredients; this.result = result; } @Override public void write(final PacketWrapper wrapper) { wrapper.write(Types.VAR_INT, SERIALIZER_ID); writeGroup(wrapper); writeCategory(wrapper); writeIngredients(wrapper, ingredients); writeResult(wrapper, result); } } private static final class ShapedRecipe extends Recipe { private static final int SERIALIZER_ID = 0; private final int width; private final int height; private final Item[][] ingredients; private final Item result; private ShapedRecipe(final int width, final int height, final Item[][] ingredients, final Item result) { this.width = width; this.height = height; this.ingredients = ingredients; this.result = result; } @Override public void write(final PacketWrapper wrapper) { wrapper.write(Types.VAR_INT, SERIALIZER_ID); writeGroup(wrapper); writeCategory(wrapper); wrapper.write(Types.VAR_INT, width); wrapper.write(Types.VAR_INT, height); Preconditions.checkArgument(width * height == ingredients.length, "Invalid shaped recipe"); // No length prefix for (final Item[] ingredient : ingredients) { writeIngredient(wrapper, ingredient); } writeResult(wrapper, result); wrapper.write(Types.BOOLEAN, false); // Doesn't matter for the init } } private static final class FurnaceRecipe extends Recipe { private final Item[] ingredient; private final Item result; private final float experience; private final int cookingTime; private FurnaceRecipe(final Item[] ingredient, final Item result, final int cookingTime, final float experience) { this.ingredient = ingredient; this.result = result; this.experience = experience; this.cookingTime = cookingTime; } @Override public void write(final PacketWrapper wrapper) { wrapper.write(Types.VAR_INT, serializerId()); writeGroup(wrapper); writeCategory(wrapper); writeIngredient(wrapper, ingredient); writeResult(wrapper, result); wrapper.write(Types.FLOAT, experience); wrapper.write(Types.VAR_INT, cookingTime); } private int serializerId() { return switch (category()) { case 4, 5, 6 -> 15; // Furnace food, bocks, misc case 7, 8 -> 16; // Blast furnace blocks, misc case 9 -> 17; // Smoker case 12 -> 18; // Campfire default -> 15; }; } } private static final class StoneCutterRecipe extends Recipe { private static final int SERIALIZER_ID = 19; private final Item[] ingredient; private final Item result; private StoneCutterRecipe(final Item[] ingredient, final Item result) { this.ingredient = ingredient; this.result = result; } @Override public void write(final PacketWrapper wrapper) { wrapper.write(Types.VAR_INT, SERIALIZER_ID); writeGroup(wrapper); writeIngredient(wrapper, ingredient); writeResult(wrapper, result); } } public void setRecipeBookSettings(final boolean[] recipeBookSettings) { this.recipeBookSettings = recipeBookSettings; } public void clearRecipes() { recipes.clear(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/storage/SignStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.minecraft.BlockPosition; import java.util.HashSet; import java.util.Set; public final class SignStorage implements StorableObject { private final Set signs = new HashSet<>(); public void addSign(final BlockPosition position) { signs.add(position); } public boolean isSign(final BlockPosition position) { return signs.contains(position); } public void removeSign(final BlockPosition position) { signs.remove(position); } public void removeSigns(final int chunkX, final int chunkZ) { signs.removeIf(pos -> pos.x() >> 4 == chunkX && pos.z() >> 4 == chunkZ); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_2to1_21/task/PlayerPacketsTickTask.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_2to1_21.task; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.Protocol1_21_2To1_21; import com.viaversion.viabackwards.protocol.v1_21_2to1_21.storage.PlayerStorage; import com.viaversion.viaversion.api.connection.ProtocolInfo; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.packet.State; import com.viaversion.viaversion.connection.StorableObjectTask; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ServerboundPackets1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.storage.ClientVehicleStorage; import java.util.logging.Level; public final class PlayerPacketsTickTask extends StorableObjectTask { public PlayerPacketsTickTask() { super(PlayerStorage.class); } @Override public void run(final UserConnection connection, final PlayerStorage storableObject) { final ProtocolInfo protocolInfo = connection.getProtocolInfo(); if (protocolInfo.getClientState() != State.PLAY || protocolInfo.getServerState() != State.PLAY) { return; } final EntityTracker entityTracker = connection.getEntityTracker(Protocol1_21_2To1_21.class); if (!entityTracker.hasClientEntityId()) { return; } try { if (!connection.has(ClientVehicleStorage.class)) { storableObject.tick(connection); } } catch (final Throwable t) { ViaBackwards.getPlatform().getLogger().log(Level.SEVERE, "Error while sending player input packet.", t); } try { final PacketWrapper clientTickEndPacket = PacketWrapper.create(ServerboundPackets1_21_2.CLIENT_TICK_END, connection); clientTickEndPacket.sendToServer(Protocol1_21_2To1_21.class); } catch (final Throwable t) { ViaBackwards.getPlatform().getLogger().log(Level.SEVERE, "Error while sending client tick end packet.", t); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_4to1_21_2/Protocol1_21_4To1_21_2.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_4to1_21_2; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_21_4to1_21_2.rewriter.BlockItemPacketRewriter1_21_4; import com.viaversion.viabackwards.protocol.v1_21_4to1_21_2.rewriter.ComponentRewriter1_21_4; import com.viaversion.viabackwards.protocol.v1_21_4to1_21_2.rewriter.EntityPacketRewriter1_21_4; import com.viaversion.viabackwards.protocol.v1_21_4to1_21_2.rewriter.ParticleRewriter1_21_4; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.RegistryEntry; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_4; 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; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.api.type.types.version.VersionedTypesHolder; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundConfigurationPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundConfigurationPackets1_21; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.Protocol1_21_2To1_21_4; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPacket1_21_4; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPackets1_21_4; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ServerboundPacket1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ServerboundPackets1_21_2; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.RecipeDisplayRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.util.ArrayUtil; import com.viaversion.viaversion.util.Key; import java.util.BitSet; import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap; public final class Protocol1_21_4To1_21_2 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.21.4", "1.21.2", Protocol1_21_2To1_21_4.class); private final EntityPacketRewriter1_21_4 entityRewriter = new EntityPacketRewriter1_21_4(this); private final BlockItemPacketRewriter1_21_4 itemRewriter = new BlockItemPacketRewriter1_21_4(this); private final ParticleRewriter particleRewriter = new ParticleRewriter1_21_4(this); private final JsonNBTComponentRewriter translatableRewriter = new ComponentRewriter1_21_4(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final RecipeDisplayRewriter recipeRewriter = new RecipeDisplayRewriter<>(this); private final BlockRewriter blockRewriter = BlockRewriter.for1_20_2(this, ChunkType1_20_2::new); private final BackwardsRegistryRewriter registryDataRewriter = new BackwardsRegistryRewriter(this) { @Override public RegistryEntry[] handle(final UserConnection connection, final String key, final RegistryEntry[] entries) { final String strippedKey = Key.stripMinecraftNamespace(key); if (strippedKey.equals("worldgen/biome")) { for (final RegistryEntry entry : entries) { if (entry.tag() == null) { continue; } final CompoundTag effectsTag = ((CompoundTag) entry.tag()).getCompoundTag("effects"); final ListTag weightedMusicTags = effectsTag.getListTag("music", CompoundTag.class); if (weightedMusicTags == null) { continue; } if (weightedMusicTags.isEmpty()) { effectsTag.remove("music"); continue; } // Unwrap music final CompoundTag musicTag = weightedMusicTags.get(0); effectsTag.put("music", musicTag.get("data")); } } else if (strippedKey.equals("trim_material")) { for (final RegistryEntry entry : entries) { if (entry.tag() == null) { continue; } final CompoundTag compoundTag = ((CompoundTag) entry.tag()); compoundTag.putFloat("item_model_index", itemModelIndex(entry.key())); } } return super.handle(connection, key, entries); } }; public Protocol1_21_4To1_21_2() { super(ClientboundPacket1_21_2.class, ClientboundPacket1_21_2.class, ServerboundPacket1_21_4.class, ServerboundPacket1_21_2.class); } @Override protected void registerPackets() { super.registerPackets(); replaceClientbound(ClientboundPackets1_21_2.LEVEL_PARTICLES, wrapper -> { wrapper.passthrough(Types.BOOLEAN); // Override limiter wrapper.read(Types.BOOLEAN); // Always show wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z wrapper.passthrough(Types.FLOAT); // Offset X wrapper.passthrough(Types.FLOAT); // Offset Y wrapper.passthrough(Types.FLOAT); // Offset Z wrapper.passthrough(Types.FLOAT); // Particle Data wrapper.passthrough(Types.INT); // Particle Count final Particle particle = wrapper.passthroughAndMap(VersionedTypes.V1_21_4.particle(), VersionedTypes.V1_21_2.particle()); particleRewriter.rewriteParticle(wrapper.user(), particle); }); registerClientbound(ClientboundConfigurationPackets1_21.UPDATE_ENABLED_FEATURES, wrapper -> { final String[] enabledFeatures = wrapper.read(Types.STRING_ARRAY); wrapper.write(Types.STRING_ARRAY, ArrayUtil.add(enabledFeatures, "winter_drop")); }); replaceClientbound(ClientboundPackets1_21_2.PLAYER_INFO_UPDATE, wrapper -> { final BitSet actions = wrapper.read(Types.PROFILE_ACTIONS_ENUM1_21_4); // Remove new action final BitSet updatedActions = new BitSet(7); for (int i = 0; i < 7; i++) { if (actions.get(i)) { updatedActions.set(i); } } wrapper.write(Types.PROFILE_ACTIONS_ENUM1_21_2, updatedActions); final int entries = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < entries; i++) { wrapper.passthrough(Types.UUID); if (actions.get(0)) { wrapper.passthrough(Types.STRING); // Player Name wrapper.passthrough(Types.PROFILE_PROPERTY_ARRAY); } if (actions.get(1) && wrapper.passthrough(Types.BOOLEAN)) { wrapper.passthrough(Types.UUID); // Session UUID wrapper.passthrough(Types.PROFILE_KEY); } if (actions.get(2)) { wrapper.passthrough(Types.VAR_INT); // Gamemode } if (actions.get(3)) { wrapper.passthrough(Types.BOOLEAN); // Listed } if (actions.get(4)) { wrapper.passthrough(Types.VAR_INT); // Latency } if (actions.get(5)) { translatableRewriter.processTag(wrapper.user(), wrapper.passthrough(Types.TRUSTED_OPTIONAL_TAG)); } if (actions.get(6)) { wrapper.passthrough(Types.VAR_INT); // List order } // Remove if (actions.get(7)) { wrapper.read(Types.BOOLEAN); // Show head } } }); } @Override protected void onMappingDataLoaded() { super.onMappingDataLoaded(); tagRewriter.addEmptyTags(RegistryType.ITEM, "minecraft:tall_flowers", "minecraft:flowers"); tagRewriter.addEmptyTag(RegistryType.BLOCK, "minecraft:tall_flowers"); } @Override public void init(final UserConnection user) { addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_21_4.PLAYER)); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_21_4 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_21_4 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } @Override public RecipeDisplayRewriter getRecipeRewriter() { return recipeRewriter; } @Override public BackwardsRegistryRewriter getRegistryDataRewriter() { return registryDataRewriter; } @Override public VersionedTypesHolder types() { return VersionedTypes.V1_21_4; } @Override public VersionedTypesHolder mappedTypes() { return VersionedTypes.V1_21_2; } @Override protected PacketTypesProvider createPacketTypesProvider() { return new SimplePacketTypesProvider<>( packetTypeMap(unmappedClientboundPacketType, ClientboundPackets1_21_2.class, ClientboundConfigurationPackets1_21.class), packetTypeMap(mappedClientboundPacketType, ClientboundPackets1_21_2.class, ClientboundConfigurationPackets1_21.class), packetTypeMap(mappedServerboundPacketType, ServerboundPackets1_21_4.class, ServerboundConfigurationPackets1_20_5.class), packetTypeMap(unmappedServerboundPacketType, ServerboundPackets1_21_2.class, ServerboundConfigurationPackets1_20_5.class) ); } private float itemModelIndex(final String trim) { return switch (Key.stripNamespace(trim)) { case "amethyst" -> 1.0F; case "copper" -> 0.5F; case "diamond" -> 0.8F; case "emerald" -> 0.7F; case "gold" -> 0.6F; case "iron" -> 0.2F; case "lapis" -> 0.9F; case "netherite" -> 0.3F; case "quartz" -> 0.1F; case "redstone" -> 0.4F; default -> 1.0f; }; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_4to1_21_2/rewriter/BlockItemPacketRewriter1_21_4.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_4to1_21_2.rewriter; import com.viaversion.nbt.tag.ByteArrayTag; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntArrayTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.rewriters.BackwardsStructuredItemRewriter; import com.viaversion.viabackwards.protocol.v1_21_4to1_21_2.Protocol1_21_4To1_21_2; 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.data.CustomModelData1_21_4; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ServerboundPacket1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ServerboundPackets1_21_2; import static com.viaversion.viaversion.protocols.v1_21_2to1_21_4.rewriter.BlockItemPacketRewriter1_21_4.downgradeItemData; import static com.viaversion.viaversion.protocols.v1_21_2to1_21_4.rewriter.BlockItemPacketRewriter1_21_4.updateItemData; public final class BlockItemPacketRewriter1_21_4 extends BackwardsStructuredItemRewriter { public BlockItemPacketRewriter1_21_4(final Protocol1_21_4To1_21_2 protocol) { super(protocol); } @Override public void registerPackets() { protocol.registerClientbound(ClientboundPackets1_21_2.SET_HELD_SLOT, wrapper -> { final int slot = wrapper.read(Types.VAR_INT); wrapper.write(Types.BYTE, (byte) slot); }); protocol.cancelServerbound(ServerboundPackets1_21_2.PICK_ITEM); } @Override public Item handleItemToClient(final UserConnection connection, Item item) { item = super.handleItemToClient(connection, item); final StructuredDataContainer dataContainer = item.dataContainer(); final CustomModelData1_21_4 modelData = dataContainer.get(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4); if (modelData != null) { saveTag(createCustomTag(item), customModelDataToTag(modelData), "custom_model_data"); if (ViaBackwards.getConfig().mapCustomModelData() && modelData.floats().length > 0) { // Put first float as old custom model data as this is the most common replacement final int data = (int) modelData.floats()[0]; dataContainer.set(StructuredDataKey.CUSTOM_MODEL_DATA1_20_5, data); } } downgradeItemData(item); return item; } @Override public Item handleItemToServer(final UserConnection connection, Item item) { item = super.handleItemToServer(connection, item); final StructuredDataContainer dataContainer = item.dataContainer(); final CompoundTag customData = dataContainer.get(StructuredDataKey.CUSTOM_DATA); if (customData != null) { if (customData.remove(nbtTagName("custom_model_data")) instanceof final CompoundTag customModelData) { dataContainer.set(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4, customModelDataFromTag(customModelData)); removeCustomTag(dataContainer, customData); } } updateItemData(item); return item; } private CustomModelData1_21_4 customModelDataFromTag(final CompoundTag tag) { final IntArrayTag floatsTag = tag.getIntArrayTag("floats"); final float[] floats = new float[floatsTag.getValue().length]; for (int i = 0; i < floats.length; i++) { floats[i] = Float.intBitsToFloat(floatsTag.get(i)); } final ByteArrayTag booleansTag = tag.getByteArrayTag("booleans"); final boolean[] booleans = new boolean[booleansTag.getValue().length]; for (int i = 0; i < booleans.length; i++) { booleans[i] = booleansTag.get(i) != 0; } final ListTag stringsTag = tag.getListTag("strings", StringTag.class); final String[] strings = new String[stringsTag.size()]; for (int i = 0; i < strings.length; i++) { strings[i] = stringsTag.get(i).getValue(); } final IntArrayTag colorsTag = tag.getIntArrayTag("colors"); return new CustomModelData1_21_4(floats, booleans, strings, colorsTag.getValue()); } private CompoundTag customModelDataToTag(final CustomModelData1_21_4 customModelData) { final CompoundTag tag = new CompoundTag(); final int[] floats = new int[customModelData.floats().length]; for (int i = 0; i < floats.length; i++) { floats[i] = Float.floatToIntBits(customModelData.floats()[i]); } tag.put("floats", new IntArrayTag(floats)); final byte[] booleans = new byte[customModelData.booleans().length]; for (int i = 0; i < booleans.length; i++) { booleans[i] = (byte) (customModelData.booleans()[i] ? 1 : 0); } tag.put("booleans", new ByteArrayTag(booleans)); final ListTag strings = new ListTag<>(StringTag.class); for (final String string : customModelData.strings()) { strings.add(new StringTag(string)); } tag.put("strings", strings); tag.put("colors", new IntArrayTag(customModelData.colors())); return tag; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_4to1_21_2/rewriter/ComponentRewriter1_21_4.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_4to1_21_2.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2; import com.viaversion.viaversion.util.SerializerVersion; public final class ComponentRewriter1_21_4 extends JsonNBTComponentRewriter { public ComponentRewriter1_21_4(final BackwardsProtocol protocol) { super(protocol, ReadType.NBT); } @Override protected void handleShowItem(final UserConnection connection, final CompoundTag itemTag, final CompoundTag componentsTag) { super.handleShowItem(connection, itemTag, componentsTag); if (componentsTag == null) { return; } removeDataComponents(componentsTag, StructuredDataKey.CUSTOM_MODEL_DATA1_21_4, StructuredDataKey.TRIM1_21_4); } @Override protected SerializerVersion inputSerializerVersion() { return SerializerVersion.V1_21_4; } @Override protected SerializerVersion outputSerializerVersion() { return SerializerVersion.V1_20_5; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_4to1_21_2/rewriter/EntityPacketRewriter1_21_4.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_4to1_21_2.rewriter; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_21_4to1_21_2.Protocol1_21_4To1_21_2; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_4; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPackets1_21_4; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ServerboundPackets1_21_2; public final class EntityPacketRewriter1_21_4 extends EntityRewriter { public EntityPacketRewriter1_21_4(final Protocol1_21_4To1_21_2 protocol) { super(protocol, VersionedTypes.V1_21_4.entityDataTypes.optionalComponentType, VersionedTypes.V1_21_4.entityDataTypes.booleanType); } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_21_2.LOGIN, wrapper -> { final int entityId = wrapper.passthrough(Types.INT); // Entity id wrapper.passthrough(Types.BOOLEAN); // Hardcore wrapper.passthrough(Types.STRING_ARRAY); // World List wrapper.passthrough(Types.VAR_INT); // Max players wrapper.passthrough(Types.VAR_INT); // View distance wrapper.passthrough(Types.VAR_INT); // Simulation distance wrapper.passthrough(Types.BOOLEAN); // Reduced debug info wrapper.passthrough(Types.BOOLEAN); // Show death screen wrapper.passthrough(Types.BOOLEAN); // Limited crafting final int dimensionId = wrapper.passthrough(Types.VAR_INT); final String world = wrapper.passthrough(Types.STRING); trackWorldDataByKey1_20_5(wrapper.user(), dimensionId, world); trackPlayer(wrapper.user(), entityId); final PacketWrapper playerLoadedPacket = wrapper.create(ServerboundPackets1_21_4.PLAYER_LOADED); playerLoadedPacket.scheduleSendToServer(Protocol1_21_4To1_21_2.class); }); protocol.replaceClientbound(ClientboundPackets1_21_2.RESPAWN, wrapper -> { final int dimensionId = wrapper.passthrough(Types.VAR_INT); final String world = wrapper.passthrough(Types.STRING); trackWorldDataByKey1_20_5(wrapper.user(), dimensionId, world); final PacketWrapper playerLoadedPacket = wrapper.create(ServerboundPackets1_21_4.PLAYER_LOADED); playerLoadedPacket.scheduleSendToServer(Protocol1_21_4To1_21_2.class); }); protocol.registerServerbound(ServerboundPackets1_21_2.MOVE_VEHICLE, wrapper -> { wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z wrapper.passthrough(Types.FLOAT); // Yaw wrapper.passthrough(Types.FLOAT); // Pitch wrapper.write(Types.BOOLEAN, true); // On ground // TODO ... }); } @Override protected void registerRewrites() { dataTypeMapper().register(); registerEntityDataTypeHandler1_20_3( VersionedTypes.V1_21_2.entityDataTypes.itemType, VersionedTypes.V1_21_2.entityDataTypes.blockStateType, VersionedTypes.V1_21_2.entityDataTypes.optionalBlockStateType, VersionedTypes.V1_21_2.entityDataTypes.particleType, VersionedTypes.V1_21_2.entityDataTypes.particlesType, VersionedTypes.V1_21_2.entityDataTypes.componentType, VersionedTypes.V1_21_2.entityDataTypes.optionalComponentType ); registerBlockStateHandler(EntityTypes1_21_4.ABSTRACT_MINECART, 11); filter().type(EntityTypes1_21_4.CREAKING).removeIndex(19); // Home pos filter().type(EntityTypes1_21_4.CREAKING).removeIndex(18); // Is tearing down filter().type(EntityTypes1_21_4.SALMON).index(17).handler((event, data) -> { final int typeId = data.value(); final String type = switch (typeId) { case 0 -> "small"; case 2 -> "large"; default -> "medium"; }; data.setTypeAndValue(VersionedTypes.V1_21_4.entityDataTypes.stringType, type); }); } @Override public EntityType typeFromId(final int type) { return EntityTypes1_21_4.getTypeFromId(type); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_4to1_21_2/rewriter/ParticleRewriter1_21_4.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_4to1_21_2.rewriter; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.protocol.Protocol; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2; import com.viaversion.viaversion.rewriter.ParticleRewriter; public final class ParticleRewriter1_21_4 extends ParticleRewriter { public ParticleRewriter1_21_4(final Protocol protocol) { super(protocol); } @Override public void rewriteParticle(final UserConnection connection, final Particle particle) { super.rewriteParticle(connection, particle); final String identifier = protocol.getMappingData().getParticleMappings().mappedIdentifier(particle.id()); if (identifier.equals("minecraft:trail")) { particle.removeArgument(4); // Duration } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_5to1_21_4/Protocol1_21_5To1_21_4.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_5to1_21_4; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter; import com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.rewriter.BlockItemPacketRewriter1_21_5; import com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.rewriter.BlockPacketRewriter1_21_5; import com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.rewriter.ComponentRewriter1_21_5; import com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.rewriter.EntityPacketRewriter1_21_5; import com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.rewriter.RegistryDataRewriter1_21_5; import com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.storage.HashedItemConverterStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_21_2; import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_21_5; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_4; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_2; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_5; import com.viaversion.viaversion.api.minecraft.item.data.ArmorTrimPattern; import com.viaversion.viaversion.api.minecraft.item.data.ChatType; 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; import com.viaversion.viaversion.api.type.Types; 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.protocols.v1_19_3to1_19_4.rewriter.CommandRewriter1_19_4; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundConfigurationPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundConfigurationPackets1_21; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPacket1_21_4; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPackets1_21_4; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.Protocol1_21_4To1_21_5; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ClientboundPacket1_21_5; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ClientboundPackets1_21_5; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ServerboundPacket1_21_5; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ServerboundPackets1_21_5; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.rewriter.RecipeDisplayRewriter1_21_5; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.RecipeDisplayRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.Limit; import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap; public final class Protocol1_21_5To1_21_4 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.21.5", "1.21.4", Protocol1_21_4To1_21_5.class); private final EntityPacketRewriter1_21_5 entityRewriter = new EntityPacketRewriter1_21_5(this); private final BlockItemPacketRewriter1_21_5 itemRewriter = new BlockItemPacketRewriter1_21_5(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this) { @Override public void rewriteParticle(final UserConnection connection, final Particle particle) { if (particle.id() == MAPPINGS.getParticleMappings().id("tinted_leaves")) { particle.getArguments().clear(); } super.rewriteParticle(connection, particle); } }; private final ComponentRewriter1_21_5 translatableRewriter = new ComponentRewriter1_21_5(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final RecipeDisplayRewriter recipeRewriter = new RecipeDisplayRewriter1_21_5<>(this) { @Override protected void handleSmithingTrimSlotDisplay(final PacketWrapper wrapper) { handleSlotDisplay(wrapper); // Base handleSlotDisplay(wrapper); // Material wrapper.read(ArmorTrimPattern.TYPE1_21_5); // Pattern // Add empty slot display... wrapper.write(Types.VAR_INT, 0); } }; private final BackwardsRegistryRewriter registryDataRewriter = new RegistryDataRewriter1_21_5(this); private final BlockRewriter blockRewriter = new BlockPacketRewriter1_21_5(this); public Protocol1_21_5To1_21_4() { super(ClientboundPacket1_21_5.class, ClientboundPacket1_21_2.class, ServerboundPacket1_21_5.class, ServerboundPacket1_21_4.class); } @Override protected void registerPackets() { super.registerPackets(); final CommandRewriter1_19_4 commandRewriter = new CommandRewriter1_19_4<>(this) { @Override public void handleArgument(final PacketWrapper wrapper, final String argumentType) { if (argumentType.equals("minecraft:resource")) { String resource = wrapper.read(Types.STRING); if (Key.equals(resource, "test_instance")) { resource = "minecraft:item"; // Just give it anything else } wrapper.write(Types.STRING, resource); } else if (argumentType.equals("minecraft:resource_selector")) { wrapper.read(Types.STRING); // Selector wrapper.write(Types.VAR_INT, 1); // Quotable string } else { super.handleArgument(wrapper, argumentType); } } }; replaceClientbound(ClientboundPackets1_21_5.COMMANDS, commandRewriter::handle1_19); replaceClientbound(ClientboundPackets1_21_5.PLAYER_CHAT, wrapper -> { wrapper.read(Types.VAR_INT); // Index wrapper.passthrough(Types.UUID); // Sender wrapper.passthrough(Types.VAR_INT); // Index wrapper.passthrough(Types.OPTIONAL_SIGNATURE_BYTES); // Signature wrapper.passthrough(Types.STRING); // Plain content wrapper.passthrough(Types.LONG); // Timestamp wrapper.passthrough(Types.LONG); // Salt final int lastSeen = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < lastSeen; i++) { final int index = wrapper.passthrough(Types.VAR_INT); if (index == 0) { wrapper.passthrough(Types.SIGNATURE_BYTES); } } translatableRewriter.processTag(wrapper.user(), wrapper.passthrough(Types.TRUSTED_OPTIONAL_TAG)); // Unsigned content final int filterMaskType = wrapper.passthrough(Types.VAR_INT); if (filterMaskType == 2) { // Partially filtered wrapper.passthrough(Types.LONG_ARRAY_PRIMITIVE); // Mask } wrapper.passthrough(ChatType.TYPE); // Chat Type translatableRewriter.processTag(wrapper.user(), wrapper.passthrough(Types.TRUSTED_TAG)); // Name translatableRewriter.processTag(wrapper.user(), wrapper.passthrough(Types.TRUSTED_OPTIONAL_TAG)); // Target Name }); registerServerbound(ServerboundPackets1_21_4.CHAT_COMMAND_SIGNED, wrapper -> { wrapper.passthrough(Types.STRING); // Command wrapper.passthrough(Types.LONG); // Timestamp wrapper.passthrough(Types.LONG); // Salt final int signatures = Limit.max(wrapper.passthrough(Types.VAR_INT), 8); for (int i = 0; i < signatures; i++) { wrapper.passthrough(Types.STRING); // Argument name wrapper.passthrough(Types.SIGNATURE_BYTES); // Signature } wrapper.passthrough(Types.VAR_INT); // Offset wrapper.passthrough(Types.ACKNOWLEDGED_BIT_SET); // Acknowledged wrapper.write(Types.BYTE, (byte) 0); // Checksum }); registerServerbound(ServerboundPackets1_21_4.CHAT, wrapper -> { wrapper.passthrough(Types.STRING); // Message wrapper.passthrough(Types.LONG); // Timestamp wrapper.passthrough(Types.LONG); // Salt wrapper.passthrough(Types.OPTIONAL_SIGNATURE_BYTES); // Signature wrapper.passthrough(Types.VAR_INT); // Offset wrapper.passthrough(Types.ACKNOWLEDGED_BIT_SET); // Acknowledged wrapper.write(Types.BYTE, (byte) 0); // Checksum }); cancelClientbound(ClientboundPackets1_21_5.TEST_INSTANCE_BLOCK_STATUS); } @Override public void init(final UserConnection user) { addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_21_4.PLAYER)); user.put(new HashedItemConverterStorage(this)); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_21_5 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_21_5 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public BackwardsRegistryRewriter getRegistryDataRewriter() { return registryDataRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public ComponentRewriter1_21_5 getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } @Override public RecipeDisplayRewriter getRecipeRewriter() { return recipeRewriter; } @Override public Types1_20_5 types() { return VersionedTypes.V1_21_5; } @Override public Types1_20_5 mappedTypes() { return VersionedTypes.V1_21_4; } @Override protected PacketTypesProvider createPacketTypesProvider() { return new SimplePacketTypesProvider<>( packetTypeMap(unmappedClientboundPacketType, ClientboundPackets1_21_5.class, ClientboundConfigurationPackets1_21.class), packetTypeMap(mappedClientboundPacketType, ClientboundPackets1_21_2.class, ClientboundConfigurationPackets1_21.class), packetTypeMap(mappedServerboundPacketType, ServerboundPackets1_21_5.class, ServerboundConfigurationPackets1_20_5.class), packetTypeMap(unmappedServerboundPacketType, ServerboundPackets1_21_4.class, ServerboundConfigurationPackets1_20_5.class) ); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_5to1_21_4/rewriter/BlockItemPacketRewriter1_21_5.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.FloatTag; import com.viaversion.nbt.tag.IntArrayTag; import com.viaversion.nbt.tag.IntTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.LongArrayTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.rewriters.BackwardsStructuredItemRewriter; import com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.Protocol1_21_5To1_21_4; import com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.storage.HashedItemConverterStorage; import com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.storage.HorseDataStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.Mappings; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.data.entity.TrackedEntity; import com.viaversion.viaversion.api.minecraft.Holder; import com.viaversion.viaversion.api.minecraft.HolderSet; import com.viaversion.viaversion.api.minecraft.PaintingVariant; import com.viaversion.viaversion.api.minecraft.SoundEvent; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.chunks.Chunk1_18; import com.viaversion.viaversion.api.minecraft.chunks.DataPalette; import com.viaversion.viaversion.api.minecraft.chunks.Heightmap; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_5; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.item.data.ArmorTrimMaterial; import com.viaversion.viaversion.api.minecraft.item.data.BlocksAttacks; import com.viaversion.viaversion.api.minecraft.item.data.BlocksAttacks.DamageReduction; import com.viaversion.viaversion.api.minecraft.item.data.BlocksAttacks.ItemDamageFunction; import com.viaversion.viaversion.api.minecraft.item.data.Equippable; import com.viaversion.viaversion.api.minecraft.item.data.ProvidesTrimMaterial; import com.viaversion.viaversion.api.minecraft.item.data.ToolProperties; import com.viaversion.viaversion.api.minecraft.item.data.TooltipDisplay; import com.viaversion.viaversion.api.minecraft.item.data.TropicalFishPattern; import com.viaversion.viaversion.api.minecraft.item.data.Weapon; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.rewriter.ComponentRewriter; import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkBiomesType1_19_4; import com.viaversion.viaversion.api.type.types.chunk.ChunkBiomesType1_21_5; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_21_5; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.data.item.ItemHasherBase; import com.viaversion.viaversion.libs.fastutil.ints.IntArrayList; import com.viaversion.viaversion.libs.fastutil.ints.IntLinkedOpenHashSet; import com.viaversion.viaversion.libs.fastutil.ints.IntList; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPacket1_21_4; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPackets1_21_4; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ClientboundPacket1_21_5; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ClientboundPackets1_21_5; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2; import com.viaversion.viaversion.util.Either; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.Limit; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.viaversion.viaversion.protocols.v1_21_4to1_21_5.rewriter.BlockItemPacketRewriter1_21_5.downgradeItemData; import static com.viaversion.viaversion.protocols.v1_21_4to1_21_5.rewriter.BlockItemPacketRewriter1_21_5.updateItemData; import static com.viaversion.viaversion.util.MathUtil.ceilLog2; public final class BlockItemPacketRewriter1_21_5 extends BackwardsStructuredItemRewriter { private static final int SADDLE_EQUIPMENT_SLOT = 7; static final byte SADDLED_FLAG = 4; public BlockItemPacketRewriter1_21_5(final Protocol1_21_5To1_21_4 protocol) { super(protocol); } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_21_5.LEVEL_CHUNK_WITH_LIGHT, wrapper -> { final EntityTracker tracker = protocol.getEntityRewriter().tracker(wrapper.user()); final Mappings blockStateMappings = protocol.getMappingData().getBlockStateMappings(); final Type chunkType = new ChunkType1_21_5(tracker.currentWorldSectionHeight(), ceilLog2(blockStateMappings.size()), ceilLog2(tracker.biomesSent())); final Chunk chunk = wrapper.read(chunkType); protocol.getBlockRewriter().handleChunk(chunk); protocol.getBlockRewriter().handleBlockEntities(chunk, wrapper.user()); final Type newChunkType = new ChunkType1_20_2(tracker.currentWorldSectionHeight(), ceilLog2(blockStateMappings.mappedSize()), ceilLog2(tracker.biomesSent())); final CompoundTag heightmapTag = new CompoundTag(); for (final Heightmap heightmap : chunk.heightmaps()) { final String typeKey = heightmapType(heightmap.type()); if (typeKey == null) { protocol.getLogger().warning("Unknown heightmap type id: " + heightmap.type()); continue; } heightmapTag.put(typeKey, new LongArrayTag(heightmap.data())); } final Chunk mappedChunk = new Chunk1_18(chunk.getX(), chunk.getZ(), chunk.getSections(), heightmapTag, chunk.blockEntities()); wrapper.write(newChunkType, mappedChunk); }); protocol.registerClientbound(ClientboundPackets1_21_5.CHUNKS_BIOMES, wrapper -> { final EntityTracker tracker = protocol.getEntityRewriter().tracker(wrapper.user()); final int globalPaletteBiomeBits = ceilLog2(tracker.biomesSent()); final Type biomesType = new ChunkBiomesType1_21_5(tracker.currentWorldSectionHeight(), globalPaletteBiomeBits); final Type newBiomesType = new ChunkBiomesType1_19_4(tracker.currentWorldSectionHeight(), globalPaletteBiomeBits); final int size = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < size; i++) { wrapper.passthrough(Types.CHUNK_POSITION); wrapper.passthroughAndMap(biomesType, newBiomesType); } }); protocol.replaceServerbound(ServerboundPackets1_21_4.SET_CREATIVE_MODE_SLOT, wrapper -> { wrapper.passthrough(Types.SHORT); // Slot final Item item = handleItemToServer(wrapper.user(), wrapper.read(mappedItemType())); wrapper.write(VersionedTypes.V1_21_5.lengthPrefixedItem(), item); }); protocol.replaceServerbound(ServerboundPackets1_21_4.CONTAINER_CLICK, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Container id wrapper.passthrough(Types.VAR_INT); // State id wrapper.passthrough(Types.SHORT); // Slot wrapper.passthrough(Types.BYTE); // Button wrapper.passthrough(Types.VAR_INT); // Mode // Try our best to get a hashed item out of it - will be wrong for some data component types that don't have their conversion implemented final HashedItemConverterStorage hashedItemConverter = wrapper.user().get(HashedItemConverterStorage.class); final int affectedItems = Limit.max(wrapper.passthrough(Types.VAR_INT), 128); for (int i = 0; i < affectedItems; i++) { wrapper.passthrough(Types.SHORT); // Slot final Item item = handleItemToServer(wrapper.user(), wrapper.read(mappedItemType())); wrapper.write(Types.HASHED_ITEM, ItemHasherBase.toHashedItem(hashedItemConverter.hasher(), item)); } final Item carriedItem = handleItemToServer(wrapper.user(), wrapper.read(mappedItemType())); wrapper.write(Types.HASHED_ITEM, ItemHasherBase.toHashedItem(hashedItemConverter.hasher(), carriedItem)); }); protocol.replaceClientbound(ClientboundPackets1_21_5.SET_EQUIPMENT, wrapper -> { final int entityId = wrapper.passthrough(Types.VAR_INT); final TrackedEntity trackedEntity = protocol.getEntityRewriter().tracker(wrapper.user()).entity(entityId); // Remove saddle equipment, keep the rest final IntList keptSlots = new IntArrayList(); final List keptItems = new ArrayList<>(); byte value; do { value = wrapper.read(Types.BYTE); final int equipmentSlot = value & 0x7F; final Item item = wrapper.read(itemType()); if (equipmentSlot == SADDLE_EQUIPMENT_SLOT) { // Send saddle entity data for horses if (trackedEntity != null && ( trackedEntity.entityType().isOrHasParent(EntityTypes1_21_5.ABSTRACT_HORSE) || trackedEntity.entityType().isOrHasParent(EntityTypes1_21_5.PIG) || trackedEntity.entityType().isOrHasParent(EntityTypes1_21_5.STRIDER) )) { sendSaddledEntityData(wrapper.user(), trackedEntity, entityId, item.identifier() == 800); } } else { keptSlots.add(equipmentSlot); keptItems.add(handleItemToClient(wrapper.user(), item)); } } while (value < 0); if (keptSlots.isEmpty()) { wrapper.cancel(); return; } for (int i = 0; i < keptSlots.size(); i++) { final int slot = keptSlots.getInt(i); final boolean more = i < keptSlots.size() - 1; wrapper.write(Types.BYTE, (byte) (more ? (slot | -128) : slot)); wrapper.write(mappedItemType(), keptItems.get(i)); } }); protocol.replaceClientbound(ClientboundPackets1_21_5.UPDATE_ADVANCEMENTS, wrapper -> { wrapper.passthrough(Types.BOOLEAN); // Reset/clear int size = wrapper.passthrough(Types.VAR_INT); // Mapping size for (int i = 0; i < size; i++) { wrapper.passthrough(Types.STRING); // Identifier wrapper.passthrough(Types.OPTIONAL_STRING); // Parent // Display data if (wrapper.passthrough(Types.BOOLEAN)) { final Tag title = wrapper.passthrough(Types.TRUSTED_TAG); final Tag description = wrapper.passthrough(Types.TRUSTED_TAG); final ComponentRewriter componentRewriter = protocol.getComponentRewriter(); if (componentRewriter != null) { componentRewriter.processTag(wrapper.user(), title); componentRewriter.processTag(wrapper.user(), description); } passthroughClientboundItem(wrapper); // Icon wrapper.passthrough(Types.VAR_INT); // Frame type int flags = wrapper.passthrough(Types.INT); // Flags if ((flags & 1) != 0) { convertClientAsset(wrapper); } wrapper.passthrough(Types.FLOAT); // X wrapper.passthrough(Types.FLOAT); // Y } int requirements = wrapper.passthrough(Types.VAR_INT); for (int array = 0; array < requirements; array++) { wrapper.passthrough(Types.STRING_ARRAY); } wrapper.passthrough(Types.BOOLEAN); // Send telemetry } wrapper.passthrough(Types.STRING_ARRAY); // Removed final int progressSize = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < progressSize; i++) { wrapper.passthrough(Types.STRING); // Key final int criterionSize = wrapper.passthrough(Types.VAR_INT); for (int j = 0; j < criterionSize; j++) { wrapper.passthrough(Types.STRING); // Key wrapper.passthrough(Types.OPTIONAL_LONG); // Obtained instant } } wrapper.read(Types.BOOLEAN); // Show advancements }); } private void convertClientAsset(final PacketWrapper wrapper) { final String background = wrapper.read(Types.STRING); final String namespace = Key.namespace(background); final String path = Key.stripNamespace(background); wrapper.write(Types.STRING, namespace + ":textures/" + path + ".png"); } private void sendSaddledEntityData(final UserConnection connection, final TrackedEntity trackedEntity, final int entityId, final boolean saddled) { EntityData entityData = null; if (trackedEntity.entityType().isOrHasParent(EntityTypes1_21_5.ABSTRACT_HORSE)) { 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, saddled)); if (saddled) { data = (byte) (data | SADDLED_FLAG); } entityData = new EntityData(17, VersionedTypes.V1_21_4.entityDataTypes.byteType, data); } else if (trackedEntity.entityType().isOrHasParent(EntityTypes1_21_5.PIG)) { entityData = new EntityData(17, VersionedTypes.V1_21_4.entityDataTypes.booleanType, saddled); } else if (trackedEntity.entityType().isOrHasParent(EntityTypes1_21_5.STRIDER)) { entityData = new EntityData(19, VersionedTypes.V1_21_4.entityDataTypes.booleanType, saddled); } if (entityData != null) { final PacketWrapper entityDataPacket = PacketWrapper.create(ClientboundPackets1_21_2.SET_ENTITY_DATA, connection); final List entityDataList = new ArrayList<>(); entityDataList.add(entityData); entityDataPacket.write(Types.VAR_INT, entityId); entityDataPacket.write(VersionedTypes.V1_21_4.entityDataList, entityDataList); entityDataPacket.send(Protocol1_21_5To1_21_4.class); } } private String heightmapType(final int id) { return switch (id) { case 0 -> "WORLD_SURFACE_WG"; case 1 -> "WORLD_SURFACE"; case 2 -> "OCEAN_FLOOR_WG"; case 3 -> "OCEAN_FLOOR"; case 4 -> "MOTION_BLOCKING"; case 5 -> "MOTION_BLOCKING_NO_LEAVES"; default -> null; }; } @Override protected void backupInconvertibleData(final UserConnection connection, final Item item, final StructuredDataContainer dataContainer, final CompoundTag backupTag) { super.backupInconvertibleData(connection, item, dataContainer, backupTag); final ToolProperties toolProperties = dataContainer.get(StructuredDataKey.TOOL1_21_5); if (toolProperties != null && toolProperties.canDestroyBlocksInCreative()) { backupTag.putBoolean("tool", true); } final Equippable equippable = dataContainer.get(StructuredDataKey.EQUIPPABLE1_21_5); if (equippable != null && equippable.equipOnInteract()) { backupTag.putBoolean("equippable", true); } final Weapon weapon = dataContainer.get(StructuredDataKey.WEAPON); if (weapon != null) { final CompoundTag weaponTag = new CompoundTag(); backupTag.put("weapon", weaponTag); weaponTag.putInt("item_damage_per_attack", weapon.itemDamagePerAttack()); weaponTag.putFloat("disable_blocking_for_seconds", weapon.disableBlockingForSeconds()); } final ProvidesTrimMaterial providesTrimMaterial = dataContainer.get(StructuredDataKey.PROVIDES_TRIM_MATERIAL1_21_5); if (providesTrimMaterial != null) { final Tag materialTag = eitherHolderToTag(providesTrimMaterial.material(), (material, tag) -> { tag.putString("asset_name", material.assetName()); tag.putInt("item_id", material.itemId()); tag.putFloat("item_model_index", material.itemModelIndex()); final CompoundTag overrideArmorMaterials = new CompoundTag(); material.overrideArmorMaterials().forEach(overrideArmorMaterials::putString); tag.put("override_armor_materials", overrideArmorMaterials); tag.put("description", material.description()); }); backupTag.put("provides_trim_material", materialTag); } final BlocksAttacks blocksAttacks = dataContainer.get(StructuredDataKey.BLOCKS_ATTACKS1_21_5); if (blocksAttacks != null) { final CompoundTag blocksAttackTag = new CompoundTag(); backupTag.put("blocks_attack", blocksAttackTag); blocksAttackTag.putFloat("block_delay_seconds", blocksAttacks.blockDelaySeconds()); blocksAttackTag.putFloat("disable_cooldown_scale", blocksAttacks.disableCooldownScale()); final ListTag damageReductions = new ListTag<>(CompoundTag.class); blocksAttackTag.put("damage_reductions", damageReductions); for (final DamageReduction damageReduction : blocksAttacks.damageReductions()) { final CompoundTag damageReductionTag = new CompoundTag(); damageReductionTag.putFloat("horizontal_blocking_angle", damageReduction.horizontalBlockingAngle()); if (damageReduction.type() != null) { damageReductionTag.put("type", holderSetToTag(damageReduction.type())); } damageReductionTag.putFloat("base", damageReduction.base()); damageReductionTag.putFloat("factor", damageReduction.factor()); damageReductions.add(damageReductionTag); } final CompoundTag itemDamageTag = new CompoundTag(); blocksAttackTag.put("item_damage", itemDamageTag); itemDamageTag.putFloat("threshold", blocksAttacks.itemDamage().threshold()); itemDamageTag.putFloat("base", blocksAttacks.itemDamage().base()); itemDamageTag.putFloat("factor", blocksAttacks.itemDamage().factor()); if (blocksAttacks.bypassedBy() != null) { itemDamageTag.putString("bypassed_by", blocksAttacks.bypassedBy().tagKey()); } if (blocksAttacks.blockSound() != null) { blocksAttackTag.put("block_sound", holderToTag(blocksAttacks.blockSound(), this::saveSoundEvent)); } if (blocksAttacks.disableSound() != null) { blocksAttackTag.put("disable_sound", holderToTag(blocksAttacks.disableSound(), this::saveSoundEvent)); } } final TooltipDisplay tooltipDisplay = dataContainer.get(StructuredDataKey.TOOLTIP_DISPLAY); if (tooltipDisplay != null) { backupTag.put("hidden_components", new IntArrayTag(tooltipDisplay.hiddenComponents().toIntArray())); } final TropicalFishPattern tropicalFishPattern = dataContainer.get(StructuredDataKey.TROPICAL_FISH_PATTERN); if (tropicalFishPattern != null) { backupTag.putInt("tropical_fish_pattern", tropicalFishPattern.packedId()); } saveKeyData(StructuredDataKey.PROVIDES_BANNER_PATTERNS1_21_5, dataContainer, backupTag); saveFloatData(StructuredDataKey.POTION_DURATION_SCALE, dataContainer, backupTag); saveIntData(StructuredDataKey.VILLAGER_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.FOX_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.SALMON_SIZE, dataContainer, backupTag); saveIntData(StructuredDataKey.PARROT_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.TROPICAL_FISH_BASE_COLOR, dataContainer, backupTag); saveIntData(StructuredDataKey.TROPICAL_FISH_PATTERN_COLOR, dataContainer, backupTag); saveIntData(StructuredDataKey.MOOSHROOM_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.RABBIT_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.FROG_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.HORSE_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.LLAMA_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.AXOLOTL_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.CAT_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.CAT_COLLAR, dataContainer, backupTag); saveIntData(StructuredDataKey.SHEEP_COLOR, dataContainer, backupTag); saveIntData(StructuredDataKey.SHULKER_COLOR, dataContainer, backupTag); saveIntData(StructuredDataKey.WOLF_SOUND_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.COW_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.PIG_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.WOLF_VARIANT, dataContainer, backupTag); final Either chickenVariant = dataContainer.get(StructuredDataKey.CHICKEN_VARIANT1_21_5); if (chickenVariant != null) { if (chickenVariant.isLeft()) { backupTag.putInt("chicken_variant", chickenVariant.left()); } else { backupTag.putString("chicken_variant", chickenVariant.right()); } } saveHolderData(StructuredDataKey.PAINTING_VARIANT, dataContainer, backupTag, (paintingVariant, tag) -> { tag.putInt("width", paintingVariant.width()); tag.putInt("height", paintingVariant.height()); tag.putString("asset_id", paintingVariant.assetId()); if (paintingVariant.title() != null) { tag.put("title", paintingVariant.title()); } if (paintingVariant.author() != null) { tag.put("author", paintingVariant.author()); } }); saveHolderData(StructuredDataKey.BREAK_SOUND, dataContainer, backupTag, this::saveSoundEvent); } @Override protected void handleItemDataComponentsToClient(final UserConnection connection, final Item item, final StructuredDataContainer dataContainer) { super.handleItemDataComponentsToClient(connection, item, dataContainer); downgradeItemData(item); } @Override protected void handleItemDataComponentsToServer(final UserConnection connection, final Item item, final StructuredDataContainer container) { super.handleItemDataComponentsToServer(connection, item, container); updateItemData(item); } @Override protected void restoreBackupData(final Item item, final StructuredDataContainer data, final CompoundTag customData) { super.restoreBackupData(item, data, customData); if (!(customData.remove(nbtTagName("backup")) instanceof final CompoundTag backupTag)) { return; } final IntArrayTag hiddenComponentsTag = backupTag.getIntArrayTag("hidden_components"); if (hiddenComponentsTag != null) { data.set(StructuredDataKey.TOOLTIP_DISPLAY, new TooltipDisplay(data.has(StructuredDataKey.HIDE_TOOLTIP), new IntLinkedOpenHashSet(hiddenComponentsTag.getValue()))); } if (backupTag.getBoolean("tool")) { data.replace(StructuredDataKey.TOOL1_20_5, StructuredDataKey.TOOL1_21_5, t -> new ToolProperties(t.rules(), t.defaultMiningSpeed(), t.damagePerBlock(), true)); } if (backupTag.getBoolean("equippable")) { data.replace(StructuredDataKey.EQUIPPABLE1_21_2, StructuredDataKey.EQUIPPABLE1_21_5, e -> new Equippable(e.equipmentSlot(), e.soundEvent(), e.model(), e.cameraOverlay(), e.allowedEntities(), e.dispensable(), e.swappable(), e.damageOnHurt(), true)); } final CompoundTag weaponTag = backupTag.getCompoundTag("weapon"); if (weaponTag != null) { data.set(StructuredDataKey.WEAPON, new Weapon(weaponTag.getInt("item_damage_per_attack"), weaponTag.getFloat("disable_blocking_for_seconds"))); } final Tag materialTag = backupTag.get("provides_trim_material"); if (materialTag != null) { data.set(StructuredDataKey.PROVIDES_TRIM_MATERIAL1_21_5, new ProvidesTrimMaterial(restoreEitherHolder(backupTag, "provides_trim_material", tag -> { final String assetName = tag.getString("asset_name"); final int itemId = tag.getInt("item_id"); final float itemModelIndex = tag.getFloat("item_model_index"); final CompoundTag overrideArmorMaterialsTag = tag.getCompoundTag("override_armor_materials"); final Map overrideArmorMaterials = new HashMap<>(); for (final String key : overrideArmorMaterialsTag.keySet()) { overrideArmorMaterials.put(key, overrideArmorMaterialsTag.getString(key)); } final Tag description = tag.get("description"); return new ArmorTrimMaterial(assetName, itemId, itemModelIndex, overrideArmorMaterials, description); }))); } final CompoundTag blocksAttackTag = backupTag.getCompoundTag("blocks_attack"); if (blocksAttackTag != null) { final float blockDelaySeconds = blocksAttackTag.getFloat("block_delay_seconds"); final float disableCooldownScale = blocksAttackTag.getFloat("disable_cooldown_scale"); final CompoundTag itemDamageTag = blocksAttackTag.getCompoundTag("item_damage"); final ItemDamageFunction itemDamage = new ItemDamageFunction(itemDamageTag.getFloat("threshold"), itemDamageTag.getFloat("base"), itemDamageTag.getFloat("factor")); final String bypassedBy = blocksAttackTag.getString("bypassed_by"); final Holder blockSound = blocksAttackTag.contains("block_sound") ? restoreHolder(blocksAttackTag, "block_sound", this::tagToSound) : null; final Holder disableSound = blocksAttackTag.contains("disable_sound") ? restoreHolder(blocksAttackTag, "disable_sound", this::tagToSound) : null; final List damageReductions = new ArrayList<>(); for (final CompoundTag damageReductionTag : blocksAttackTag.getListTag("damage_reductions", CompoundTag.class)) { final float horizontalBlockingAngle = damageReductionTag.getFloat("horizontal_blocking_angle"); final HolderSet type = damageReductionTag.contains("type") ? restoreHolderSet(damageReductionTag, "type") : null; final float base = damageReductionTag.getFloat("base"); final float factor = damageReductionTag.getFloat("factor"); damageReductions.add(new DamageReduction(horizontalBlockingAngle, type, base, factor)); } data.set(StructuredDataKey.BLOCKS_ATTACKS1_21_5, new BlocksAttacks( blockDelaySeconds, disableCooldownScale, damageReductions.toArray(new DamageReduction[0]), itemDamage, bypassedBy != null ? HolderSet.of(bypassedBy) : null, blockSound, disableSound )); } final IntTag chickenVariant = backupTag.getIntTag("chicken_variant"); if (chickenVariant != null) { data.set(StructuredDataKey.CHICKEN_VARIANT1_21_5, Either.left(chickenVariant.asInt())); } else { final String chickenVariantKey = backupTag.getString("chicken_variant"); if (chickenVariantKey != null) { data.set(StructuredDataKey.CHICKEN_VARIANT1_21_5, Either.right(chickenVariantKey)); } } final IntTag tropicalFishPattern = backupTag.getIntTag("tropical_fish_pattern"); if (tropicalFishPattern != null) { data.set(StructuredDataKey.TROPICAL_FISH_PATTERN, new TropicalFishPattern(tropicalFishPattern.asInt())); } restoreKeyData(StructuredDataKey.PROVIDES_BANNER_PATTERNS1_21_5, data, backupTag); restoreFloatData(StructuredDataKey.POTION_DURATION_SCALE, data, backupTag); restoreIntData(StructuredDataKey.VILLAGER_VARIANT, data, backupTag); restoreIntData(StructuredDataKey.FOX_VARIANT, data, backupTag); restoreIntData(StructuredDataKey.SALMON_SIZE, data, backupTag); restoreIntData(StructuredDataKey.PARROT_VARIANT, data, backupTag); restoreIntData(StructuredDataKey.TROPICAL_FISH_BASE_COLOR, data, backupTag); restoreIntData(StructuredDataKey.TROPICAL_FISH_PATTERN_COLOR, data, backupTag); restoreIntData(StructuredDataKey.MOOSHROOM_VARIANT, data, backupTag); restoreIntData(StructuredDataKey.RABBIT_VARIANT, data, backupTag); restoreIntData(StructuredDataKey.FROG_VARIANT, data, backupTag); restoreIntData(StructuredDataKey.HORSE_VARIANT, data, backupTag); restoreIntData(StructuredDataKey.LLAMA_VARIANT, data, backupTag); restoreIntData(StructuredDataKey.AXOLOTL_VARIANT, data, backupTag); restoreIntData(StructuredDataKey.CAT_VARIANT, data, backupTag); restoreIntData(StructuredDataKey.CAT_COLLAR, data, backupTag); restoreIntData(StructuredDataKey.SHEEP_COLOR, data, backupTag); restoreIntData(StructuredDataKey.SHULKER_COLOR, data, backupTag); restoreIntData(StructuredDataKey.WOLF_SOUND_VARIANT, data, backupTag); restoreIntData(StructuredDataKey.COW_VARIANT, data, backupTag); restoreIntData(StructuredDataKey.PIG_VARIANT, data, backupTag); restoreIntData(StructuredDataKey.WOLF_VARIANT, data, backupTag); restoreHolderData(StructuredDataKey.BREAK_SOUND, data, backupTag, this::tagToSound); restoreHolderData(StructuredDataKey.PAINTING_VARIANT, data, backupTag, tag -> { final int width = tag.getInt("width"); final int height = tag.getInt("height"); final String assetId = tag.getString("asset_id"); final Tag title = tag.get("title"); final Tag author = tag.get("author"); return new PaintingVariant(width, height, assetId, title, author); }); removeCustomTag(data, customData); } private SoundEvent tagToSound(final CompoundTag tag) { final String identifier = tag.getString("identifier"); final FloatTag fixedRangeTag = tag.getFloatTag("fixed_range"); return new SoundEvent(identifier, fixedRangeTag != null ? fixedRangeTag.asFloat() : null); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_5to1_21_4/rewriter/BlockPacketRewriter1_21_5.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.Protocol1_21_5To1_21_4; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.blockentity.BlockEntity; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_21_5; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ClientboundPacket1_21_5; import com.viaversion.viaversion.rewriter.BlockRewriter; public final class BlockPacketRewriter1_21_5 extends BlockRewriter { private static final int SIGN_BOCK_ENTITY_ID = 7; private static final int HANGING_SIGN_BOCK_ENTITY_ID = 8; private final Protocol1_21_5To1_21_4 protocol; public BlockPacketRewriter1_21_5(final Protocol1_21_5To1_21_4 protocol) { super(protocol, Types.BLOCK_POSITION1_14, Types.COMPOUND_TAG, ChunkType1_21_5::new, ChunkType1_20_2::new); this.protocol = protocol; } @Override public void handleBlockEntity(final UserConnection connection, final BlockEntity blockEntity) { final CompoundTag tag = blockEntity.tag(); if (tag == null) { return; } if (blockEntity.typeId() == SIGN_BOCK_ENTITY_ID || blockEntity.typeId() == HANGING_SIGN_BOCK_ENTITY_ID) { updateSignMessages(connection, tag.getCompoundTag("front_text")); updateSignMessages(connection, tag.getCompoundTag("back_text")); } final Tag customName = tag.get("CustomName"); if (customName != null) { tag.putString("CustomName", protocol.getComponentRewriter().toUglyJson(connection, customName)); } } private void updateSignMessages(final UserConnection connection, final CompoundTag tag) { if (tag == null) { return; } final ListTag messages = tag.getListTag("messages"); tag.put("messages", protocol.getComponentRewriter().updateComponentList(connection, messages)); final ListTag filteredMessages = tag.getListTag("filtered_messages"); if (filteredMessages != null) { tag.put("filtered_messages", protocol.getComponentRewriter().updateComponentList(connection, filteredMessages)); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_5to1_21_4/rewriter/ComponentRewriter1_21_5.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.NumberTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.text.NBTComponentRewriter; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ClientboundPacket1_21_5; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.rewriter.BlockItemPacketRewriter1_21_5; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.SerializerVersion; import com.viaversion.viaversion.util.TagUtil; import java.util.HashSet; import java.util.Set; import org.checkerframework.checker.nullness.qual.Nullable; import static com.viaversion.viaversion.util.TagUtil.getNamespacedCompoundTag; import static com.viaversion.viaversion.util.TagUtil.getNamespacedCompoundTagList; import static com.viaversion.viaversion.util.TagUtil.getNamespacedNumberTag; import static com.viaversion.viaversion.util.TagUtil.removeNamespaced; public final class ComponentRewriter1_21_5 extends NBTComponentRewriter { public ComponentRewriter1_21_5(final BackwardsProtocol protocol) { super(protocol); } @Override protected void processCompoundTag(final UserConnection connection, final CompoundTag tag) { super.processCompoundTag(connection, tag); if (tag.remove("hover_event") instanceof final CompoundTag hoverEvent) { tag.put("hoverEvent", hoverEvent); } if (tag.remove("click_event") instanceof final CompoundTag clickEvent) { tag.put("clickEvent", clickEvent); updateClickEvent(clickEvent); } } @Override protected void handleHoverEvent(final UserConnection connection, final CompoundTag hoverEventTag) { final String action = hoverEventTag.getString("action"); if (action == null) { return; } switch (action) { case "show_text" -> updateShowTextHover(hoverEventTag); case "show_entity" -> updateShowEntityHover(hoverEventTag); case "show_item" -> updateShowItemHover(connection, hoverEventTag); } } private void updateClickEvent(final CompoundTag clickEventTag) { final String action = clickEventTag.getString("action"); if (action == null) { return; } 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" -> { 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")); } } private void updateShowTextHover(final CompoundTag hoverEventTag) { final Tag text = hoverEventTag.remove("value"); hoverEventTag.put("contents", text); } private void updateShowItemHover(final UserConnection connection, final CompoundTag hoverEventTag) { final CompoundTag contents = new CompoundTag(); hoverEventTag.put("contents", contents); if (hoverEventTag.get("count") instanceof NumberTag countTag) { contents.put("count", countTag); } if (hoverEventTag.get("id") instanceof StringTag idTag) { contents.put("id", idTag); } final CompoundTag componentsTag = hoverEventTag.getCompoundTag("components"); handleShowItem(connection, contents, componentsTag); if (componentsTag != null) { hoverEventTag.remove("components"); contents.put("components", componentsTag); } } @Override protected void handleShowItem(final UserConnection connection, final CompoundTag itemTag, @Nullable final CompoundTag componentsTag) { super.handleShowItem(connection, itemTag, componentsTag); if (componentsTag == null) { return; } insertUglyJson(componentsTag, connection); updateDataComponents(componentsTag); removeDataComponents(componentsTag, BlockItemPacketRewriter1_21_5.NEW_DATA_TO_REMOVE); } private void updateDataComponents(final CompoundTag componentsTag) { final CompoundTag tooltipDisplay = getNamespacedCompoundTag(componentsTag, "tooltip_display"); Set hiddenComponents = Set.of(); if (tooltipDisplay != null) { final ListTag hiddenComponentsTag = tooltipDisplay.getListTag("hidden_components", StringTag.class); if (hiddenComponentsTag != null) { hiddenComponents = new HashSet<>(hiddenComponentsTag.size()); for (final StringTag stringTag : hiddenComponentsTag) { hiddenComponents.add(Key.stripMinecraftNamespace(stringTag.getValue())); } } } if (hiddenComponents.containsAll(BlockItemPacketRewriter1_21_5.HIDE_ADDITIONAL_KEYS.stream().map(StructuredDataKey::identifier).toList())) { componentsTag.put("hide_additional_tooltip", new CompoundTag()); } final ListTag attributeModifiers = getNamespacedCompoundTagList(componentsTag, "attribute_modifiers"); if (attributeModifiers != null) { removeNamespaced(componentsTag, "attribute_modifiers"); final CompoundTag attributesParent = new CompoundTag(); attributesParent.put("modifiers", attributeModifiers); attributesParent.putBoolean("show_in_tooltip", hiddenComponents.contains("attribute_modifiers")); componentsTag.put("attribute_modifiers", attributesParent); } final NumberTag dyedColor = getNamespacedNumberTag(componentsTag, "dyed_color"); if (dyedColor != null) { removeNamespaced(componentsTag, "dyed_color"); final CompoundTag dyedColorParent = new CompoundTag(); dyedColorParent.put("rgb", dyedColor); dyedColorParent.putBoolean("show_in_tooltip", hiddenComponents.contains("dyed_color")); componentsTag.put("dyed_color", dyedColorParent); } updateShowInTooltip(componentsTag, "unbreakable", hiddenComponents); updateShowInTooltip(componentsTag, "dyed_color", hiddenComponents); updateShowInTooltip(componentsTag, "trim", hiddenComponents); updateShowInTooltip(componentsTag, "jukebox_playable", hiddenComponents); handleAdventureModePredicate(componentsTag, "can_place_on", hiddenComponents); handleAdventureModePredicate(componentsTag, "can_break", hiddenComponents); handleEnchantments(componentsTag, "enchantments", hiddenComponents); handleEnchantments(componentsTag, "stored_enchantments", hiddenComponents); removeDataComponents(componentsTag, StructuredDataKey.INSTRUMENT1_21_5, StructuredDataKey.JUKEBOX_PLAYABLE1_21_5); } private void updateShowInTooltip(final CompoundTag tag, final String key, final Set hiddenComponents) { final CompoundTag data = getNamespacedCompoundTag(tag, key); if (data != null) { data.putBoolean("show_in_tooltip", !hiddenComponents.contains(key)); } } private void handleAdventureModePredicate(final CompoundTag componentsTag, final String key, final Set hiddenComponents) { final ListTag blockPredicates = getNamespacedCompoundTagList(componentsTag, key); if (blockPredicates == null) { return; } removeDataComponents(componentsTag, key); final CompoundTag predicate = new CompoundTag(); predicate.put("predicates", blockPredicates); predicate.putBoolean("show_in_tooltip", !hiddenComponents.contains(key)); componentsTag.put(key, predicate); } private void handleEnchantments(final CompoundTag componentsTag, final String key, final Set hiddenComponents) { final CompoundTag levels = getNamespacedCompoundTag(componentsTag, key); if (levels != null) { removeNamespaced(componentsTag, key); final CompoundTag enchantments = new CompoundTag(); enchantments.put("levels", levels); enchantments.putBoolean("show_in_tooltip", !hiddenComponents.contains(key)); componentsTag.put(key, enchantments); } } private void insertUglyJson(final CompoundTag componentsTag, final UserConnection connection) { insertUglyJson(componentsTag, "item_name", connection); insertUglyJson(componentsTag, "custom_name", connection); final String loreKey = TagUtil.getNamespacedTagKey(componentsTag, "lore"); final ListTag lore = componentsTag.getListTag(loreKey); if (lore != null) { componentsTag.put(loreKey, updateComponentList(connection, lore)); } } public ListTag updateComponentList(final UserConnection connection, final ListTag messages) { final ListTag updatedMessages = new ListTag<>(StringTag.class); for (final Tag message : messages) { updatedMessages.add(new StringTag(toUglyJson(connection, message))); } return updatedMessages; } private void insertUglyJson(final CompoundTag componentsTag, final String key, final UserConnection connection) { final String actualKey = TagUtil.getNamespacedTagKey(componentsTag, key); final Tag tag = componentsTag.get(actualKey); if (tag == null) { return; } componentsTag.putString(actualKey, toUglyJson(connection, tag)); } private void updateShowEntityHover(final CompoundTag hoverEventTag) { final CompoundTag contents = new CompoundTag(); hoverEventTag.put("contents", contents); final Tag nameTag = hoverEventTag.remove("name"); if (nameTag != null) { contents.put("name", nameTag); } if (hoverEventTag.remove("id") instanceof StringTag idTag) { idTag.setValue(protocol.getEntityRewriter().mappedEntityIdentifier(idTag.getValue())); contents.put("type", idTag); } final Tag uuidTag = hoverEventTag.remove("uuid"); if (uuidTag != null) { contents.put("id", uuidTag); } } String toUglyJson(final UserConnection connection, final Tag value) { processTag(connection, value); return SerializerVersion.V1_21_4.toString(SerializerVersion.V1_21_4.toComponent(value)); } @Override protected void handleWrittenBookContents(final UserConnection connection, final CompoundTag tag) { final CompoundTag book = TagUtil.getNamespacedCompoundTag(tag, "written_book_content"); if (book == null) { return; } final ListTag pagesTag = book.getListTag("pages", CompoundTag.class); if (pagesTag == null) { return; } for (final CompoundTag compoundTag : pagesTag) { final Tag raw = compoundTag.get("raw"); compoundTag.putString("raw", toUglyJson(connection, raw)); final Tag filtered = compoundTag.get("filtered"); if (filtered != null) { compoundTag.putString("filtered", toUglyJson(connection, raw)); } } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_5to1_21_4/rewriter/EntityPacketRewriter1_21_5.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.rewriter; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.Protocol1_21_5To1_21_4; import com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.storage.HorseDataStorage; import com.viaversion.viaversion.api.data.entity.TrackedEntity; import com.viaversion.viaversion.api.minecraft.Holder; import com.viaversion.viaversion.api.minecraft.WolfVariant; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_5; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_2; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_5; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; 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.ClientboundPacket1_21_5; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ClientboundPackets1_21_5; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2; import java.util.UUID; public final class EntityPacketRewriter1_21_5 extends EntityRewriter { public EntityPacketRewriter1_21_5(final Protocol1_21_5To1_21_4 protocol) { super(protocol, protocol.mappedTypes().entityDataTypes().optionalComponentType, protocol.mappedTypes().entityDataTypes().booleanType); } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_21_5.ADD_ENTITY, wrapper -> { final int entityId = wrapper.passthrough(Types.VAR_INT); final UUID uuid = wrapper.read(Types.UUID); final int entityType = wrapper.read(Types.VAR_INT); if (entityType != EntityTypes1_21_5.EXPERIENCE_ORB.getId()) { wrapper.write(Types.UUID, uuid); wrapper.write(Types.VAR_INT, entityType); wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z wrapper.passthrough(Types.BYTE); // Pitch wrapper.passthrough(Types.BYTE); // Yaw wrapper.passthrough(Types.BYTE); // Head yaw wrapper.passthrough(Types.VAR_INT); // Data getSpawnTrackerWithDataHandler1_19().handle(wrapper); return; } tracker(wrapper.user()).addEntity(entityId, EntityTypes1_21_5.EXPERIENCE_ORB); // Back to its own special packet wrapper.setPacketType(ClientboundPackets1_21_2.ADD_EXPERIENCE_ORB); wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z wrapper.read(Types.BYTE); // Pitch wrapper.read(Types.BYTE); // Yaw wrapper.read(Types.BYTE); // Head yaw final int data = wrapper.read(Types.VAR_INT); wrapper.write(Types.SHORT, (short) data); final short velocityX = wrapper.read(Types.SHORT); final short velocityY = wrapper.read(Types.SHORT); final short velocityZ = wrapper.read(Types.SHORT); if (velocityX != 0 || velocityY != 0 || velocityZ != 0) { // Send movement separately final PacketWrapper motionPacket = wrapper.create(ClientboundPackets1_21_2.SET_ENTITY_MOTION); motionPacket.write(Types.VAR_INT, entityId); motionPacket.write(Types.SHORT, velocityX); motionPacket.write(Types.SHORT, velocityY); motionPacket.write(Types.SHORT, velocityZ); wrapper.send(Protocol1_21_5To1_21_4.class); motionPacket.send(Protocol1_21_5To1_21_4.class); wrapper.cancel(); } }); protocol.replaceClientbound(ClientboundPackets1_21_5.SET_PLAYER_TEAM, wrapper -> { wrapper.passthrough(Types.STRING); // Team Name final byte action = wrapper.passthrough(Types.BYTE); // Mode if (action == 0 || action == 2) { protocol.getComponentRewriter().passthroughAndProcess(wrapper); // Display name wrapper.passthrough(Types.BYTE); // Flags final int nametagVisibility = wrapper.read(Types.VAR_INT); final int collisionRule = wrapper.read(Types.VAR_INT); wrapper.write(Types.STRING, visibility(nametagVisibility)); wrapper.write(Types.STRING, collision(collisionRule)); wrapper.passthrough(Types.VAR_INT); // Color protocol.getComponentRewriter().passthroughAndProcess(wrapper); // Prefix protocol.getComponentRewriter().passthroughAndProcess(wrapper); // Suffix } }); } private String visibility(final int id) { return switch (id) { case 0 -> "always"; case 1 -> "never"; case 2 -> "hideForOtherTeams"; case 3 -> "hideForOwnTeam"; default -> "always"; }; } private String collision(final int id) { return switch (id) { case 0 -> "always"; case 1 -> "never"; case 2 -> "pushOtherTeams"; case 3 -> "pushOwnTeam"; default -> "always"; }; } @Override protected void registerRewrites() { final EntityDataTypes1_21_5 entityDataTypes = VersionedTypes.V1_21_5.entityDataTypes; final EntityDataTypes1_21_2 mappedEntityDataTypes = VersionedTypes.V1_21_4.entityDataTypes; dataTypeMapper() .removed(entityDataTypes.cowVariantType) .removed(entityDataTypes.wolfSoundVariantType) .removed(entityDataTypes.pigVariantType) .removed(entityDataTypes.chickenVariantType) .skip(entityDataTypes.wolfVariantType) .register(); filter().dataType(entityDataTypes.wolfVariantType).handler((event, data) -> { final int type = data.value(); final Holder variant = Holder.of(type); data.setTypeAndValue(mappedEntityDataTypes.wolfVariantType, variant); }); registerEntityDataTypeHandler1_20_3( mappedEntityDataTypes.itemType, mappedEntityDataTypes.blockStateType, mappedEntityDataTypes.optionalBlockStateType, mappedEntityDataTypes.particleType, mappedEntityDataTypes.particlesType, mappedEntityDataTypes.componentType, mappedEntityDataTypes.optionalComponentType ); filter().dataType(mappedEntityDataTypes.frogVariantType).handler((event, data) -> { final int value = data.value(); final String variantKey = protocol.getRegistryDataRewriter().getMappings("frog_variant").idToKey(value); final int newValue = (variantKey == null) ? 0 : switch (variantKey) { case "cold" -> 2; case "temperate" -> 0; case "warm" -> 1; default -> 0; }; data.setValue(newValue); }); filter().dataType(mappedEntityDataTypes.catVariantType).handler((event, data) -> { final int value = data.value(); final String variantKey = protocol.getRegistryDataRewriter().getMappings("cat_variant").idToKey(value); final int newValue = (variantKey == null) ? 1 : switch (variantKey) { case "all_black" -> 10; case "black" -> 1; case "british_shorthair" -> 4; case "calico" -> 5; case "jellie" -> 9; case "persian" -> 6; case "ragdoll" -> 7; case "red" -> 2; case "siamese" -> 3; case "tabby" -> 0; case "white" -> 8; default -> 1; }; data.setValue(newValue); }); filter().type(EntityTypes1_21_5.ABSTRACT_MINECART).addIndex(13); // Custom display filter().type(EntityTypes1_21_5.ABSTRACT_MINECART).index(11).handler((event, data) -> { final int state = (int) data.getValue(); if (state == 0) { event.cancel(); return; } final int mappedBlockState = protocol.getMappingData().getNewBlockStateId(state); data.setTypeAndValue(entityDataTypes.varIntType, mappedBlockState); event.createExtraData(new EntityData(13, mappedEntityDataTypes.booleanType, true)); }); filter().type(EntityTypes1_21_5.MOOSHROOM).index(17).handler(((event, data) -> { final int typeId = data.value(); final String typeName = typeId == 0 ? "red" : "brown"; data.setTypeAndValue(entityDataTypes.stringType, typeName); })); filter().type(EntityTypes1_21_5.ABSTRACT_HORSE).index(17).handler((event, data) -> { // Store data and set saddled flag if needed final TrackedEntity entity = event.trackedEntity(); 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(horseData, saddled)); }); filter().type(EntityTypes1_21_5.CHICKEN).cancel(17); // Chicken variant filter().type(EntityTypes1_21_5.COW).cancel(17); // Cow variant filter().type(EntityTypes1_21_5.PIG).cancel(18); // Pig variant filter().type(EntityTypes1_21_5.WOLF).cancel(23); // Sound variant filter().type(EntityTypes1_21_5.EXPERIENCE_ORB).cancel(8); // Value filter().type(EntityTypes1_21_5.DOLPHIN).addIndex(17); // Treasure pos filter().type(EntityTypes1_21_5.TURTLE).addIndex(17); // Home pos // Saddled filter().type(EntityTypes1_21_5.PIG).addIndex(17); filter().type(EntityTypes1_21_5.STRIDER).addIndex(19); } @Override public EntityType typeFromId(final int type) { return EntityTypes1_21_5.getTypeFromId(type); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_5to1_21_4/rewriter/RegistryDataRewriter1_21_5.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.RegistryEntry; import java.util.List; public final class RegistryDataRewriter1_21_5 extends BackwardsRegistryRewriter { public RegistryDataRewriter1_21_5(final BackwardsProtocol protocol) { super(protocol); final List newRegistries = List.of("pig_variant", "cow_variant", "frog_variant", "cat_variant", "chicken_variant", "test_environment", "test_instance", "wolf_sound_variant"); for (final String registry : newRegistries) { remove(registry); } } @Override public RegistryEntry[] handle(final UserConnection connection, final String key, final RegistryEntry[] entries) { final boolean trimPatternRegistry = key.equals("trim_pattern"); if (trimPatternRegistry || key.equals("trim_material")) { updateTrim(entries, trimPatternRegistry ? "template_item" : "ingredient"); } else if (key.equals("enchantment")) { updateEnchantment(entries); } else if (key.equals("wolf_variant")) { for (final RegistryEntry entry : entries) { if (entry.tag() == null) { continue; } final CompoundTag variant = (CompoundTag) entry.tag(); final CompoundTag assets = (CompoundTag) variant.remove("assets"); variant.put("wild_texture", assets.get("wild")); variant.put("tame_texture", assets.get("tame")); variant.put("angry_texture", assets.get("angry")); variant.put("biomes", new ListTag<>(StringTag.class)); } } return super.handle(connection, key, entries); } private void updateTrim(final RegistryEntry[] entries, final String itemKey) { for (final RegistryEntry entry : entries) { if (entry.tag() == null) { continue; } final CompoundTag tag = (CompoundTag) entry.tag(); tag.putString(itemKey, "stone"); // dummy ingredient } } private void updateEnchantment(final RegistryEntry[] entries) { for (final RegistryEntry entry : entries) { if (entry.tag() == null) { continue; } final CompoundTag enchantment = (CompoundTag) entry.tag(); final ListTag slots = enchantment.getListTag("slots", StringTag.class); if (slots != null) { slots.getValue().removeIf(tag -> tag.getValue().equals("saddle")); // Remove saddle slot } } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_5to1_21_4/storage/HashedItemConverterStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.storage; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.minecraft.codec.CodecContext; import com.viaversion.viaversion.api.minecraft.codec.CodecContext.RegistryAccess; import com.viaversion.viaversion.api.minecraft.codec.hash.Hasher; import com.viaversion.viaversion.api.protocol.Protocol; import com.viaversion.viaversion.codec.CodecRegistryContext; import com.viaversion.viaversion.codec.hash.HashFunction; import com.viaversion.viaversion.codec.hash.HashOps; public class HashedItemConverterStorage implements StorableObject { private final Hasher hasher; public HashedItemConverterStorage(final Protocol protocol) { final RegistryAccess registryAccess = RegistryAccess.of(protocol); final CodecContext context = new CodecRegistryContext(protocol, registryAccess, false); this.hasher = new HashOps(context, HashFunction.CRC32C); } public Hasher hasher() { return hasher; // reusable } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_5to1_21_4/storage/HorseDataStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_5to1_21_4.storage; public record HorseDataStorage(byte data, boolean saddled) { } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/Protocol1_21_6To1_21_5.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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.ViaBackwards; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter; 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.rewriter.RegistryDataRewriter1_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.Holder; 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; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_21_5; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.api.type.types.version.VersionedTypesHolder; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.data.item.ItemHasherBase; import com.viaversion.viaversion.protocols.v1_19_3to1_19_4.rewriter.CommandRewriter1_19_4; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundConfigurationPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundConfigurationPackets1_21; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ClientboundPacket1_21_5; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ClientboundPackets1_21_5; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ServerboundPacket1_21_5; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ServerboundPackets1_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.Protocol1_21_5To1_21_6; 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.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.RecipeDisplayRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.block.BlockRewriter1_21_5; import com.viaversion.viaversion.util.Key; import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap; public final class Protocol1_21_6To1_21_5 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.21.6", "1.21.5", Protocol1_21_5To1_21_6.class); private final EntityPacketRewriter1_21_6 entityRewriter = new EntityPacketRewriter1_21_6(this); private final BlockItemPacketRewriter1_21_6 itemRewriter = new BlockItemPacketRewriter1_21_6(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final NBTComponentRewriter translatableRewriter = new ComponentRewriter1_21_6(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final RecipeDisplayRewriter recipeRewriter = new RecipeDisplayRewriter1_21_5<>(this); private final BackwardsRegistryRewriter registryDataRewriter = new RegistryDataRewriter1_21_6(this); private final BlockRewriter blockRewriter = new BlockRewriter1_21_5<>(this, ChunkType1_21_5::new); public Protocol1_21_6To1_21_5() { super(ClientboundPacket1_21_6.class, ClientboundPacket1_21_5.class, ServerboundPacket1_21_6.class, ServerboundPacket1_21_5.class); } @Override protected void registerPackets() { super.registerPackets(); appendClientbound(ClientboundPackets1_21_6.SOUND, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Source fixSoundSource(wrapper); }); appendClientbound(ClientboundPackets1_21_6.SOUND_ENTITY, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Source fixSoundSource(wrapper); }); appendClientbound(ClientboundPackets1_21_6.STOP_SOUND, wrapper -> { final byte flags = wrapper.get(Types.BYTE, 0); if ((flags & 0x01) != 0) { fixSoundSource(wrapper); } }); final CommandRewriter1_19_4 commandRewriter = new CommandRewriter1_19_4<>(this) { @Override public void handleArgument(final PacketWrapper wrapper, final String argumentType) { if (argumentType.equals("minecraft:hex_color") || argumentType.equals("minecraft:dialog")) { wrapper.write(Types.VAR_INT, 0); // Word } else { super.handleArgument(wrapper, argumentType); } } }; replaceClientbound(ClientboundPackets1_21_6.COMMANDS, commandRewriter::handle1_19); 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); }); registerServerbound(ServerboundPackets1_21_5.CHAT_COMMAND, this::handleClickEvents); registerServerbound(ServerboundPackets1_21_5.CHAT_COMMAND_SIGNED, this::handleClickEvents); // 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(); if (!ViaBackwards.getConfig().dialogsViaChests()) { return; } final RegistryAndTags registryAndTags = wrapper.user().get(RegistryAndTags.class); final ServerLinks serverLinks = wrapper.user().get(ServerLinks.class); final Holder holder = wrapper.passthrough(Types.TRUSTED_COMPOUND_TAG_HOLDER); final CompoundTag tag = holder.isDirect() ? holder.value() : registryAndTags.fromRegistry(holder.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(); if (!ViaBackwards.getConfig().dialogsViaChests()) { return; } final RegistryAndTags registryAndTags = wrapper.user().get(RegistryAndTags.class); final ServerLinks serverLinks = wrapper.user().get(ServerLinks.class); final CompoundTag tag = wrapper.read(Types.TRUSTED_COMPOUND_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); // 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); } private void fixSoundSource(final PacketWrapper wrapper) { final int source = wrapper.get(Types.VAR_INT, 0); if (source == 10) { // New ui source, map to master wrapper.set(Types.VAR_INT, 0, 0); } } 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 clearDialog(final PacketWrapper wrapper) { wrapper.cancel(); if (!ViaBackwards.getConfig().dialogsViaChests()) { return; } final DialogViewProvider provider = Via.getManager().getProviders().get(DialogViewProvider.class); provider.closeDialog(wrapper.user()); } 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.TRUSTED_TAG); final String url = wrapper.passthrough(Types.STRING); serverLinks.storeLink(tag, url); } } wrapper.user().put(serverLinks); } private void handleClickEvents(final PacketWrapper wrapper) { final String command = wrapper.passthrough(Types.STRING); final ClickEvents clickEvents = wrapper.user().get(ClickEvents.class); if (clickEvents.handleChatCommand(wrapper.user(), command)) { wrapper.cancel(); } } @Override public void init(final UserConnection user) { addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_21_6.PLAYER)); addItemHasher(user, new ItemHasherBase(this, user)); user.put(new RegistryAndTags()); user.put(new ClickEvents()); } @Override public void register(final ViaProviders providers) { providers.register(DialogViewProvider.class, new ChestDialogViewProvider(this)); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_21_6 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_21_6 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public BackwardsRegistryRewriter getRegistryDataRewriter() { return registryDataRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public NBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } @Override public RecipeDisplayRewriter getRecipeRewriter() { return recipeRewriter; } @Override public VersionedTypesHolder types() { return VersionedTypes.V1_21_6; } @Override public VersionedTypesHolder mappedTypes() { return VersionedTypes.V1_21_5; } @Override protected PacketTypesProvider createPacketTypesProvider() { return new SimplePacketTypesProvider<>( packetTypeMap(unmappedClientboundPacketType, ClientboundPackets1_21_6.class, ClientboundConfigurationPackets1_21_6.class), packetTypeMap(mappedClientboundPacketType, ClientboundPackets1_21_5.class, ClientboundConfigurationPackets1_21.class), packetTypeMap(mappedServerboundPacketType, ServerboundPackets1_21_6.class, ServerboundConfigurationPackets1_21_6.class), packetTypeMap(unmappedServerboundPacketType, ServerboundPackets1_21_5.class, ServerboundConfigurationPackets1_20_5.class) ); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/data/Button.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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.copy(); 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 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; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/data/Dialog.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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 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 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 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 : new Button(this, actionTag); } 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 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 / singleton inlined, a list of registry entries / singleton entry or a tag. ListTag 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); } ListTag registryDialogsTag = tag.getListTag("dialogs", StringTag.class); StringTag registryDialogTag = tag.getStringTag("dialogs"); if (registryDialogTag != null) { registryDialogsTag = new ListTag<>(StringTag.class); registryDialogsTag.add(registryDialogTag); } if (registryDialogsTag != null) { dialogsTag = new ListTag<>(CompoundTag.class); for (final StringTag nameTag : registryDialogsTag) { final String key = nameTag.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 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 widgets() { return widgets; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/data/Template.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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 segments, List variables) { private static final int MAX_LENGTH = 2000000; public static Template fromString(final String string) { final Builder segmentsBuilder = ImmutableList.builder(); final Builder 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 map) { return substitute(variables().stream().map(string -> map.getOrDefault(string, "")).toList()); } private String substitute(final List 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(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/data/input/BooleanInput.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/data/input/Input.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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(); } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/data/input/NumberRangeInput.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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.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, translate(valueAsString())); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/data/input/SingleOptionInput.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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 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)); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/data/input/TextInput.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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 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; } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/data/widget/ItemWidget.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/data/widget/TextWidget.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/data/widget/Widget.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.data.widget; public interface Widget { } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/provider/ChestDialogViewProvider.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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.ViaBackwards; import com.viaversion.viabackwards.api.DialogStyleConfig; 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.minecraft.item.data.TooltipDisplay; 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.libs.fastutil.ints.IntSortedSets; 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.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 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()) { // Flush collected texts before adding the non-text widget dialog.widgets().add(dialog.widgets().indexOf(widget), new MultiTextWidget(texts.toArray(Tag[]::new))); texts.clear(); } } // Flush remaining texts if any if (!texts.isEmpty()) { dialog.widgets().add(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.TRUSTED_TAG, handleTag(connection, 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); if (storage == null) { return; } 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 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 nor opened another one. final ChestDialogStorage currentStorage = connection.get(ChestDialogStorage.class); if (currentStorage == storage && 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() { final DialogStyleConfig config = ViaBackwards.getConfig().dialogStyleConfig(); return createItem( "minecraft:arrow", translate(config.pageNavigationTitle()), config.pageNavigationNext(), config.pageNavigationPrevious() ); } 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 = translate(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.TOOLTIP_DISPLAY, new TooltipDisplay(true, IntSortedSets.EMPTY_SET)); } // 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 List lines = new ArrayList<>(); for (final Tag label : multiTextWidget.labels()) { final Tag[] split = ChatUtil.split(fixStyle(label), "\n"); for (final Tag line : split) { lines.add(handleTag(connection, line)); } } final Tag[] lore = new Tag[lines.size() - 1]; for (int i = 1; i < lines.size(); i++) { lore[i - 1] = lines.get(i); } return createItem("minecraft:paper", lines.get(0), lore); } protected Item getBooleanInput(final UserConnection connection, final BooleanInput booleanInput) { final DialogStyleConfig config = ViaBackwards.getConfig().dialogStyleConfig(); 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()), translate(config.toggleValue()) ); } 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] = translate(config.toggleValue()); 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 DialogStyleConfig config = ViaBackwards.getConfig().dialogStyleConfig(); final Tag label = handleTag(connection, numberRangeInput.displayName()); return createItem( "minecraft:clock", label, String.format(config.increaseValue(), numberRangeInput.step()), String.format(config.decreaseValue(), numberRangeInput.step()), String.format(config.valueRange(), numberRangeInput.start(), numberRangeInput.end()) ); } 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 DialogStyleConfig config = ViaBackwards.getConfig().dialogStyleConfig(); final Tag currentValue = translate(String.format(config.currentValue(), 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, translate(config.editValue())); } } protected void clickTextInput(final UserConnection connection, final TextInput textInput) { final ChestDialogStorage storage = connection.get(ChestDialogStorage.class); openAnvilView(connection, storage, translate("§7Edit text"), textInput.value(), textInput); } protected Item getSingleOptionInput(final UserConnection connection, final SingleOptionInput singleOptionInput) { final DialogStyleConfig config = ViaBackwards.getConfig().dialogStyleConfig(); 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), config.nextOption(), config.previousOption() ); } 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, translate("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, translate("Copy to clipboard"), value, null); } } ClickEvents.handleClickEvent(connection, clickEvent); // Handle show_dialog and custom } protected Item createTextInputItem(final String value) { final DialogStyleConfig config = ViaBackwards.getConfig().dialogStyleConfig(); return createItem("minecraft:paper", translate(value), config.setText()); } protected Item createTextCopyItem(final String value) { final DialogStyleConfig config = ViaBackwards.getConfig().dialogStyleConfig(); return createItem("minecraft:paper", translate(value), config.close()); } protected 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.TRUSTED_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 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 */ protected @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 lore = new ArrayList<>(); for (final String s : description) { lore.add(translate(s)); } data.set(StructuredDataKey.LORE, lore.toArray(new Tag[0])); } return new StructuredItem(id, 1, data); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/provider/DialogViewProvider.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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. *

* See {@link Dialog} for the structure of a dialog. *

* 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); } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/rewriter/BlockItemPacketRewriter1_21_6.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.viabackwards.api.rewriters.BackwardsStructuredItemRewriter; import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.Protocol1_21_6To1_21_5; 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.data.AttributeModifiers1_21; import com.viaversion.viaversion.api.minecraft.item.data.Equippable; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ServerboundPacket1_21_5; import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundPacket1_21_6; import static com.viaversion.viaversion.protocols.v1_21_5to1_21_6.rewriter.BlockItemPacketRewriter1_21_6.downgradeItemData; import static com.viaversion.viaversion.protocols.v1_21_5to1_21_6.rewriter.BlockItemPacketRewriter1_21_6.upgradeItemData; public final class BlockItemPacketRewriter1_21_6 extends BackwardsStructuredItemRewriter { public BlockItemPacketRewriter1_21_6(final Protocol1_21_6To1_21_5 protocol) { super(protocol); } @Override protected void handleItemDataComponentsToClient(final UserConnection connection, final Item item, final StructuredDataContainer container) { downgradeItemData(item); super.handleItemDataComponentsToClient(connection, item, container); } @Override protected void handleItemDataComponentsToServer(final UserConnection connection, final Item item, final StructuredDataContainer container) { upgradeItemData(item); super.handleItemDataComponentsToServer(connection, item, container); } @Override protected void backupInconvertibleData(final UserConnection connection, final Item item, final StructuredDataContainer dataContainer, final CompoundTag backupTag) { super.backupInconvertibleData(connection, item, dataContainer, backupTag); final AttributeModifiers1_21 attributeModifiers = dataContainer.get(StructuredDataKey.ATTRIBUTE_MODIFIERS1_21_6); if (attributeModifiers != null) { final ListTag modifiersBackup = new ListTag<>(CompoundTag.class); boolean needsBackup = false; for (final AttributeModifiers1_21.AttributeModifier modifier : attributeModifiers.modifiers()) { if (modifier.display().id() != 0) { needsBackup = true; } final CompoundTag modifierBackup = new CompoundTag(); modifiersBackup.add(modifierBackup); modifierBackup.putInt("id", modifier.display().id()); if (modifier.display() instanceof AttributeModifiers1_21.OverrideText overrideText) { modifierBackup.put("text", overrideText.component()); } } if (needsBackup) { backupTag.put("attribute_modifiers_displays", modifiersBackup); } } final Equippable equippable = dataContainer.get(StructuredDataKey.EQUIPPABLE1_21_6); if (equippable != null && equippable.canBeSheared()) { final CompoundTag equippableTag = new CompoundTag(); equippableTag.putBoolean("can_be_sheared", true); saveSoundEventHolder(equippableTag, equippable.shearingSound()); backupTag.put("equippable", equippableTag); } } @Override protected void restoreBackupData(final Item item, final StructuredDataContainer container, final CompoundTag customData) { super.restoreBackupData(item, container, customData); if (!(customData.remove(nbtTagName("backup")) instanceof final CompoundTag backupTag)) { return; } final ListTag attributeModifiersDisplays = backupTag.getListTag("attribute_modifiers_displays", CompoundTag.class); if (attributeModifiersDisplays != null) { container.replace(StructuredDataKey.ATTRIBUTE_MODIFIERS1_21_5, StructuredDataKey.ATTRIBUTE_MODIFIERS1_21_6, modifiers -> { final AttributeModifiers1_21.AttributeModifier[] updatedModifiers = new AttributeModifiers1_21.AttributeModifier[modifiers.modifiers().length]; for (int i = 0; i < modifiers.modifiers().length; i++) { final CompoundTag modifierBackup = attributeModifiersDisplays.get(i); final int id = modifierBackup.getInt("id"); final AttributeModifiers1_21.Display display = id == 2 ? new AttributeModifiers1_21.OverrideText(modifierBackup.get("text")) : new AttributeModifiers1_21.Display(id); final AttributeModifiers1_21.AttributeModifier modifier = modifiers.modifiers()[i]; updatedModifiers[i] = new AttributeModifiers1_21.AttributeModifier(modifier.attribute(), modifier.modifier(), modifier.slotType(), display); } return new AttributeModifiers1_21(updatedModifiers); }); } final CompoundTag equippableTag = backupTag.getCompoundTag("equippable"); if (equippableTag != null) { container.replace(StructuredDataKey.EQUIPPABLE1_21_5, StructuredDataKey.EQUIPPABLE1_21_6, equippable -> new Equippable( equippable.equipmentSlot(), equippable.soundEvent(), equippable.model(), equippable.cameraOverlay(), equippable.allowedEntities(), equippable.dispensable(), equippable.swappable(), equippable.damageOnHurt(), equippable.equipOnInteract(), equippableTag.getBoolean("can_be_sheared"), restoreSoundEventHolder(equippableTag) )); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/rewriter/ComponentRewriter1_21_6.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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.ClickEvents; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundPacket1_21_6; public final class ComponentRewriter1_21_6 extends NBTComponentRewriter { public ComponentRewriter1_21_6(final BackwardsProtocol protocol) { super(protocol); } @Override protected void handleClickEvent(final UserConnection connection, final CompoundTag clickEventTag) { final String action = clickEventTag.getString("action"); // 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(clickEventTag.copy()); clickEventTag.putString("action", "run_command"); clickEventTag.putString("command", command); clickEventTag.remove("dialog"); } else if ("custom".equals(action)) { final ClickEvents clickEvents = connection.get(ClickEvents.class); final String command = clickEvents.storeClickEvent(clickEventTag.copy()); clickEventTag.putString("action", "run_command"); clickEventTag.putString("command", command); clickEventTag.remove("id"); clickEventTag.remove("payload"); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/rewriter/EntityPacketRewriter1_21_6.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.rewriter; 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.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.protocol.packet.PacketWrapper; 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_4to1_21_5.packet.ServerboundPackets1_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; public final class EntityPacketRewriter1_21_6 extends EntityRewriter { public EntityPacketRewriter1_21_6(final Protocol1_21_6To1_21_5 protocol) { super(protocol, VersionedTypes.V1_21_6.entityDataTypes.optionalComponentType, VersionedTypes.V1_21_6.entityDataTypes.booleanType); } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_21_6.ADD_ENTITY, wrapper -> { final int entityId = wrapper.passthrough(Types.VAR_INT); wrapper.passthrough(Types.UUID); // Entity UUID final int entityType = wrapper.passthrough(Types.VAR_INT); wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z wrapper.passthrough(Types.BYTE); // Pitch wrapper.passthrough(Types.BYTE); // Yaw wrapper.passthrough(Types.BYTE); // Head yaw wrapper.passthrough(Types.VAR_INT); // Data final short velocityX = wrapper.passthrough(Types.SHORT); final short velocityY = wrapper.passthrough(Types.SHORT); final short velocityZ = wrapper.passthrough(Types.SHORT); getSpawnTrackerWithDataHandler1_19().handle(wrapper); if (velocityX != 0 || velocityY != 0 || velocityZ != 0) { if (!typeFromId(entityType).isOrHasParent(EntityTypes1_21_6.LIVING_ENTITY)) { // Send movement separately final PacketWrapper motionPacket = wrapper.create(ClientboundPackets1_21_5.SET_ENTITY_MOTION); motionPacket.write(Types.VAR_INT, entityId); motionPacket.write(Types.SHORT, velocityX); motionPacket.write(Types.SHORT, velocityY); motionPacket.write(Types.SHORT, velocityZ); wrapper.send(Protocol1_21_6To1_21_5.class); motionPacket.send(Protocol1_21_6To1_21_5.class); wrapper.cancel(); } } }); protocol.registerServerbound(ServerboundPackets1_21_5.PLAYER_COMMAND, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Entity ID // press_shift_key and release_shift_key gone. The server uses (the already sent) player input instead final int action = wrapper.read(Types.VAR_INT); if (action < 2) { wrapper.cancel(); } wrapper.write(Types.VAR_INT, action - 2); }); } @Override protected void registerRewrites() { final EntityDataTypes1_21_5 entityDataTypes = VersionedTypes.V1_21_5.entityDataTypes; dataTypeMapper().register(); registerEntityDataTypeHandler1_20_3( entityDataTypes.itemType, entityDataTypes.blockStateType, entityDataTypes.optionalBlockStateType, entityDataTypes.particleType, entityDataTypes.particlesType, entityDataTypes.componentType, entityDataTypes.optionalComponentType ); filter().type(EntityTypes1_21_6.HANGING_ENTITY).removeIndex(8); // Direction filter().type(EntityTypes1_21_6.HAPPY_GHAST).cancel(17); // Leash holder filter().type(EntityTypes1_21_6.HAPPY_GHAST).cancel(18); // Stays still } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); mapEntityTypeWithData(EntityTypes1_21_6.HAPPY_GHAST, EntityTypes1_21_6.GHAST).tagName(); } @Override public EntityType typeFromId(final int type) { return EntityTypes1_21_6.getTypeFromId(type); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/rewriter/RegistryDataRewriter1_21_6.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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.BackwardsRegistryRewriter; 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.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 RegistryDataRewriter1_21_6 extends BackwardsRegistryRewriter { public RegistryDataRewriter1_21_6(final BackwardsProtocol protocol) { super(protocol); remove("dialog"); // Tracked and now removed } @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 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); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/storage/ChestDialogStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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.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[]{ translate(""), 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 } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/storage/ClickEvents.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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.ViaBackwards; 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 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)) { if (!ViaBackwards.getConfig().dialogsViaChests()) { return; } 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); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/storage/RegistryAndTags.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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 dialogs; private Object2ObjectMap dialogTags; public void storeRegistry(final KeyMappings dialogMappings, final Object2ObjectMap 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; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/storage/ServerLinks.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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 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 links() { return links; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_6to1_21_5/task/ChestDialogViewTask.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.task; import com.viaversion.viabackwards.protocol.v1_21_6to1_21_5.storage.ChestDialogStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.connection.StorableObjectTask; public final class ChestDialogViewTask extends StorableObjectTask { public ChestDialogViewTask() { super(ChestDialogStorage.class); } @Override public void run(final UserConnection userConnection, final ChestDialogStorage storableObject) { storableObject.tick(userConnection); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_7to1_21_6/Protocol1_21_7To1_21_6.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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.BackwardsRegistryRewriter; import com.viaversion.viabackwards.api.rewriters.text.NBTComponentRewriter; 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.chunk.ChunkType1_21_5; 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_4to1_21_5.rewriter.RecipeDisplayRewriter1_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.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.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.RecipeDisplayRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap; public final class Protocol1_21_7To1_21_6 extends BackwardsProtocol { 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 particleRewriter = new ParticleRewriter<>(this); private final NBTComponentRewriter translatableRewriter = new NBTComponentRewriter<>(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BackwardsRegistryRewriter registryDataRewriter = new BackwardsRegistryRewriter(this); private final RecipeDisplayRewriter recipeRewriter = new RecipeDisplayRewriter1_21_5<>(this); private final BlockRewriter blockRewriter = BlockRewriter.for1_20_2(this, ChunkType1_21_5::new); public Protocol1_21_7To1_21_6() { super(ClientboundPacket1_21_6.class, ClientboundPacket1_21_6.class, ServerboundPacket1_21_6.class, ServerboundPacket1_21_6.class); } @Override public void init(final UserConnection connection) { addEntityTracker(connection, new EntityTrackerBase(connection, EntityTypes1_21_6.PLAYER)); addItemHasher(connection, new ItemHasherBase(this, connection)); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public Types1_20_5 types() { return VersionedTypes.V1_21_6; } @Override public Types1_20_5 mappedTypes() { return VersionedTypes.V1_21_6; } @Override public NBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public EntityPacketRewriter1_21_7 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_21_7 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public BackwardsRegistryRewriter getRegistryDataRewriter() { return registryDataRewriter; } @Override public RecipeDisplayRewriter getRecipeRewriter() { return recipeRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } @Override protected PacketTypesProvider 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) ); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_7to1_21_6/rewriter/BlockItemPacketRewriter1_21_7.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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.protocols.v1_21_5to1_21_6.packet.ClientboundPacket1_21_6; import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPacket1_21_6; public final class BlockItemPacketRewriter1_21_7 extends BackwardsStructuredItemRewriter { public BlockItemPacketRewriter1_21_7(final Protocol1_21_7To1_21_6 protocol) { super(protocol); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_7to1_21_6/rewriter/EntityPacketRewriter1_21_7.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_7to1_21_6.rewriter; 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_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.ClientboundPacket1_21_6; import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundPackets1_21_6; public final class EntityPacketRewriter1_21_7 extends EntityRewriter { 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 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); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_9to1_21_7/Protocol1_21_9To1_21_7.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_9to1_21_7; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter; import com.viaversion.viabackwards.api.rewriters.SoundRewriter; import com.viaversion.viabackwards.api.rewriters.text.NBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.rewriter.BlockItemPacketRewriter1_21_9; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.rewriter.ComponentRewriter1_21_9; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.rewriter.EntityPacketRewriter1_21_9; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.rewriter.ParticleRewriter1_21_9; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.rewriter.RegistryDataRewriter1_21_9; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.storage.DimensionScaleStorage; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.storage.PlayerRotationStorage; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.tracker.EntityTracker1_21_9; 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_9; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_5; 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; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_21_5; import com.viaversion.viaversion.api.type.types.version.Types1_20_5; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.api.type.types.version.VersionedTypesHolder; import com.viaversion.viaversion.data.item.ItemHasherBase; 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.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_7to1_21_9.Protocol1_21_7To1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundConfigurationPackets1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundPacket1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundPackets1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundConfigurationPackets1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundPacket1_21_9; import com.viaversion.viaversion.protocols.v1_21to1_21_2.storage.BundleStateTracker; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.RecipeDisplayRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.block.BlockRewriter1_21_5; import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap; public final class Protocol1_21_9To1_21_7 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.21.9", "1.21.7", Protocol1_21_7To1_21_9.class); private final EntityPacketRewriter1_21_9 entityRewriter = new EntityPacketRewriter1_21_9(this); private final BlockItemPacketRewriter1_21_9 itemRewriter = new BlockItemPacketRewriter1_21_9(this); private final ParticleRewriter particleRewriter = new ParticleRewriter1_21_9(this); private final NBTComponentRewriter translatableRewriter = new ComponentRewriter1_21_9(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final RecipeDisplayRewriter recipeRewriter = new RecipeDisplayRewriter1_21_5<>(this); private final BackwardsRegistryRewriter registryDataRewriter = new RegistryDataRewriter1_21_9(this); private final BlockRewriter blockRewriter = new BlockRewriter1_21_5<>(this, ChunkType1_21_5::new); public Protocol1_21_9To1_21_7() { super(ClientboundPacket1_21_9.class, ClientboundPacket1_21_6.class, ServerboundPacket1_21_9.class, ServerboundPacket1_21_6.class); } @Override protected void registerPackets() { super.registerPackets(); final SoundRewriter soundRewriter = new SoundRewriter<>(this); replaceClientbound(ClientboundPackets1_21_9.EXPLODE, wrapper -> { wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z wrapper.read(Types.FLOAT); // Radius wrapper.read(Types.INT); // Affected blocks if (wrapper.passthrough(Types.BOOLEAN)) { wrapper.passthrough(Types.DOUBLE); // Knockback X wrapper.passthrough(Types.DOUBLE); // Knockback Y wrapper.passthrough(Types.DOUBLE); // Knockback Z } particleRewriter.passthroughParticle(wrapper); // Explosion particle soundRewriter.soundHolderHandler().handle(wrapper); final int blockParticles = wrapper.read(Types.VAR_INT); for (int i = 0; i < blockParticles; i++) { wrapper.read(particleRewriter.particleType()); wrapper.read(Types.FLOAT); // Scaling wrapper.read(Types.FLOAT); // Speed wrapper.read(Types.VAR_INT); // Weight } }); registerClientbound(ClientboundConfigurationPackets1_21_9.CODE_OF_CONDUCT, ClientboundConfigurationPackets1_21_6.SHOW_DIALOG, wrapper -> { // TODO Remove once we implement dialogs during the configuration state in 1.21.6->1.21.5 final boolean supportsDialogs = wrapper.user().getProtocolInfo().protocolVersion().newerThan(ProtocolVersion.v1_21_5); if (!ViaBackwards.getConfig().codeOfConductAsDialog() || !supportsDialogs) { wrapper.cancel(); final PacketWrapper acceptPacket = wrapper.create(ServerboundConfigurationPackets1_21_9.ACCEPT_CODE_OF_CONDUCT); acceptPacket.sendToServer(Protocol1_21_9To1_21_7.class); return; } final String codeOfConduct = wrapper.read(Types.STRING); final CompoundTag tag = new CompoundTag(); tag.putString("type", "minecraft:confirmation"); tag.putString("title", translatableRewriter.mappedTranslationKey("multiplayer.codeOfConduct.title")); final CompoundTag body = new CompoundTag(); body.putString("type", "minecraft:plain_message"); body.putString("contents", codeOfConduct); tag.put("body", body); final CompoundTag yes = new CompoundTag(); final CompoundTag yesLabel = new CompoundTag(); yesLabel.putString("translate", "gui.acknowledge"); yes.put("label", yesLabel); final CompoundTag acceptAction = new CompoundTag(); acceptAction.putString("type", "minecraft:custom"); acceptAction.putString("id", "viabackwards:ack_code_of_conduct"); yes.put("action", acceptAction); tag.put("yes", yes); final CompoundTag no = new CompoundTag(); final CompoundTag noLabel = new CompoundTag(); noLabel.putString("translate", "menu.disconnect"); no.put("label", noLabel); final CompoundTag disconnectAction = new CompoundTag(); disconnectAction.putString("type", "minecraft:custom"); disconnectAction.putString("id", "viabackwards:disconnect"); no.put("action", disconnectAction); tag.put("no", no); wrapper.write(Types.TRUSTED_TAG, tag); }); registerServerbound(ServerboundConfigurationPackets1_21_6.CUSTOM_CLICK_ACTION, wrapper -> { final String id = wrapper.passthrough(Types.STRING); if ("viabackwards:ack_code_of_conduct".equals(id)) { wrapper.cancel(); final PacketWrapper acceptPacket = wrapper.create(ServerboundConfigurationPackets1_21_9.ACCEPT_CODE_OF_CONDUCT); acceptPacket.sendToServer(Protocol1_21_9To1_21_7.class); } else if ("viabackwards:disconnect".equals(id)) { wrapper.cancel(); wrapper.user().disconnect(translatableRewriter.mappedTranslationKey("multiplayer.disconnect.code_of_conduct")); } }); registerServerbound(ServerboundPackets1_21_6.DEBUG_SAMPLE_SUBSCRIPTION, wrapper -> { final int sampleType = wrapper.read(Types.VAR_INT); if (sampleType == 0) { // TICK_TIME wrapper.write(Types.VAR_INT, 1); // Subscription count wrapper.write(Types.VAR_INT, 0); // Subscription registry id (DEDICATED_SERVER_TICK_TIME) } }); registerClientbound(ClientboundPackets1_21_9.BUNDLE_DELIMITER, wrapper -> wrapper.user().get(BundleStateTracker.class).toggleBundling()); cancelClientbound(ClientboundPackets1_21_9.DEBUG_BLOCK_VALUE); cancelClientbound(ClientboundPackets1_21_9.DEBUG_CHUNK_VALUE); cancelClientbound(ClientboundPackets1_21_9.DEBUG_ENTITY_VALUE); cancelClientbound(ClientboundPackets1_21_9.DEBUG_EVENT); cancelClientbound(ClientboundPackets1_21_9.GAME_EVENT_TEST_HIGHLIGHT_POS); } @Override public void init(final UserConnection connection) { addEntityTracker(connection, new EntityTracker1_21_9(connection, EntityTypes1_21_9.PLAYER)); addItemHasher(connection, new ItemHasherBase(this, connection)); connection.put(new PlayerRotationStorage()); connection.put(new DimensionScaleStorage()); connection.put(new BundleStateTracker()); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_21_9 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_21_9 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public BackwardsRegistryRewriter getRegistryDataRewriter() { return registryDataRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public NBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } @Override public RecipeDisplayRewriter getRecipeRewriter() { return recipeRewriter; } @Override public VersionedTypesHolder types() { return VersionedTypes.V1_21_9; } @Override public Types1_20_5 mappedTypes() { return VersionedTypes.V1_21_6; } @Override protected PacketTypesProvider createPacketTypesProvider() { return new SimplePacketTypesProvider<>( packetTypeMap(unmappedClientboundPacketType, ClientboundPackets1_21_9.class, ClientboundConfigurationPackets1_21_9.class), packetTypeMap(mappedClientboundPacketType, ClientboundPackets1_21_6.class, ClientboundConfigurationPackets1_21_6.class), packetTypeMap(mappedServerboundPacketType, ServerboundPackets1_21_6.class, ServerboundConfigurationPackets1_21_9.class), packetTypeMap(unmappedServerboundPacketType, ServerboundPackets1_21_6.class, ServerboundConfigurationPackets1_21_6.class) ); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_9to1_21_7/rewriter/BlockItemPacketRewriter1_21_9.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.rewriters.BackwardsStructuredItemRewriter; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.Protocol1_21_9To1_21_7; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.storage.DimensionScaleStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.minecraft.ResolvableProfile; 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.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPacket1_21_6; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundPacket1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundPackets1_21_9; import static com.viaversion.viaversion.protocols.v1_21_7to1_21_9.rewriter.BlockItemPacketRewriter1_21_9.downgradeData; import static com.viaversion.viaversion.protocols.v1_21_7to1_21_9.rewriter.BlockItemPacketRewriter1_21_9.upgradeData; public final class BlockItemPacketRewriter1_21_9 extends BackwardsStructuredItemRewriter { public BlockItemPacketRewriter1_21_9(final Protocol1_21_9To1_21_7 protocol) { super(protocol); } @Override public void registerPackets() { protocol.registerClientbound(ClientboundPackets1_21_9.INITIALIZE_BORDER, this::updateBorderCenter); protocol.registerClientbound(ClientboundPackets1_21_9.SET_BORDER_CENTER, this::updateBorderCenter); } @Override protected void handleItemDataComponentsToClient(final UserConnection connection, final Item item, final StructuredDataContainer container) { super.handleItemDataComponentsToClient(connection, item, container); downgradeData(item, container); } @Override protected void handleItemDataComponentsToServer(final UserConnection connection, final Item item, final StructuredDataContainer container) { super.handleItemDataComponentsToServer(connection, item, container); upgradeData(item, container); } @Override protected void backupInconvertibleData(final UserConnection connection, final Item item, final StructuredDataContainer dataContainer, final CompoundTag backupTag) { super.backupInconvertibleData(connection, item, dataContainer, backupTag); final ResolvableProfile profile = dataContainer.get(StructuredDataKey.PROFILE1_21_9); if (profile != null) { final CompoundTag profileTag = new CompoundTag(); if (profile.bodyTexture() != null) { profileTag.putString("body_texture", profile.bodyTexture()); } if (profile.capeTexture() != null) { profileTag.putString("cape_texture", profile.capeTexture()); } if (profile.elytraTexture() != null) { profileTag.putString("elytra_texture", profile.elytraTexture()); } if (profile.modelType() != null) { profileTag.putBoolean("model", profile.modelType() == 0); } if (!profileTag.isEmpty()) { backupTag.put("profile", profileTag); } } } @Override protected void restoreBackupData(final Item item, final StructuredDataContainer container, final CompoundTag customData) { super.restoreBackupData(item, container, customData); if (!(customData.remove(nbtTagName("backup")) instanceof final CompoundTag backupTag)) { return; } final CompoundTag profileTag = backupTag.getCompoundTag("profile"); if (profileTag != null) { container.replace(StructuredDataKey.PROFILE1_20_5, StructuredDataKey.PROFILE1_21_9, profile -> new ResolvableProfile( profile, profileTag.getString("body_texture"), profileTag.getString("cape_texture"), profileTag.getString("elytra_texture"), profileTag.contains("model") ? (profileTag.getBoolean("model") ? 0 : 1) : null )); } } private void updateBorderCenter(final PacketWrapper wrapper) { double centerX = wrapper.read(Types.DOUBLE); double centerZ = wrapper.read(Types.DOUBLE); final EntityTracker tracker = protocol.getEntityRewriter().tracker(wrapper.user()); if (tracker.currentDimensionId() != -1) { final double scale = wrapper.user().get(DimensionScaleStorage.class).getScale(tracker.currentDimensionId()); centerX *= scale; centerZ *= scale; } wrapper.write(Types.DOUBLE, centerX); wrapper.write(Types.DOUBLE, centerZ); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_9to1_21_7/rewriter/ComponentRewriter1_21_9.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.text.NBTComponentRewriter; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundPacket1_21_9; public final class ComponentRewriter1_21_9 extends NBTComponentRewriter { public ComponentRewriter1_21_9(final BackwardsProtocol protocol) { super(protocol); } @Override protected void processCompoundTag(final UserConnection connection, final CompoundTag tag) { super.processCompoundTag(connection, tag); // Throw out the new object type and its properties final String type = tag.getString("type"); // Try to use the 26.1+ fallback value if present, otherwise just show an empty string Tag fallback = tag.get("fallback"); if (fallback == null) { fallback = new StringTag(""); } if ("object".equals(type)) { tag.put("text", fallback); tag.remove("type"); } if (tag.remove("sprite") != null) { tag.put("text", fallback); } if (tag.remove("player") != null) { tag.put("text", fallback); } tag.remove("atlas"); } @Override protected void handleShowItem(final UserConnection connection, final CompoundTag itemTag, final CompoundTag componentsTag) { super.handleShowItem(connection, itemTag, componentsTag); if (componentsTag == null) { return; } removeDataComponents(componentsTag, StructuredDataKey.ENTITY_DATA1_21_9, StructuredDataKey.BLOCK_ENTITY_DATA1_21_9); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_9to1_21_7/rewriter/EntityPacketRewriter1_21_9.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.Protocol1_21_9To1_21_7; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.storage.MannequinData; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.storage.PlayerRotationStorage; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.tracker.EntityTracker1_21_9; import com.viaversion.viabackwards.utils.VelocityUtil; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.entity.TrackedEntity; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.GameProfile; import com.viaversion.viaversion.api.minecraft.GlobalBlockPosition; import com.viaversion.viaversion.api.minecraft.ResolvableProfile; import com.viaversion.viaversion.api.minecraft.Vector3d; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_6; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_9; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_5; 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.api.type.types.version.VersionedTypes; 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.ServerboundPackets1_21_6; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundPacket1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundPackets1_21_9; import com.viaversion.viaversion.protocols.v1_21to1_21_2.storage.BundleStateTracker; import com.viaversion.viaversion.rewriter.entitydata.EntityDataHandler; import com.viaversion.viaversion.util.ChatColorUtil; import com.viaversion.viaversion.util.Copyable; import java.util.BitSet; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; import org.checkerframework.checker.nullness.qual.Nullable; public final class EntityPacketRewriter1_21_9 extends EntityRewriter { public EntityPacketRewriter1_21_9(final Protocol1_21_9To1_21_7 protocol) { super(protocol, VersionedTypes.V1_21_9.entityDataTypes.optionalComponentType, VersionedTypes.V1_21_9.entityDataTypes.booleanType); } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_21_9.ADD_ENTITY, wrapper -> { final int entityId = wrapper.passthrough(Types.VAR_INT); final UUID uuid = wrapper.passthrough(Types.UUID); final int entityTypeId = wrapper.passthrough(Types.VAR_INT); final double x = wrapper.passthrough(Types.DOUBLE); final double y = wrapper.passthrough(Types.DOUBLE); final double z = wrapper.passthrough(Types.DOUBLE); final Vector3d movement = wrapper.read(Types.LOW_PRECISION_VECTOR); final byte pitch = wrapper.passthrough(Types.BYTE); final byte yaw = wrapper.passthrough(Types.BYTE); final byte headYaw = wrapper.passthrough(Types.BYTE); final int data = wrapper.passthrough(Types.VAR_INT); final EntityType entityType = trackAndRewrite(wrapper, entityTypeId, entityId); if (protocol.getMappingData() != null && entityType == EntityTypes1_21_9.FALLING_BLOCK) { final int mappedBlockStateId = protocol.getMappingData().getNewBlockStateId(data); wrapper.set(Types.VAR_INT, 2, mappedBlockStateId); } writeMovementShorts(wrapper, movement); if (EntityTypes1_21_9.getTypeFromId(entityTypeId) == EntityTypes1_21_9.MANNEQUIN) { final String name = randomHackyEmptyName(); final MannequinData mannequinData = new MannequinData(uuid, name); final TrackedEntity trackedEntity = tracker(wrapper.user()).entity(entityId); trackedEntity.data().put(mannequinData); sendInitialPlayerInfoUpdate(wrapper.user(), mannequinData, new GameProfile.Property[0]); mannequinData.setPosition(x, y, z); mannequinData.setRotation(yaw, pitch); mannequinData.setHeadYaw(headYaw); } }); // Track movement protocol.registerClientbound(ClientboundPackets1_21_9.TELEPORT_ENTITY, this::trackMannequinTeleport); protocol.registerClientbound(ClientboundPackets1_21_9.ENTITY_POSITION_SYNC, this::trackMannequinTeleport); protocol.registerClientbound(ClientboundPackets1_21_9.MOVE_ENTITY_POS, wrapper -> storeMovementMannequinData(wrapper, true, false)); protocol.registerClientbound(ClientboundPackets1_21_9.MOVE_ENTITY_POS_ROT, wrapper -> storeMovementMannequinData(wrapper, true, true)); protocol.registerClientbound(ClientboundPackets1_21_9.MOVE_ENTITY_ROT, wrapper -> storeMovementMannequinData(wrapper, false, true)); protocol.registerClientbound(ClientboundPackets1_21_9.ROTATE_HEAD, wrapper -> { final int vehicleId = wrapper.passthrough(Types.VAR_INT); final byte headRotation = wrapper.passthrough(Types.BYTE); final EntityTracker1_21_9 tracker = tracker(wrapper.user()); final TrackedEntity trackedEntity = tracker.entity(vehicleId); if (trackedEntity != null && trackedEntity.hasData()) { final MannequinData data = trackedEntity.data().get(MannequinData.class); if (data != null) { data.setHeadYaw(headRotation); } } }); // Track passengers protocol.registerClientbound(ClientboundPackets1_21_9.SET_PASSENGERS, wrapper -> { final int vehicleId = wrapper.passthrough(Types.VAR_INT); final int[] passengerIds = wrapper.passthrough(Types.VAR_INT_ARRAY_PRIMITIVE); final EntityTracker1_21_9 tracker = tracker(wrapper.user()); final TrackedEntity trackedEntity = tracker.entity(vehicleId); if (trackedEntity != null && trackedEntity.hasData()) { final MannequinData data = trackedEntity.data().get(MannequinData.class); if (data != null) { data.setPassengers(passengerIds); } } }); // Track items protocol.replaceClientbound(ClientboundPackets1_21_9.SET_EQUIPMENT, wrapper -> { final int entityId = wrapper.passthrough(Types.VAR_INT); final TrackedEntity trackedEntity = tracker(wrapper.user()).entity(entityId); final MannequinData mannequinData = trackedEntity != null && trackedEntity.hasData() ? trackedEntity.data().get(MannequinData.class) : null; byte slot; do { slot = wrapper.passthrough(Types.BYTE); final Item item = protocol.getItemRewriter().handleItemToClient(wrapper.user(), wrapper.read(protocol.getItemRewriter().itemType())); wrapper.write(protocol.getItemRewriter().mappedItemType(), item); if (mannequinData != null) { mannequinData.setEquipment((byte) (slot & 0x7F), item); } } while (slot < 0); }); // Back to more non-mannequin things protocol.registerClientbound(ClientboundPackets1_21_9.SET_ENTITY_MOTION, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Entity ID writeMovementShorts(wrapper, wrapper.read(Types.LOW_PRECISION_VECTOR)); }); protocol.registerClientbound(ClientboundPackets1_21_9.PLAYER_ROTATION, wrapper -> { final PlayerRotationStorage storage = wrapper.user().get(PlayerRotationStorage.class); float yRot = wrapper.read(Types.FLOAT); if (wrapper.read(Types.BOOLEAN)) { yRot = storage.yaw() + yRot; } float xRot = wrapper.read(Types.FLOAT); if (wrapper.read(Types.BOOLEAN)) { xRot = storage.pitch() + xRot; } wrapper.write(Types.FLOAT, yRot); wrapper.write(Types.FLOAT, xRot); storage.setRotation(yRot, xRot); // Update after having used its previous data }); protocol.registerClientbound(ClientboundPackets1_21_9.SET_DEFAULT_SPAWN_POSITION, wrapper -> { final GlobalBlockPosition pos = wrapper.read(Types.GLOBAL_POSITION); wrapper.write(Types.BLOCK_POSITION1_14, new BlockPosition(pos.x(), pos.y(), pos.z())); wrapper.passthrough(Types.FLOAT); // Yaw wrapper.read(Types.FLOAT); // Pitch }); protocol.registerServerbound(ServerboundPackets1_21_6.MOVE_PLAYER_POS_ROT, wrapper -> { wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z storePlayerRotation(wrapper); }); protocol.registerServerbound(ServerboundPackets1_21_6.MOVE_PLAYER_ROT, this::storePlayerRotation); } private void trackMannequinTeleport(final PacketWrapper wrapper) { final int entityId = wrapper.passthrough(Types.VAR_INT); final EntityTracker1_21_9 tracker = tracker(wrapper.user()); final TrackedEntity trackedEntity = tracker.entity(entityId); if (trackedEntity == null || !trackedEntity.hasData()) { return; } final MannequinData mannequinData = trackedEntity.data().get(MannequinData.class); if (mannequinData == null) { return; } final double x = wrapper.passthrough(Types.DOUBLE); final double y = wrapper.passthrough(Types.DOUBLE); final double z = wrapper.passthrough(Types.DOUBLE); wrapper.passthrough(Types.DOUBLE); // Delta movement X wrapper.passthrough(Types.DOUBLE); // Delta movement Y wrapper.passthrough(Types.DOUBLE); // Delta movement Z final byte yaw = (byte) Math.floor(wrapper.passthrough(Types.FLOAT) * 256F / 360F); final byte pitch = (byte) Math.floor(wrapper.passthrough(Types.FLOAT) * 256F / 360F); mannequinData.setPosition(x, y, z); mannequinData.setRotation(yaw, pitch); } private void storeMovementMannequinData(final PacketWrapper wrapper, final boolean position, final boolean rotation) { final int entityId = wrapper.passthrough(Types.VAR_INT); final TrackedEntity trackedEntity = tracker(wrapper.user()).entity(entityId); if (trackedEntity == null || !trackedEntity.hasData()) { return; } final MannequinData mannequinData = trackedEntity.data().get(MannequinData.class); if (mannequinData == null) { return; } if (position) { final double deltaX = wrapper.passthrough(Types.SHORT) / 4096.0; final double deltaY = wrapper.passthrough(Types.SHORT) / 4096.0; final double deltaZ = wrapper.passthrough(Types.SHORT) / 4096.0; mannequinData.setPosition(mannequinData.x() + deltaX, mannequinData.y() + deltaY, mannequinData.z() + deltaZ); } if (rotation) { final byte yaw = wrapper.passthrough(Types.BYTE); final byte pitch = wrapper.passthrough(Types.BYTE); mannequinData.setRotation(yaw, pitch); } } private void sendInitialPlayerInfoUpdate(final UserConnection connection, final MannequinData mannequinData, final GameProfile.Property[] properties) { final PacketWrapper playerInfo = PacketWrapper.create(ClientboundPackets1_21_6.PLAYER_INFO_UPDATE, connection); final BitSet actions = new BitSet(8); for (int i = 0; i < 8; i++) { actions.set(i); } playerInfo.write(Types.PROFILE_ACTIONS_ENUM1_21_4, actions); playerInfo.write(Types.VAR_INT, 1); // One entry playerInfo.write(Types.UUID, mannequinData.uuid()); playerInfo.write(Types.STRING, mannequinData.name()); playerInfo.write(Types.PROFILE_PROPERTY_ARRAY, properties); playerInfo.write(Types.BOOLEAN, false); // Session info playerInfo.write(Types.VAR_INT, 0); // Gamemode playerInfo.write(Types.BOOLEAN, false); // Listed playerInfo.write(Types.VAR_INT, 0); // Latency playerInfo.write(Types.TRUSTED_OPTIONAL_TAG, null); playerInfo.write(Types.VAR_INT, 1000); // List order playerInfo.write(Types.BOOLEAN, true); // Show hat playerInfo.send(Protocol1_21_9To1_21_7.class); sendPlayerTeamDisplayName(connection, mannequinData, mannequinData.displayName()); } private void sendPlayerInfoDisplayNameUpdate(final UserConnection connection, final MannequinData mannequinData, @Nullable final Tag displayName) { final PacketWrapper playerInfo = PacketWrapper.create(ClientboundPackets1_21_6.PLAYER_INFO_UPDATE, connection); final BitSet actions = new BitSet(8); actions.set(5); playerInfo.write(Types.PROFILE_ACTIONS_ENUM1_21_4, actions); playerInfo.write(Types.VAR_INT, 1); playerInfo.write(Types.UUID, mannequinData.uuid()); playerInfo.write(Types.TRUSTED_OPTIONAL_TAG, displayName); playerInfo.send(Protocol1_21_9To1_21_7.class); sendPlayerTeamDisplayName(connection, mannequinData, displayName); } private void sendPlayerTeamDisplayName(final UserConnection connection, final MannequinData mannequinData, final Tag displayName) { // Send the display name as a team prefix final Tag nonNullDisplayName = displayName != null ? displayName : new StringTag("Mannequin"); final PacketWrapper addTeam = PacketWrapper.create(ClientboundPackets1_21_6.SET_PLAYER_TEAM, connection); addTeam.write(Types.STRING, mannequinData.name()); addTeam.write(Types.BYTE, mannequinData.hasTeam() ? (byte) 2 : 0); // Mode addTeam.write(Types.TRUSTED_TAG, nonNullDisplayName); // Display Name addTeam.write(Types.BYTE, (byte) 0); // Flags addTeam.write(Types.VAR_INT, 0); // Nametag visibility addTeam.write(Types.VAR_INT, 0); // Collision rule addTeam.write(Types.VAR_INT, 15); // Color addTeam.write(Types.TRUSTED_TAG, nonNullDisplayName); // Prefix addTeam.write(Types.TRUSTED_TAG, new StringTag("")); // Suffix if (!mannequinData.hasTeam()) { addTeam.write(Types.STRING_ARRAY, new String[]{mannequinData.name()}); mannequinData.setHasTeam(true); } addTeam.send(Protocol1_21_9To1_21_7.class); } private String randomHackyEmptyName() { final StringBuilder builder = new StringBuilder(); // Player names cannot be updated after the initial add without fully respawning them; // Stack random color codes to appear as an empty name, later filled with a team prefix for (int i = 0; i < 8; i++) { final int random = ThreadLocalRandom.current().nextInt(ChatColorUtil.ALL_CODES.length()); builder.append('§').append(ChatColorUtil.ALL_CODES.charAt(random)); } return builder.toString(); } private void writeMovementShorts(final PacketWrapper wrapper, final Vector3d movement) { wrapper.write(Types.SHORT, VelocityUtil.toLegacyVelocity(movement.x())); wrapper.write(Types.SHORT, VelocityUtil.toLegacyVelocity(movement.y())); wrapper.write(Types.SHORT, VelocityUtil.toLegacyVelocity(movement.z())); } private void storePlayerRotation(final PacketWrapper wrapper) { final float yaw = wrapper.passthrough(Types.FLOAT); final float pitch = wrapper.passthrough(Types.FLOAT); wrapper.user().get(PlayerRotationStorage.class).setRotation(yaw, pitch); } @Override protected void registerRewrites() { final EntityDataTypes1_21_5 entityDataTypes = protocol.mappedTypes().entityDataTypes(); dataTypeMapper() .added(entityDataTypes.compoundTagType) .removed(VersionedTypes.V1_21_9.entityDataTypes.copperGolemState) .removed(VersionedTypes.V1_21_9.entityDataTypes.weatheringCopperState) .removed(VersionedTypes.V1_21_9.entityDataTypes.mannequinProfileType) .register(); registerEntityDataTypeHandler1_20_3( entityDataTypes.itemType, entityDataTypes.blockStateType, entityDataTypes.optionalBlockStateType, entityDataTypes.particleType, entityDataTypes.particlesType, entityDataTypes.componentType, entityDataTypes.optionalComponentType ); final EntityDataHandler shoulderDataHandler = (event, data) -> { final CompoundTag entityTag = new CompoundTag(); final Integer value = data.value(); if (value != null) { entityTag.putInt("id", EntityTypes1_21_6.PARROT.getId()); entityTag.putInt("Variant", value); } data.setTypeAndValue(protocol.mappedTypes().entityDataTypes.compoundTagType, entityTag); }; filter().type(EntityTypes1_21_9.PLAYER).index(19).handler(shoulderDataHandler); filter().type(EntityTypes1_21_9.PLAYER).index(20).handler(shoulderDataHandler); filter().type(EntityTypes1_21_9.PLAYER).handler((event, data) -> { if (event.index() == 15) { event.setIndex(18); } else if (event.index() == 16) { event.setIndex(17); } else if (event.index() == 17 || event.index() == 18) { event.setIndex(event.index() - 2); // Move hearts and score back down } }); filter().type(EntityTypes1_21_9.MANNEQUIN).handler((event, data) -> { if (event.index() == 2) { // Display name final Tag displayName = data.value(); final MannequinData mannequinData = event.trackedEntity().data().get(MannequinData.class); mannequinData.setDisplayName(displayName); sendPlayerInfoDisplayNameUpdate(event.user(), mannequinData, displayName); } else if (event.index() == 17) { // Profile final boolean isBundling = event.user().get(BundleStateTracker.class).isBundling(); if (!isBundling) { final PacketWrapper bundleStart = PacketWrapper.create(ClientboundPackets1_21_6.BUNDLE_DELIMITER, event.user()); bundleStart.send(Protocol1_21_9To1_21_7.class); } final ResolvableProfile profile = data.value(); final MannequinData mannequinData = event.trackedEntity().data().get(MannequinData.class); final UUID uuid = mannequinData.uuid(); // Remove the old entity final PacketWrapper removeEntityPacket = PacketWrapper.create(ClientboundPackets1_21_6.REMOVE_ENTITIES, event.user()); removeEntityPacket.write(Types.VAR_INT_ARRAY_PRIMITIVE, new int[]{event.entityId()}); removeEntityPacket.send(Protocol1_21_9To1_21_7.class); final PacketWrapper playerInfoRemove = PacketWrapper.create(ClientboundPackets1_21_6.PLAYER_INFO_REMOVE, event.user()); playerInfoRemove.write(Types.UUID_ARRAY, new UUID[]{uuid}); playerInfoRemove.send(Protocol1_21_9To1_21_7.class); // Spawn new entity sendInitialPlayerInfoUpdate(event.user(), mannequinData, profile.profile().properties()); final PacketWrapper spawnEntityPacket = PacketWrapper.create(ClientboundPackets1_21_6.ADD_ENTITY, event.user()); spawnEntityPacket.write(Types.VAR_INT, event.entityId()); spawnEntityPacket.write(Types.UUID, mannequinData.uuid()); spawnEntityPacket.write(Types.VAR_INT, EntityTypes1_21_6.PLAYER.getId()); spawnEntityPacket.write(Types.DOUBLE, mannequinData.x()); spawnEntityPacket.write(Types.DOUBLE, mannequinData.y()); spawnEntityPacket.write(Types.DOUBLE, mannequinData.z()); spawnEntityPacket.write(Types.BYTE, mannequinData.pitch()); spawnEntityPacket.write(Types.BYTE, mannequinData.yaw()); spawnEntityPacket.write(Types.BYTE, mannequinData.headYaw()); spawnEntityPacket.write(Types.VAR_INT, 0); // Data spawnEntityPacket.write(Types.SHORT, (short) 0); // Velocity X spawnEntityPacket.write(Types.SHORT, (short) 0); // Velocity Y spawnEntityPacket.write(Types.SHORT, (short) 0); // Velocity Z spawnEntityPacket.send(Protocol1_21_9To1_21_7.class); // Re-apply entity data previously set final PacketWrapper setEntityDataPacket = PacketWrapper.create(ClientboundPackets1_21_6.SET_ENTITY_DATA, event.user()); setEntityDataPacket.write(Types.VAR_INT, event.entityId()); setEntityDataPacket.write(VersionedTypes.V1_21_6.entityDataList, mannequinData.entityData()); setEntityDataPacket.send(Protocol1_21_9To1_21_7.class); // Re-attach all passengers if (mannequinData.passengers() != null) { final PacketWrapper setPassengersPacket = PacketWrapper.create(ClientboundPackets1_21_6.SET_PASSENGERS, event.user()); setPassengersPacket.write(Types.VAR_INT, event.entityId()); setPassengersPacket.write(Types.VAR_INT_ARRAY_PRIMITIVE, mannequinData.passengers()); setPassengersPacket.send(Protocol1_21_9To1_21_7.class); } // Put on items if (!mannequinData.itemMap().isEmpty()) { final PacketWrapper equipment = PacketWrapper.create(ClientboundPackets1_21_6.SET_EQUIPMENT, event.user()); equipment.write(Types.VAR_INT, event.entityId()); int i = 0; for (final Map.Entry itemEntry : mannequinData.itemMap().entrySet()) { final boolean more = i < mannequinData.itemMap().size() - 1; equipment.write(Types.BYTE, more ? (byte) (itemEntry.getKey() | -128) : itemEntry.getKey()); equipment.write(protocol.getItemRewriter().mappedItemType(), itemEntry.getValue()); i++; } equipment.send(Protocol1_21_9To1_21_7.class); } final PacketWrapper setHeadRotation = PacketWrapper.create(ClientboundPackets1_21_6.ROTATE_HEAD, event.user()); setHeadRotation.write(Types.VAR_INT, event.entityId()); setHeadRotation.write(Types.BYTE, mannequinData.headYaw()); setHeadRotation.send(Protocol1_21_9To1_21_7.class); if (!isBundling) { final PacketWrapper bundleStart = PacketWrapper.create(ClientboundPackets1_21_6.BUNDLE_DELIMITER, event.user()); bundleStart.send(Protocol1_21_9To1_21_7.class); } event.cancel(); } else if (event.index() == 15) { event.setIndex(18); } else if (event.index() == 16) { event.setIndex(17); } else if (event.index() == 18) { // TODO Immovable? event.cancel(); } else if (event.index() == 19) { event.cancel(); // Description } }); } @Override public void handleEntityData(final int entityId, final List dataList, final UserConnection connection) { super.handleEntityData(entityId, dataList, connection); final EntityTracker1_21_9 tracker = tracker(connection); final EntityType entityType = tracker.entityType(entityId); if (entityType == null || !entityType.isOrHasParent(EntityTypes1_21_9.MANNEQUIN)) { return; } final MannequinData mannequinData = tracker.entity(entityId).data().get(MannequinData.class); if (mannequinData == null) { return; } final List entityData = mannequinData.entityData(); entityData.removeIf(first -> dataList.stream().anyMatch(second -> first.id() == second.id())); for (final EntityData data : dataList) { final Object value = data.value(); entityData.add(new EntityData(data.id(), data.dataType(), Copyable.copy(value))); } } @Override public void onMappingDataLoaded() { super.onMappingDataLoaded(); mapEntityTypeWithData(EntityTypes1_21_9.COPPER_GOLEM, EntityTypes1_21_9.FROG).tagName(); mapEntityTypeWithData(EntityTypes1_21_9.MANNEQUIN, EntityTypes1_21_9.PLAYER); } @Override public EntityType typeFromId(final int type) { return EntityTypes1_21_9.getTypeFromId(type); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_9to1_21_7/rewriter/ParticleRewriter1_21_9.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.rewriter; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.protocol.Protocol; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundPacket1_21_9; import com.viaversion.viaversion.rewriter.ParticleRewriter; public final class ParticleRewriter1_21_9 extends ParticleRewriter { public ParticleRewriter1_21_9(final Protocol protocol) { super(protocol); } @Override public void rewriteParticle(final UserConnection connection, final Particle particle) { super.rewriteParticle(connection, particle); final String identifier = protocol.getMappingData().getParticleMappings().mappedIdentifier(particle.id()); if ("minecraft:dragon_breath".equals(identifier)) { particle.removeArgument(0); // Power } else if ("minecraft:flash".equals(identifier)) { particle.removeArgument(0); // Color } else if ("minecraft:effect".equals(identifier) || "minecraft:instant_effect".equals(identifier)) { particle.removeArgument(1); // Power particle.removeArgument(0); // Color } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_9to1_21_7/rewriter/RegistryDataRewriter1_21_9.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.storage.DimensionScaleStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.RegistryEntry; public final class RegistryDataRewriter1_21_9 extends BackwardsRegistryRewriter { public RegistryDataRewriter1_21_9(final BackwardsProtocol protocol) { super(protocol); } @Override public void trackDimensionAndBiomes(final UserConnection connection, final String registryKey, final RegistryEntry[] entries) { super.trackDimensionAndBiomes(connection, registryKey, entries); if (!registryKey.equals("dimension_type")) { return; } final DimensionScaleStorage dimensionScaleStorage = connection.get(DimensionScaleStorage.class); for (int i = 0; i < entries.length; i++) { final RegistryEntry entry = entries[i]; final CompoundTag dimension = (CompoundTag) entry.tag(); if (dimension == null) { continue; } final double coordinateScale = dimension.getDouble("coordinate_scale", 1); dimensionScaleStorage.setScale(i, coordinateScale); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_9to1_21_7/storage/DimensionScaleStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.storage; import com.viaversion.viaversion.api.connection.StorableObject; import java.util.HashMap; import java.util.Map; public final class DimensionScaleStorage implements StorableObject { private final Map dimensionScales = new HashMap<>(4); public double getScale(final int dimensionId) { return dimensionScales.getOrDefault(dimensionId, 1D); } public void setScale(final int dimensionId, final double scale) { dimensionScales.put(dimensionId, scale); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_9to1_21_7/storage/MannequinData.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.storage; import com.viaversion.nbt.tag.Tag; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; import com.viaversion.viaversion.api.minecraft.item.Item; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; public final class MannequinData { private final UUID uuid; private final String name; private boolean hasTeam; private double x; private double y; private double z; // Packed values private byte yaw; private byte pitch; private byte headYaw; private int[] passengers; private final List entityData = new ArrayList<>(); private final Map itemMap = new HashMap<>(); private Tag displayName; public MannequinData(final UUID uuid, final String name) { this.uuid = uuid; this.name = name; } public void setHasTeam(final boolean hasTeam) { this.hasTeam = hasTeam; } public boolean hasTeam() { return hasTeam; } public UUID uuid() { return uuid; } public String name() { return name; } public void setPosition(final double x, final double y, final double z) { this.x = x; this.y = y; this.z = z; } public void setRotation(final byte yaw, final byte pitch) { this.yaw = yaw; this.pitch = pitch; } public void setPassengers(final int[] passengers) { this.passengers = passengers; } public double x() { return x; } public double y() { return y; } public double z() { return z; } public byte yaw() { return yaw; } public byte pitch() { return pitch; } public List entityData() { return entityData; } public int[] passengers() { return passengers; } public void setDisplayName(Tag displayName) { this.displayName = displayName; } public Tag displayName() { return displayName; } public void setEquipment(byte slot, Item item) { itemMap.put(slot, item); } public Map itemMap() { return itemMap; } public byte headYaw() { return headYaw; } public void setHeadYaw(byte headYaw) { this.headYaw = headYaw; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_9to1_21_7/storage/PlayerRotationStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.storage; import com.viaversion.viaversion.api.connection.StorableObject; public final class PlayerRotationStorage implements StorableObject { private float yaw, pitch; public void setRotation(final float yaw, final float pitch) { this.yaw = yaw; this.pitch = pitch; } public float yaw() { return yaw; } public float pitch() { return pitch; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_9to1_21_7/tracker/EntityTracker1_21_9.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.tracker; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.Protocol1_21_9To1_21_7; import com.viaversion.viabackwards.protocol.v1_21_9to1_21_7.storage.MannequinData; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.entity.StoredEntityData; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ClientboundPackets1_21_6; import java.util.UUID; import org.checkerframework.checker.nullness.qual.Nullable; public final class EntityTracker1_21_9 extends EntityTrackerBase { public EntityTracker1_21_9(final UserConnection connection, @Nullable final EntityType playerType) { super(connection, playerType); } @Override public void removeEntity(final int id) { sendRemovePacket(id); super.removeEntity(id); } public void sendRemovePacket(final int id) { final StoredEntityData entityData = entityDataIfPresent(id); final MannequinData trackedEntityId; if (entityData != null && (trackedEntityId = entityData.get(MannequinData.class)) != null) { final PacketWrapper playerInfoRemove = PacketWrapper.create(ClientboundPackets1_21_6.PLAYER_INFO_REMOVE, user()); playerInfoRemove.write(Types.UUID_ARRAY, new UUID[]{trackedEntityId.uuid()}); playerInfoRemove.send(Protocol1_21_9To1_21_7.class); final PacketWrapper removePlayerTeam = PacketWrapper.create(ClientboundPackets1_21_6.SET_PLAYER_TEAM, user()); removePlayerTeam.write(Types.STRING, trackedEntityId.name()); removePlayerTeam.write(Types.BYTE, (byte) 1); // Method removePlayerTeam.send(Protocol1_21_9To1_21_7.class); } } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21to1_20_5/Protocol1_21To1_20_5.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21to1_20_5; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.rewriter.BlockItemPacketRewriter1_21; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.rewriter.ComponentRewriter1_21; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.rewriter.EntityPacketRewriter1_21; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.EnchantmentsPaintingsStorage; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.OpenScreenStorage; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.PlayerRotationStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.Holder; import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_20_5; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_20_5; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_20_5; import com.viaversion.viaversion.api.minecraft.item.data.ChatType; 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; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; import com.viaversion.viaversion.api.type.types.version.Types1_20_5; import com.viaversion.viaversion.api.type.types.version.Types1_21; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ClientboundConfigurationPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ClientboundPacket1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ClientboundPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundConfigurationPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPacket1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_5to1_21.Protocol1_20_5To1_21; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundConfigurationPackets1_21; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPacket1_21; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.util.ArrayUtil; import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap; public final class Protocol1_21To1_20_5 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("1.21", "1.20.5", Protocol1_20_5To1_21.class); private final EntityPacketRewriter1_21 entityRewriter = new EntityPacketRewriter1_21(this); private final BlockItemPacketRewriter1_21 itemRewriter = new BlockItemPacketRewriter1_21(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final JsonNBTComponentRewriter translatableRewriter = new ComponentRewriter1_21(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BlockRewriter blockRewriter = BlockRewriter.for1_20_2(this, ChunkType1_20_2::new); private final BackwardsRegistryRewriter registryDataRewriter = new BackwardsRegistryRewriter(this); public Protocol1_21To1_20_5() { super(ClientboundPacket1_21.class, ClientboundPacket1_20_5.class, ServerboundPacket1_20_5.class, ServerboundPacket1_20_5.class); } @Override protected void registerPackets() { super.registerPackets(); // Format change we can't properly map - all this really results in is a desync one version earlier cancelClientbound(ClientboundPackets1_21.PROJECTILE_POWER); cancelClientbound(ClientboundPackets1_21.CUSTOM_REPORT_DETAILS); cancelClientbound(ClientboundPackets1_21.SERVER_LINKS); cancelClientbound(ClientboundConfigurationPackets1_21.CUSTOM_REPORT_DETAILS); cancelClientbound(ClientboundConfigurationPackets1_21.SERVER_LINKS); replaceClientbound(ClientboundPackets1_21.DISGUISED_CHAT, wrapper -> { translatableRewriter.processTag(wrapper.user(), wrapper.passthrough(Types.TRUSTED_TAG)); // Message final Holder chatType = wrapper.read(ChatType.TYPE); if (chatType.isDirect()) { // Oh well wrapper.write(Types.VAR_INT, 0); return; } wrapper.write(Types.VAR_INT, chatType.id()); }); replaceClientbound(ClientboundPackets1_21.PLAYER_CHAT, wrapper -> { wrapper.passthrough(Types.UUID); // Sender wrapper.passthrough(Types.VAR_INT); // Index wrapper.passthrough(Types.OPTIONAL_SIGNATURE_BYTES); // Signature wrapper.passthrough(Types.STRING); // Plain content wrapper.passthrough(Types.LONG); // Timestamp wrapper.passthrough(Types.LONG); // Salt final int lastSeen = wrapper.passthrough(Types.VAR_INT); for (int i = 0; i < lastSeen; i++) { final int index = wrapper.passthrough(Types.VAR_INT); if (index == 0) { wrapper.passthrough(Types.SIGNATURE_BYTES); } } wrapper.passthrough(Types.TRUSTED_OPTIONAL_TAG); // Unsigned content final int filterMaskType = wrapper.passthrough(Types.VAR_INT); if (filterMaskType == 2) { wrapper.passthrough(Types.LONG_ARRAY_PRIMITIVE); // Mask } final Holder chatType = wrapper.read(ChatType.TYPE); if (chatType.isDirect()) { // Oh well wrapper.write(Types.VAR_INT, 0); } else { wrapper.write(Types.VAR_INT, chatType.id()); } translatableRewriter.processTag(wrapper.user(), wrapper.passthrough(Types.TRUSTED_TAG)); // Name translatableRewriter.processTag(wrapper.user(), wrapper.passthrough(Types.TRUSTED_OPTIONAL_TAG)); // Target Name }); replaceClientbound(ClientboundPackets1_21.UPDATE_ATTRIBUTES, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Entity ID final int size = wrapper.passthrough(Types.VAR_INT); int newSize = size; for (int i = 0; i < size; i++) { final int attributeId = wrapper.read(Types.VAR_INT); final int mappedId = MAPPINGS.getNewAttributeId(attributeId); if (mappedId == -1) { newSize--; wrapper.read(Types.DOUBLE); // Base final int modifierSize = wrapper.read(Types.VAR_INT); for (int j = 0; j < modifierSize; j++) { wrapper.read(Types.STRING); // ID wrapper.read(Types.DOUBLE); // Amount wrapper.read(Types.BYTE); // Operation } continue; } wrapper.write(Types.VAR_INT, mappedId); wrapper.passthrough(Types.DOUBLE); // Base final int modifierSize = wrapper.passthrough(Types.VAR_INT); for (int j = 0; j < modifierSize; j++) { final String id = wrapper.read(Types.STRING); wrapper.write(Types.UUID, Protocol1_20_5To1_21.mapAttributeId(id)); wrapper.passthrough(Types.DOUBLE); // Amount wrapper.passthrough(Types.BYTE); // Operation } } if (size != newSize) { wrapper.set(Types.VAR_INT, 1, newSize); } }); registerClientbound(ClientboundConfigurationPackets1_21.UPDATE_ENABLED_FEATURES, wrapper -> { final String[] enabledFeatures = wrapper.read(Types.STRING_ARRAY); wrapper.write(Types.STRING_ARRAY, ArrayUtil.add(enabledFeatures, "minecraft:update_1_21")); }); } @Override public void init(final UserConnection user) { addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_20_5.PLAYER)); user.put(new EnchantmentsPaintingsStorage()); user.put(new OpenScreenStorage()); user.put(new PlayerRotationStorage()); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter1_21 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter1_21 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public BackwardsRegistryRewriter getRegistryDataRewriter() { return registryDataRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public JsonNBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } @Override public Types1_21 types() { return VersionedTypes.V1_21; } @Override public Types1_20_5 mappedTypes() { return VersionedTypes.V1_20_5; } @Override protected PacketTypesProvider createPacketTypesProvider() { return new SimplePacketTypesProvider<>( packetTypeMap(unmappedClientboundPacketType, ClientboundPackets1_21.class, ClientboundConfigurationPackets1_21.class), packetTypeMap(mappedClientboundPacketType, ClientboundPackets1_20_5.class, ClientboundConfigurationPackets1_20_5.class), packetTypeMap(mappedServerboundPacketType, ServerboundPackets1_20_5.class, ServerboundConfigurationPackets1_20_5.class), packetTypeMap(unmappedServerboundPacketType, ServerboundPackets1_20_5.class, ServerboundConfigurationPackets1_20_5.class) ); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21to1_20_5/rewriter/BlockItemPacketRewriter1_21.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21to1_20_5.rewriter; import com.viaversion.nbt.tag.ByteTag; 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.api.rewriters.BackwardsStructuredItemRewriter; import com.viaversion.viabackwards.api.rewriters.EnchantmentRewriter; import com.viaversion.viabackwards.api.rewriters.StructuredEnchantmentRewriter; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.Protocol1_21To1_20_5; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.EnchantmentsPaintingsStorage; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.OpenScreenStorage; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.PlayerRotationStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.EitherHolder; import com.viaversion.viaversion.api.minecraft.Holder; import com.viaversion.viaversion.api.minecraft.SoundEvent; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.item.data.AttributeModifiers1_21; import com.viaversion.viaversion.api.minecraft.item.data.AttributeModifiers1_21.AttributeModifier; import com.viaversion.viaversion.api.minecraft.item.data.AttributeModifiers1_21.ModifierData; import com.viaversion.viaversion.api.minecraft.item.data.Enchantments; import com.viaversion.viaversion.api.minecraft.item.data.JukeboxPlayable; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; import com.viaversion.viaversion.libs.fastutil.ints.Int2IntMap; import com.viaversion.viaversion.libs.mcstructs.text.TextFormatting; import com.viaversion.viaversion.libs.mcstructs.text.components.StringComponent; import com.viaversion.viaversion.libs.mcstructs.text.components.TranslationComponent; import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.rewriter.RecipeRewriter1_20_3; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.data.Enchantments1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPacket1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPacket1_21; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.IdRewriteFunction; import com.viaversion.viaversion.util.SerializerVersion; import java.util.ArrayList; import java.util.List; import static com.viaversion.viaversion.protocols.v1_20_5to1_21.rewriter.BlockItemPacketRewriter1_21.downgradeItemData; import static com.viaversion.viaversion.protocols.v1_20_5to1_21.rewriter.BlockItemPacketRewriter1_21.updateItemData; public final class BlockItemPacketRewriter1_21 extends BackwardsStructuredItemRewriter { private final StructuredEnchantmentRewriter enchantmentRewriter = new StructuredEnchantmentRewriter(this); public BlockItemPacketRewriter1_21(final Protocol1_21To1_20_5 protocol) { super(protocol); } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundPackets1_21.OPEN_SCREEN, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Id // Tracking the type actually matters now with crafters also using container data above index 3 final int menuType = wrapper.passthrough(Types.VAR_INT); wrapper.user().get(OpenScreenStorage.class).setMenuType(menuType); protocol.getComponentRewriter().passthroughAndProcess(wrapper); }); protocol.registerClientbound(ClientboundPackets1_21.CONTAINER_SET_DATA, wrapper -> { wrapper.passthrough(Types.UNSIGNED_BYTE); // Container id final short property = wrapper.passthrough(Types.SHORT); if (property >= 4 && property <= 6) { // Enchantment hints final OpenScreenStorage openScreenStorage = wrapper.user().get(OpenScreenStorage.class); if (openScreenStorage.menuType() != 13) { // Enchantment table return; } final short enchantmentId = wrapper.read(Types.SHORT); final EnchantmentsPaintingsStorage storage = wrapper.user().get(EnchantmentsPaintingsStorage.class); final String key = storage.enchantments().idToKey(enchantmentId); final int mappedId = key != null ? Enchantments1_20_5.keyToId(key) : -1; wrapper.write(Types.SHORT, (short) mappedId); } }); protocol.registerClientbound(ClientboundPackets1_21.HORSE_SCREEN_OPEN, wrapper -> { wrapper.passthrough(Types.UNSIGNED_BYTE); // Container id // From columns to size final int columns = wrapper.read(Types.VAR_INT); wrapper.write(Types.VAR_INT, columns * 3 + 1); }); protocol.replaceClientbound(ClientboundPackets1_21.LEVEL_EVENT, wrapper -> { final int event = wrapper.passthrough(Types.INT); wrapper.passthrough(Types.BLOCK_POSITION1_14); final int data = wrapper.read(Types.INT); if (event == 1010) { final int itemId = wrapper.user().get(EnchantmentsPaintingsStorage.class).jubeboxSongToItem(data); if (itemId == -1) { wrapper.cancel(); return; } wrapper.write(Types.INT, itemId); } else if (event == 2001) { wrapper.write(Types.INT, protocol.getMappingData().getNewBlockStateId(data)); } else { wrapper.write(Types.INT, data); } }); protocol.registerServerbound(ServerboundPackets1_20_5.USE_ITEM, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Hand wrapper.passthrough(Types.VAR_INT); // Sequence final PlayerRotationStorage rotation = wrapper.user().get(PlayerRotationStorage.class); wrapper.write(Types.FLOAT, rotation.yaw()); wrapper.write(Types.FLOAT, rotation.pitch()); }); new RecipeRewriter1_20_3<>(protocol).register1_20_5(ClientboundPackets1_21.UPDATE_RECIPES); } @Override public Item handleItemToClient(final UserConnection connection, Item item) { if (item.isEmpty()) { return item; } final StructuredDataContainer data = item.dataContainer(); data.setIdLookup(protocol, true); // Enchantments final EnchantmentsPaintingsStorage storage = connection.get(EnchantmentsPaintingsStorage.class); final IdRewriteFunction idRewriteFunction = id -> { final String key = storage.enchantments().idToKey(id); return key != null ? Enchantments1_20_5.keyToId(key) : -1; }; final StructuredEnchantmentRewriter.DescriptionSupplier descriptionSupplier = (id, level) -> { final Tag description = storage.enchantmentDescription(id); if (description == null) { return new StringTag("Unknown enchantment"); } final var component = SerializerVersion.V1_20_5.toComponent(description); component.getStyle().setItalic(false); component.getStyle().setFormatting(TextFormatting.GRAY); if (level != 1 || storage.enchantmentMaxLevel(id) != 1) { component.getSiblings().add(new StringComponent(" ")); component.getSiblings().add(new TranslationComponent(EnchantmentRewriter.ENCHANTMENT_LEVEL_TRANSLATION.formatted(level))); } return SerializerVersion.V1_20_5.toTag(component); }; enchantmentRewriter.rewriteEnchantmentsToClient(data, StructuredDataKey.ENCHANTMENTS1_20_5, idRewriteFunction, descriptionSupplier, false); enchantmentRewriter.rewriteEnchantmentsToClient(data, StructuredDataKey.STORED_ENCHANTMENTS1_20_5, idRewriteFunction, descriptionSupplier, true); final int identifier = item.identifier(); // Order is important backupInconvertibleData(item); item = super.handleItemToClient(connection, item); downgradeItemData(item); if (data.has(StructuredDataKey.RARITY)) { return item; } // Change rarity of trident and piglin banner pattern final boolean trident = identifier == 1188; if (trident || identifier == 1200) { data.set(StructuredDataKey.RARITY, trident ? 3 : 1); // Epic or Uncommon saveTag(createCustomTag(item), new ByteTag(true), "rarity"); } return item; } @Override public Item handleItemToServer(final UserConnection connection, Item item) { if (item.isEmpty()) { return item; } final StructuredDataContainer data = item.dataContainer(); data.setIdLookup(protocol, false); // Rewrite enchantments final EnchantmentsPaintingsStorage storage = connection.get(EnchantmentsPaintingsStorage.class); rewriteEnchantmentToServer(storage, item, StructuredDataKey.ENCHANTMENTS1_20_5); rewriteEnchantmentToServer(storage, item, StructuredDataKey.STORED_ENCHANTMENTS1_20_5); // Restore originals if present enchantmentRewriter.handleToServer(item); // Order is important item = super.handleItemToServer(connection, item); updateItemData(item); restoreInconvertibleData(item); final CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA); if (customData == null) { return item; } if (customData.remove(nbtTagName("rarity")) != null) { data.remove(StructuredDataKey.RARITY); removeCustomTag(data, customData); } return item; } private void rewriteEnchantmentToServer(final EnchantmentsPaintingsStorage storage, final Item item, final StructuredDataKey key) { final Enchantments enchantments = item.dataContainer().get(key); if (enchantments == null) { return; } final List updatedIds = new ArrayList<>(); for (final Int2IntMap.Entry entry : enchantments.enchantments().int2IntEntrySet()) { final int id = entry.getIntKey(); final String enchantmentKey = Enchantments1_20_5.idToKey(id); if (enchantmentKey == null) { continue; } final int mappedId = storage.enchantments().keyToId(enchantmentKey); if (id != mappedId) { final int level = entry.getIntValue(); updatedIds.add(new PendingIdChange(id, mappedId, level)); } } // Remove first, then add updated entries for (final PendingIdChange change : updatedIds) { enchantments.remove(change.id); } for (final PendingIdChange change : updatedIds) { enchantments.add(change.mappedId, change.level); } } private void backupInconvertibleData(final Item item) { final StructuredDataContainer data = item.dataContainer(); data.setIdLookup(protocol, true); final CompoundTag backupTag = new CompoundTag(); final JukeboxPlayable jukeboxPlayable = data.get(StructuredDataKey.JUKEBOX_PLAYABLE1_21); if (jukeboxPlayable != null) { final CompoundTag tag = new CompoundTag(); if (jukeboxPlayable.song().hasHolder()) { final Holder songHolder = jukeboxPlayable.song().holder(); tag.put("song", holderToTag(songHolder, (song, songTag) -> { saveSoundEventHolder(songTag, song.soundEvent()); songTag.put("description", song.description()); songTag.putFloat("length_in_seconds", song.lengthInSeconds()); songTag.putInt("comparator_output", song.comparatorOutput()); })); } else { tag.putString("song_identifier", jukeboxPlayable.song().key()); } tag.putBoolean("show_in_tooltip", jukeboxPlayable.showInTooltip()); backupTag.put("jukebox_playable", tag); } final AttributeModifiers1_21 attributeModifiers = data.get(StructuredDataKey.ATTRIBUTE_MODIFIERS1_21); if (attributeModifiers != null) { final ListTag attributeIds = new ListTag<>(StringTag.class); for (final AttributeModifier modifier : attributeModifiers.modifiers()) { attributeIds.add(new StringTag(modifier.modifier().id())); } backupTag.put("attribute_modifiers", attributeIds); } if (!backupTag.isEmpty()) { saveTag(createCustomTag(item), backupTag, "inconvertible_data"); } } private void restoreInconvertibleData(final Item item) { final StructuredDataContainer data = item.dataContainer(); final CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA); if (customData == null || !(customData.remove(nbtTagName("inconvertible_data")) instanceof CompoundTag tag)) { return; } final CompoundTag jukeboxPlayableTag = tag.getCompoundTag("jukebox_playable"); if (jukeboxPlayableTag != null) { final EitherHolder song; final String songIdentifier = tag.getString("song_identifier"); if (songIdentifier != null) { song = EitherHolder.of(songIdentifier); } else { song = EitherHolder.of(restoreHolder(jukeboxPlayableTag, "song", songTag -> { final Holder soundEvent = restoreSoundEventHolder(songTag); final Tag description = songTag.get("description"); final float lengthInSeconds = songTag.getFloat("length_in_seconds"); final int comparatorOutput = songTag.getInt("comparator_output"); return new JukeboxPlayable.JukeboxSong(soundEvent, description, lengthInSeconds, comparatorOutput); })); } final JukeboxPlayable jukeboxPlayable = new JukeboxPlayable(song, tag.getBoolean("show_in_tooltip")); data.set(StructuredDataKey.JUKEBOX_PLAYABLE1_21, jukeboxPlayable); } final ListTag attributeIds = tag.getListTag("attribute_modifiers", StringTag.class); final AttributeModifiers1_21 attributeModifiers = data.get(StructuredDataKey.ATTRIBUTE_MODIFIERS1_21); if (attributeIds != null && attributeModifiers != null && attributeIds.size() == attributeModifiers.modifiers().length) { for (int i = 0; i < attributeIds.size(); i++) { final String id = attributeIds.get(i).getValue(); final AttributeModifier modifier = attributeModifiers.modifiers()[i]; final ModifierData updatedModifierData = new ModifierData(id, modifier.modifier().amount(), modifier.modifier().operation()); attributeModifiers.modifiers()[i] = new AttributeModifier(modifier.attribute(), updatedModifierData, modifier.slotType()); } } removeCustomTag(data, customData); } private record PendingIdChange(int id, int mappedId, int level) { } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21to1_20_5/rewriter/ComponentRewriter1_21.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21to1_20_5.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntArrayTag; import com.viaversion.nbt.tag.ListTag; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.Protocol1_21To1_20_5; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.data.Attributes1_20_5; import com.viaversion.viaversion.protocols.v1_20_5to1_21.Protocol1_20_5To1_21; import com.viaversion.viaversion.protocols.v1_20_5to1_21.data.AttributeModifierMappings1_21; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPacket1_21; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.SerializerVersion; import com.viaversion.viaversion.util.TagUtil; import com.viaversion.viaversion.util.UUIDUtil; import java.util.UUID; public final class ComponentRewriter1_21 extends JsonNBTComponentRewriter { public ComponentRewriter1_21(final Protocol1_21To1_20_5 protocol) { super(protocol, ReadType.NBT); } private void convertAttributeModifiersComponent(final CompoundTag tag) { final CompoundTag attributeModifiers = TagUtil.getNamespacedCompoundTag(tag, "attribute_modifiers"); if (attributeModifiers == null) { return; } final ListTag modifiers = attributeModifiers.getListTag("modifiers", CompoundTag.class); int size = modifiers.size(); for (int i = 0; i < size; i++) { final CompoundTag modifier = modifiers.get(i); final String type = Key.stripMinecraftNamespace(modifier.getString("type")); if (Attributes1_20_5.keyToId(type) == -1) { // Ignore new attributes modifiers.remove(i--); size--; continue; } final String id = modifier.getString("id"); final UUID uuid = Protocol1_20_5To1_21.mapAttributeId(id); final String name = AttributeModifierMappings1_21.idToName(id); modifier.put("uuid", new IntArrayTag(UUIDUtil.toIntArray(uuid))); modifier.putString("name", name != null ? name : id); } } @Override protected void handleShowItem(final UserConnection connection, final CompoundTag itemTag, final CompoundTag componentsTag) { super.handleShowItem(connection, itemTag, componentsTag); if (componentsTag != null) { removeDataComponents(componentsTag, StructuredDataKey.JUKEBOX_PLAYABLE1_21); convertAttributeModifiersComponent(componentsTag); } } @Override protected SerializerVersion inputSerializerVersion() { return SerializerVersion.V1_20_5; } @Override protected SerializerVersion outputSerializerVersion() { return SerializerVersion.V1_20_5; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21to1_20_5/rewriter/EntityPacketRewriter1_21.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21to1_20_5.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.Tag; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.Protocol1_21To1_20_5; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.EnchantmentsPaintingsStorage; import com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage.PlayerRotationStorage; import com.viaversion.viaversion.api.minecraft.Holder; import com.viaversion.viaversion.api.minecraft.PaintingVariant; import com.viaversion.viaversion.api.minecraft.RegistryEntry; import com.viaversion.viaversion.api.minecraft.WolfVariant; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_20_5; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_20_5; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_5to1_21.data.Paintings1_20_5; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundConfigurationPackets1_21; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPacket1_21; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.KeyMappings; import java.util.HashMap; import java.util.Map; public final class EntityPacketRewriter1_21 extends EntityRewriter { private final Map oldPaintings = new HashMap<>(); public EntityPacketRewriter1_21(final Protocol1_21To1_20_5 protocol) { super(protocol, protocol.mappedTypes().entityDataTypes().optionalComponentType, protocol.mappedTypes().entityDataTypes().booleanType); for (int i = 0; i < Paintings1_20_5.PAINTINGS.length; i++) { final PaintingVariant painting = Paintings1_20_5.PAINTINGS[i]; oldPaintings.put(painting.assetId(), new PaintingData(painting, i)); } } @Override public void registerPackets() { protocol.replaceClientbound(ClientboundConfigurationPackets1_21.REGISTRY_DATA, wrapper -> { final String key = Key.stripMinecraftNamespace(wrapper.passthrough(Types.STRING)); final RegistryEntry[] entries = wrapper.passthrough(Types.REGISTRY_ENTRY_ARRAY); final boolean paintingVariant = key.equals("painting_variant"); final boolean enchantment = key.equals("enchantment"); if (paintingVariant || enchantment || key.equals("jukebox_song")) { // Track custom registries and cancel the packet final String[] keys = new String[entries.length]; for (int i = 0; i < entries.length; i++) { keys[i] = Key.stripMinecraftNamespace(entries[i].key()); } final EnchantmentsPaintingsStorage storage = wrapper.user().get(EnchantmentsPaintingsStorage.class); if (paintingVariant) { storage.setPaintings(new KeyMappings(keys), paintingMappingsForEntries(entries)); } else if (enchantment) { final Tag[] descriptions = new Tag[entries.length]; final int[] maxLevels = new int[entries.length]; for (int i = 0; i < entries.length; i++) { final RegistryEntry entry = entries[i]; if (entry.tag() instanceof final CompoundTag tag) { descriptions[i] = tag.get("description"); maxLevels[i] = tag.getInt("max_level"); } } storage.setEnchantments(new KeyMappings(keys), descriptions, maxLevels); } else { final int[] jukeboxSongMappings = new int[keys.length]; for (int i = 0; i < keys.length; i++) { final int itemId = protocol.getMappingData().getFullItemMappings().mappedId("music_disc_" + keys[i]); jukeboxSongMappings[i] = itemId; } storage.setJubeboxSongsToItems(jukeboxSongMappings); } wrapper.cancel(); } else { protocol.getRegistryDataRewriter().trackDimensionAndBiomes(wrapper.user(), key, entries); } }); protocol.registerServerbound(ServerboundPackets1_20_5.MOVE_PLAYER_POS_ROT, wrapper -> { wrapper.passthrough(Types.DOUBLE); // X wrapper.passthrough(Types.DOUBLE); // Y wrapper.passthrough(Types.DOUBLE); // Z storePlayerRotation(wrapper); }); protocol.registerServerbound(ServerboundPackets1_20_5.MOVE_PLAYER_ROT, this::storePlayerRotation); } private void storePlayerRotation(final PacketWrapper wrapper) { final float yaw = wrapper.passthrough(Types.FLOAT); final float pitch = wrapper.passthrough(Types.FLOAT); wrapper.user().get(PlayerRotationStorage.class).setRotation(yaw, pitch); } private int[] paintingMappingsForEntries(final RegistryEntry[] entries) { final int[] mappings = new int[entries.length]; for (int i = 0; i < entries.length; i++) { final RegistryEntry entry = entries[i]; final PaintingData paintingData = oldPaintings.get(Key.stripMinecraftNamespace(entry.key())); if (paintingData != null) { mappings[i] = paintingData.id; continue; } // Figure out which works by size if (entry.tag() == null) { continue; } final CompoundTag tag = (CompoundTag) entry.tag(); for (int j = 0; j < Paintings1_20_5.PAINTINGS.length; j++) { final PaintingVariant painting = Paintings1_20_5.PAINTINGS[j]; if (painting.width() == tag.getInt("width") && painting.height() == tag.getInt("height")) { mappings[i] = j; break; } } } return mappings; } @Override protected void registerRewrites() { final EntityDataTypes1_20_5 mappedEntityDataTypes = VersionedTypes.V1_20_5.entityDataTypes; dataTypeMapper() .skip(VersionedTypes.V1_21.entityDataTypes.wolfVariantType) .skip(VersionedTypes.V1_21.entityDataTypes.paintingVariantType) .register(); filter().dataType(VersionedTypes.V1_21.entityDataTypes.wolfVariantType).handler((event, data) -> { final Holder variant = data.value(); if (variant.hasId()) { data.setTypeAndValue(mappedEntityDataTypes.wolfVariantType, variant.id()); } else { event.cancel(); } }); filter().dataType(VersionedTypes.V1_21.entityDataTypes.paintingVariantType).handler((event, data) -> { final Holder variant = data.value(); if (variant.hasId()) { final EnchantmentsPaintingsStorage storage = event.user().get(EnchantmentsPaintingsStorage.class); final int mappedId = storage.mappedPainting(variant.id()); data.setTypeAndValue(mappedEntityDataTypes.paintingVariantType, mappedId); } else { event.cancel(); } }); registerEntityDataTypeHandler1_20_3( mappedEntityDataTypes.itemType, mappedEntityDataTypes.blockStateType, mappedEntityDataTypes.optionalBlockStateType, mappedEntityDataTypes.particleType, mappedEntityDataTypes.particlesType, mappedEntityDataTypes.componentType, mappedEntityDataTypes.optionalComponentType ); registerBlockStateHandler(EntityTypes1_20_5.ABSTRACT_MINECART, 11); } @Override public EntityType typeFromId(final int type) { return EntityTypes1_20_5.getTypeFromId(type); } private record PaintingData(PaintingVariant painting, int id) { } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21to1_20_5/storage/EnchantmentsPaintingsStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage; import com.viaversion.nbt.tag.Tag; import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.util.KeyMappings; import org.checkerframework.checker.nullness.qual.Nullable; public final class EnchantmentsPaintingsStorage implements StorableObject { private KeyMappings enchantments; private KeyMappings paintings; private int[] paintingMappings; private Tag[] enchantmentDescriptions; private int[] enchantmentMaxLevels; private int[] jubeboxSongsToItems; public KeyMappings enchantments() { return enchantments; } public void setEnchantments(final KeyMappings enchantment, final Tag[] enchantmentDescriptions, final int[] enchantmentMaxLevels) { this.enchantments = enchantment; this.enchantmentDescriptions = enchantmentDescriptions; this.enchantmentMaxLevels = enchantmentMaxLevels; } public KeyMappings paintings() { return paintings; } public void setPaintings(final KeyMappings paintings, final int[] paintingMappings) { this.paintings = paintings; this.paintingMappings = paintingMappings; } public void setJubeboxSongsToItems(final int[] jubeboxSongsToItems) { this.jubeboxSongsToItems = jubeboxSongsToItems; } public int jubeboxSongToItem(final int id) { return id >= 0 && id < jubeboxSongsToItems.length ? jubeboxSongsToItems[id] : -1; } public int mappedPainting(final int id) { return id > 0 && id < paintingMappings.length ? paintingMappings[id] : 0; } public @Nullable Tag enchantmentDescription(final int id) { return id > 0 && id < enchantmentDescriptions.length ? enchantmentDescriptions[id] : null; } public int enchantmentMaxLevel(final int id) { return id > 0 && id < enchantmentMaxLevels.length ? enchantmentMaxLevels[id] : -1; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21to1_20_5/storage/OpenScreenStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage; import com.viaversion.viaversion.api.connection.StorableObject; public final class OpenScreenStorage implements StorableObject { private int menuType = -1; public int menuType() { return menuType; } public void setMenuType(final int menuType) { this.menuType = menuType; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_21to1_20_5/storage/PlayerRotationStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_21to1_20_5.storage; import com.viaversion.viaversion.api.connection.StorableObject; public final class PlayerRotationStorage implements StorableObject { private float yaw, pitch; public void setRotation(final float yaw, final float pitch) { this.yaw = yaw; this.pitch = pitch; } public float yaw() { return yaw; } public float pitch() { return pitch; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_9_1to1_9/Protocol1_9_1To1_9.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_9_1to1_9; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ClientboundPackets1_9; import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ServerboundPackets1_9; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; public class Protocol1_9_1To1_9 extends BackwardsProtocol { public Protocol1_9_1To1_9() { super(ClientboundPackets1_9.class, ClientboundPackets1_9.class, ServerboundPackets1_9.class, ServerboundPackets1_9.class); } @Override protected void registerPackets() { registerClientbound(ClientboundPackets1_9.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Player ID map(Types.UNSIGNED_BYTE); // 1 - Player Gamemode // 1.9.1 PRE 2 Changed this map(Types.INT, Types.BYTE); // 2 - Player Dimension map(Types.UNSIGNED_BYTE); // 3 - World Difficulty map(Types.UNSIGNED_BYTE); // 4 - Max Players (Tab) map(Types.STRING); // 5 - Level Type map(Types.BOOLEAN); // 6 - Reduced Debug info } }); registerClientbound(ClientboundPackets1_9.SOUND, new PacketHandlers() { @Override public void register() { map(Types.VAR_INT); // 0 - Sound ID handler(wrapper -> { int sound = wrapper.get(Types.VAR_INT, 0); if (sound == 415) // Stop the Elytra sound for 1.9 (It's introduced in 1.9.2) wrapper.cancel(); else if (sound >= 416) // Act like the Elytra sound never existed wrapper.set(Types.VAR_INT, 0, sound - 1); }); } }); JsonNBTComponentRewriter componentRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); componentRewriter.registerComponentPacket(ClientboundPackets1_9.CHAT); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_9_3to1_9_1/Protocol1_9_3To1_9_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_9_3to1_9_1; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.text.JsonNBTComponentRewriter; import com.viaversion.viabackwards.protocol.v1_9_3to1_9_1.data.BlockEntity1_9_1; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.ClientWorld; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_9_1; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_9_3; import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ClientboundPackets1_9; import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ServerboundPackets1_9; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ServerboundPackets1_9_3; import com.viaversion.viaversion.rewriter.text.ComponentRewriterBase; public class Protocol1_9_3To1_9_1 extends BackwardsProtocol { public Protocol1_9_3To1_9_1() { super(ClientboundPackets1_9_3.class, ClientboundPackets1_9.class, ServerboundPackets1_9_3.class, ServerboundPackets1_9.class); } @Override protected void registerPackets() { registerClientbound(ClientboundPackets1_9_3.BLOCK_ENTITY_DATA, new PacketHandlers() { @Override public void register() { map(Types.BLOCK_POSITION1_8); //Position map(Types.UNSIGNED_BYTE); //Type map(Types.NAMED_COMPOUND_TAG); //NBT handler(wrapper -> { if (wrapper.get(Types.UNSIGNED_BYTE, 0) == 9) { BlockPosition position = wrapper.get(Types.BLOCK_POSITION1_8, 0); CompoundTag tag = wrapper.get(Types.NAMED_COMPOUND_TAG, 0); wrapper.clearPacket(); //Clear the packet wrapper.setPacketType(ClientboundPackets1_9.UPDATE_SIGN); wrapper.write(Types.BLOCK_POSITION1_8, position); // Position for (int i = 0; i < 4; i++) { // Should technically be written as COMPONENT, but left as String for simplification/to remove redundant wrapping for VR StringTag textTag = tag.getStringTag("Text" + (i + 1)); String line = textTag != null ? textTag.getValue() : ""; wrapper.write(Types.STRING, line); // Sign line } } }); } }); registerClientbound(ClientboundPackets1_9_3.LEVEL_CHUNK, wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_9_3To1_9_1.class); ChunkType1_9_3 newType = ChunkType1_9_3.forEnvironment(clientWorld.getEnvironment()); ChunkType1_9_1 oldType = ChunkType1_9_1.forEnvironment(clientWorld.getEnvironment()); // Get the old type to not write Block Entities Chunk chunk = wrapper.read(newType); wrapper.write(oldType, chunk); BlockEntity1_9_1.handle(chunk.getBlockEntities(), wrapper.user()); }); registerClientbound(ClientboundPackets1_9_3.LOGIN, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Entity ID map(Types.UNSIGNED_BYTE); // 1 - Gamemode map(Types.INT); // 2 - Dimension handler(wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_9_3To1_9_1.class); int dimensionId = wrapper.get(Types.INT, 1); clientWorld.setEnvironment(dimensionId); }); } }); registerClientbound(ClientboundPackets1_9_3.RESPAWN, new PacketHandlers() { @Override public void register() { map(Types.INT); // 0 - Dimension ID handler(wrapper -> { ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_9_3To1_9_1.class); int dimensionId = wrapper.get(Types.INT, 0); clientWorld.setEnvironment(dimensionId); }); } }); JsonNBTComponentRewriter componentRewriter = new JsonNBTComponentRewriter<>(this, ComponentRewriterBase.ReadType.JSON); componentRewriter.registerComponentPacket(ClientboundPackets1_9_3.CHAT); } @Override public void init(UserConnection userConnection) { userConnection.addClientWorld(this.getClass(), new ClientWorld()); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v1_9_3to1_9_1/data/BlockEntity1_9_1.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v1_9_3to1_9_1.data; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.protocol.v1_9_3to1_9_1.Protocol1_9_3To1_9_1; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ClientboundPackets1_9_3; import java.util.HashMap; import java.util.List; import java.util.Map; public class BlockEntity1_9_1 { private static final Map TYPES = new HashMap<>(); static { TYPES.put("MobSpawner", 1); TYPES.put("Control", 2); TYPES.put("Beacon", 3); TYPES.put("Skull", 4); TYPES.put("FlowerPot", 5); TYPES.put("Banner", 6); TYPES.put("UNKNOWN", 7); TYPES.put("EndGateway", 8); TYPES.put("Sign", 9); } public static void handle(List tags, UserConnection connection) { for (CompoundTag tag : tags) { StringTag idTag = tag.getStringTag("id"); if (idTag == null) { continue; } String id = idTag.getValue(); if (!TYPES.containsKey(id)) { continue; } int newId = TYPES.get(id); if (newId == -1) { continue; } int x = tag.getNumberTag("x").asInt(); short y = tag.getNumberTag("y").asShort(); int z = tag.getNumberTag("z").asInt(); BlockPosition pos = new BlockPosition(x, y, z); updateBlockEntity(pos, (short) newId, tag, connection); } } private static void updateBlockEntity(BlockPosition pos, short id, CompoundTag tag, UserConnection connection) { PacketWrapper wrapper = PacketWrapper.create(ClientboundPackets1_9_3.BLOCK_ENTITY_DATA, null, connection); wrapper.write(Types.BLOCK_POSITION1_8, pos); wrapper.write(Types.UNSIGNED_BYTE, id); wrapper.write(Types.NAMED_COMPOUND_TAG, tag); wrapper.scheduleSend(Protocol1_9_3To1_9_1.class, false); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v26_1to1_21_11/Protocol26_1To1_21_11.java ================================================ /* * 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 . */ package com.viaversion.viabackwards.protocol.v26_1to1_21_11; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.rewriters.BackwardsRegistryRewriter; import com.viaversion.viabackwards.api.rewriters.text.NBTComponentRewriter; import com.viaversion.viabackwards.protocol.v26_1to1_21_11.rewriter.BlockItemPacketRewriter26_1; import com.viaversion.viabackwards.protocol.v26_1to1_21_11.rewriter.ComponentRewriter26_1; import com.viaversion.viabackwards.protocol.v26_1to1_21_11.rewriter.EntityPacketRewriter26_1; import com.viaversion.viabackwards.protocol.v26_1to1_21_11.storage.DayTimeStorage; import com.viaversion.viabackwards.protocol.v26_1to1_21_11.storage.GameModeStorage; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_21_11; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_11; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_11; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes26_1; 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; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_21_5; import com.viaversion.viaversion.api.type.types.chunk.ChunkType26_1; import com.viaversion.viaversion.api.type.types.version.Types1_20_5; import com.viaversion.viaversion.api.type.types.version.Types26_1; 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_11to26_1.Protocol1_21_11To26_1; import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPacket26_1; import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPackets26_1; import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ServerboundPacket26_1; import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ServerboundPackets26_1; import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.rewriter.RecipeDisplayRewriter1_21_5; import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPackets1_21_6; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ClientboundConfigurationPackets1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundConfigurationPackets1_21_9; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundPacket1_21_9; import com.viaversion.viaversion.protocols.v1_21_9to1_21_11.packet.ClientboundPacket1_21_11; import com.viaversion.viaversion.protocols.v1_21_9to1_21_11.packet.ClientboundPackets1_21_11; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ParticleRewriter; import com.viaversion.viaversion.rewriter.RecipeDisplayRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.block.BlockRewriter1_21_5; import com.viaversion.viaversion.util.Key; import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap; import static com.viaversion.viaversion.util.TagUtil.removeNamespaced; public final class Protocol26_1To1_21_11 extends BackwardsProtocol { public static final BackwardsMappingData MAPPINGS = new BackwardsMappingData("26.1", "1.21.11", Protocol1_21_11To26_1.class); private final EntityPacketRewriter26_1 entityRewriter = new EntityPacketRewriter26_1(this); private final BlockItemPacketRewriter26_1 itemRewriter = new BlockItemPacketRewriter26_1(this); private final ParticleRewriter particleRewriter = new ParticleRewriter<>(this); private final NBTComponentRewriter translatableRewriter = new ComponentRewriter26_1(this); private final TagRewriter tagRewriter = new TagRewriter<>(this); private final BackwardsRegistryRewriter registryDataRewriter = new BackwardsRegistryRewriter(this); private final BlockRewriter blockRewriter = new BlockRewriter1_21_5<>(this, ChunkType26_1::new, ChunkType1_21_5::new); private final RecipeDisplayRewriter recipeRewriter = new RecipeDisplayRewriter1_21_5<>(this); public Protocol26_1To1_21_11() { super(ClientboundPacket26_1.class, ClientboundPacket1_21_11.class, ServerboundPacket26_1.class, ServerboundPacket1_21_9.class); } @Override protected void registerPackets() { super.registerPackets(); // Remove new environment attributes registryDataRewriter.addHandler("dimension_type", (key, tag) -> { final CompoundTag attributes = tag.getCompoundTag("attributes"); if (attributes != null) { removeNamespaced(attributes, "visual/block_light_tint"); removeNamespaced(attributes, "visual/night_vision_color"); removeNamespaced(attributes, "visual/ambient_light_color"); } }); // Move around entity variant names and sounds registryDataRewriter.addHandler("wolf_sound_variant", (key, tag) -> { final CompoundTag sounds = tag.getCompoundTag("adult_sounds"); tag.remove("baby_sounds"); tag.putAll(sounds); }); registryDataRewriter.addHandler("frog_variant", (key, tag) -> swapEntityNameAffix("frog", tag)); registryDataRewriter.addHandler("chicken_variant", (key, tag) -> swapEntityNameAffix("chicken", tag)); registryDataRewriter.addHandler("cow_variant", (key, tag) -> swapEntityNameAffix("cow", tag)); registryDataRewriter.addHandler("pig_variant", (key, tag) -> swapEntityNameAffix("pig", tag)); registryDataRewriter.addHandler("cat_variant", (key, tag) -> removeEntityNamePrefix("cat", tag)); registryDataRewriter.remove("world_clock"); registryDataRewriter.remove("cat_sound_variant"); registryDataRewriter.remove("cow_sound_variant"); registryDataRewriter.remove("pig_sound_variant"); registryDataRewriter.remove("chicken_sound_variant"); tagRewriter.addEmptyTags(RegistryType.BLOCK, "big_dripleaf_placeable", "small_dripleaf_placeable", "mushroom_grow_block", "bamboo_plantable_on"); cancelClientbound(ClientboundPackets26_1.LOW_DISK_SPACE_WARNING); cancelClientbound(ClientboundPackets26_1.GAME_RULE_VALUES); registerClientbound(ClientboundPackets26_1.SET_TIME, wrapper -> { final long gameTime = wrapper.passthrough(Types.LONG); Long dayTime = null; boolean advanceTime = true; final int count = wrapper.read(Types.VAR_INT); for (int i = 0; i < count; i++) { final int clockType = wrapper.read(Types.VAR_INT); final long totalTicks = wrapper.read(Types.VAR_LONG); wrapper.read(Types.FLOAT); // Partial tick final float tickRate = wrapper.read(Types.FLOAT); if (Key.equals(registryDataRewriter.getMappings("world_clock").idToKey(clockType), "overworld")) { dayTime = totalTicks; advanceTime = tickRate != 0; } } final DayTimeStorage dayTimeStorage = wrapper.user().get(DayTimeStorage.class); if (dayTime == null) { // Determine from previously sent values based on the current game time dayTime = dayTimeStorage.setGameTimeAndUpdateDayTime(gameTime); advanceTime = dayTimeStorage.advanceTime(); } else { dayTimeStorage.setGameTime(gameTime); dayTimeStorage.setDayTime(dayTime); dayTimeStorage.setAdvanceTime(advanceTime); } wrapper.write(Types.LONG, dayTime); wrapper.write(Types.BOOLEAN, advanceTime); }); } private void removeEntityNamePrefix(final String key, final CompoundTag tag) { final StringTag assetIdTag = tag.getStringTag("asset_id"); final String assetId = assetIdTag.getValue(); assetIdTag.setValue(assetId.replace(key + "_", "")); } private void swapEntityNameAffix(final String key, final CompoundTag tag) { final StringTag assetIdTag = tag.getStringTag("asset_id"); final String assetId = assetIdTag.getValue(); if (assetId.contains(key + "_")) { assetIdTag.setValue(assetId.replace(key + "_", "") + "_" + key); } } @Override public void init(final UserConnection connection) { addEntityTracker(connection, new EntityTrackerBase(connection, EntityTypes1_21_11.PLAYER)); addItemHasher(connection, new ItemHasherBase(this, connection)); connection.put(new DayTimeStorage()); connection.put(new GameModeStorage()); } @Override public BackwardsMappingData getMappingData() { return MAPPINGS; } @Override public EntityPacketRewriter26_1 getEntityRewriter() { return entityRewriter; } @Override public BlockItemPacketRewriter26_1 getItemRewriter() { return itemRewriter; } @Override public BlockRewriter getBlockRewriter() { return blockRewriter; } @Override public BackwardsRegistryRewriter getRegistryDataRewriter() { return registryDataRewriter; } @Override public RecipeDisplayRewriter getRecipeRewriter() { return recipeRewriter; } @Override public ParticleRewriter getParticleRewriter() { return particleRewriter; } @Override public NBTComponentRewriter getComponentRewriter() { return translatableRewriter; } @Override public TagRewriter getTagRewriter() { return tagRewriter; } @Override public Types26_1 types() { return VersionedTypes.V26_1; } @Override public Types1_20_5 mappedTypes() { return VersionedTypes.V1_21_11; } @Override protected PacketTypesProvider createPacketTypesProvider() { return new SimplePacketTypesProvider<>( packetTypeMap(unmappedClientboundPacketType, ClientboundPackets26_1.class, ClientboundConfigurationPackets1_21_9.class), packetTypeMap(mappedClientboundPacketType, ClientboundPackets1_21_11.class, ClientboundConfigurationPackets1_21_9.class), packetTypeMap(mappedServerboundPacketType, ServerboundPackets26_1.class, ServerboundConfigurationPackets1_21_9.class), packetTypeMap(unmappedServerboundPacketType, ServerboundPackets1_21_6.class, ServerboundConfigurationPackets1_21_9.class) ); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v26_1to1_21_11/rewriter/BlockItemPacketRewriter26_1.java ================================================ /* * 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 . */ package com.viaversion.viabackwards.protocol.v26_1to1_21_11.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.rewriters.BackwardsStructuredItemRewriter; import com.viaversion.viabackwards.protocol.v26_1to1_21_11.Protocol26_1To1_21_11; 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.protocols.v1_21_11to26_1.packet.ClientboundPacket26_1; import com.viaversion.viaversion.protocols.v1_21_7to1_21_9.packet.ServerboundPacket1_21_9; import static com.viaversion.viaversion.protocols.v1_21_11to26_1.rewriter.BlockItemPacketRewriter26_1.downgradeData; import static com.viaversion.viaversion.protocols.v1_21_11to26_1.rewriter.BlockItemPacketRewriter26_1.upgradeData; public final class BlockItemPacketRewriter26_1 extends BackwardsStructuredItemRewriter { public BlockItemPacketRewriter26_1(final Protocol26_1To1_21_11 protocol) { super(protocol); } @Override protected void handleItemDataComponentsToClient(final UserConnection connection, final Item item, final StructuredDataContainer container) { super.handleItemDataComponentsToClient(connection, item, container); downgradeData(protocol.mappedTypes().structuredDataKeys(), container); } @Override protected void handleItemDataComponentsToServer(final UserConnection connection, final Item item, final StructuredDataContainer container) { super.handleItemDataComponentsToServer(connection, item, container); upgradeData(protocol, protocol.types().structuredDataKeys(), container); } @Override protected void restoreBackupData(final Item item, final StructuredDataContainer container, final CompoundTag customData) { super.restoreBackupData(item, container, customData); if (!(customData.remove(nbtTagName("backup")) instanceof final CompoundTag backupTag)) { return; } restoreIntData(StructuredDataKey.ADDITIONAL_TRADE_COST, container, backupTag); restoreIntData(StructuredDataKey.DYE, container, backupTag); restoreIntData(StructuredDataKey.CAT_SOUND_VARIANT, container, backupTag); restoreIntData(StructuredDataKey.CHICKEN_SOUND_VARIANT, container, backupTag); restoreIntData(StructuredDataKey.COW_SOUND_VARIANT, container, backupTag); restoreIntData(StructuredDataKey.PIG_SOUND_VARIANT, container, backupTag); } @Override protected void backupInconvertibleData(final UserConnection connection, final Item item, final StructuredDataContainer dataContainer, final CompoundTag backupTag) { super.backupInconvertibleData(connection, item, dataContainer, backupTag); saveIntData(StructuredDataKey.ADDITIONAL_TRADE_COST, dataContainer, backupTag); saveIntData(StructuredDataKey.DYE, dataContainer, backupTag); saveIntData(StructuredDataKey.CAT_SOUND_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.CHICKEN_SOUND_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.COW_SOUND_VARIANT, dataContainer, backupTag); saveIntData(StructuredDataKey.PIG_SOUND_VARIANT, dataContainer, backupTag); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v26_1to1_21_11/rewriter/ComponentRewriter26_1.java ================================================ /* * 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 . */ package com.viaversion.viabackwards.protocol.v26_1to1_21_11.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.text.NBTComponentRewriter; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPacket26_1; public final class ComponentRewriter26_1 extends NBTComponentRewriter { public ComponentRewriter26_1(final BackwardsProtocol protocol) { super(protocol); } @Override protected void handleShowItem(final UserConnection connection, final CompoundTag itemTag, final CompoundTag componentsTag) { super.handleShowItem(connection, itemTag, componentsTag); if (componentsTag == null) { return; } removeDataComponents(componentsTag, StructuredDataKey.ADDITIONAL_TRADE_COST, StructuredDataKey.DAMAGE_RESISTANT26_1, StructuredDataKey.BLOCKS_ATTACKS26_1, StructuredDataKey.PROVIDES_BANNER_PATTERNS26_1, StructuredDataKey.DYE, StructuredDataKey.CAT_SOUND_VARIANT, StructuredDataKey.CHICKEN_SOUND_VARIANT, StructuredDataKey.COW_SOUND_VARIANT, StructuredDataKey.PIG_SOUND_VARIANT); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v26_1to1_21_11/rewriter/EntityPacketRewriter26_1.java ================================================ /* * 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 . */ package com.viaversion.viabackwards.protocol.v26_1to1_21_11.rewriter; import com.viaversion.viabackwards.api.rewriters.EntityRewriter; import com.viaversion.viabackwards.protocol.v26_1to1_21_11.Protocol26_1To1_21_11; import com.viaversion.viabackwards.protocol.v26_1to1_21_11.storage.GameModeStorage; import com.viaversion.viaversion.api.minecraft.GameMode; import com.viaversion.viaversion.api.minecraft.Vector3d; import com.viaversion.viaversion.api.minecraft.entities.EntityType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_11; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_21_11; import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes26_1; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPacket26_1; import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPackets26_1; import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ServerboundPackets26_1; import com.viaversion.viaversion.protocols.v1_21_5to1_21_6.packet.ServerboundPackets1_21_6; public final class EntityPacketRewriter26_1 extends EntityRewriter { private static final int INTERACT_ACTION = 0; private static final int ATTACK_ACTION = 1; private static final int INTERACT_AT_ACTION = 2; public EntityPacketRewriter26_1(final Protocol26_1To1_21_11 protocol) { super(protocol, VersionedTypes.V1_21_11.entityDataTypes.optionalComponentType, VersionedTypes.V1_21_11.entityDataTypes.booleanType); } @Override public void registerPackets() { protocol.appendClientbound(ClientboundPackets26_1.RESPAWN, wrapper -> { final byte gamemode = wrapper.get(Types.BYTE, 0); wrapper.user().get(GameModeStorage.class).setGameMode(gamemode); }); protocol.appendClientbound(ClientboundPackets26_1.LOGIN, wrapper -> { final byte gamemode = wrapper.get(Types.BYTE, 0); wrapper.user().get(GameModeStorage.class).setGameMode(gamemode); }); protocol.registerServerbound(ServerboundPackets1_21_6.INTERACT, wrapper -> { wrapper.passthrough(Types.VAR_INT); // Entity ID final int action = wrapper.read(Types.VAR_INT); switch (action) { case INTERACT_ACTION -> wrapper.cancel(); // Drop "normal" interacts, as interact_at is always sent by Vanilla clients, and always sent first, with this following after case ATTACK_ACTION -> { final boolean spectator = wrapper.user().get(GameModeStorage.class).gameMode() == GameMode.SPECTATOR.id(); wrapper.setPacketType(spectator ? ServerboundPackets26_1.SPECTATE_ENTITY : ServerboundPackets26_1.ATTACK); wrapper.read(Types.BOOLEAN); // Using secondary action } case INTERACT_AT_ACTION -> { final float x = wrapper.read(Types.FLOAT); final float y = wrapper.read(Types.FLOAT); final float z = wrapper.read(Types.FLOAT); wrapper.passthrough(Types.VAR_INT); // Hand wrapper.write(Types.LOW_PRECISION_VECTOR, new Vector3d(x, y, z)); // Keep secondary action } default -> throw new IllegalArgumentException("Invalid interact action"); } }); } @Override protected void registerRewrites() { final EntityDataTypes26_1 entityDataTypes = VersionedTypes.V26_1.entityDataTypes; final EntityDataTypes1_21_11 mappedEntityDataTypes = VersionedTypes.V1_21_11.entityDataTypes; dataTypeMapper() .removed(entityDataTypes.catSoundVariant) .removed(entityDataTypes.cowSoundVariant) .removed(entityDataTypes.pigSoundVariant) .removed(entityDataTypes.chickenSoundVariant) .register(); registerEntityDataTypeHandler1_20_3( mappedEntityDataTypes.itemType, mappedEntityDataTypes.blockStateType, mappedEntityDataTypes.optionalBlockStateType, mappedEntityDataTypes.particleType, mappedEntityDataTypes.particlesType, mappedEntityDataTypes.componentType, mappedEntityDataTypes.optionalComponentType ); filter().type(EntityTypes1_21_11.ZOMBIE_VILLAGER).removeIndex(21); // Is villager data finalized filter().type(EntityTypes1_21_11.VILLAGER).removeIndex(20); // Is villager data finalized filter().type(EntityTypes1_21_11.CAT).removeIndex(24); // Sound variant filter().type(EntityTypes1_21_11.CHICKEN).removeIndex(19); // Sound variant filter().type(EntityTypes1_21_11.PIG).removeIndex(20); // Sound variant filter().type(EntityTypes1_21_11.COW).removeIndex(19); // Sound variant filter().type(EntityTypes1_21_11.TADPOLE).removeIndex(17); // Age locked filter().type(EntityTypes1_21_11.ABSTRACT_AGEABLE).removeIndex(17); // Age locked } @Override public EntityType typeFromId(final int type) { return EntityTypes1_21_11.getTypeFromId(type); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v26_1to1_21_11/storage/DayTimeStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v26_1to1_21_11.storage; import com.viaversion.viaversion.api.connection.StorableObject; public final class DayTimeStorage implements StorableObject { private long gameTime; private long dayTime; private boolean advanceTime; public long gameTime() { return gameTime; } public void setGameTime(final long gameTime) { this.gameTime = gameTime; } public long setGameTimeAndUpdateDayTime(final long gameTime) { this.dayTime += gameTime - this.gameTime; this.gameTime = gameTime; return dayTime; } public long dayTime() { return dayTime; } public void setDayTime(final long dayTime) { this.dayTime = dayTime; } public boolean advanceTime() { return advanceTime; } public void setAdvanceTime(final boolean advanceTime) { this.advanceTime = advanceTime; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/protocol/v26_1to1_21_11/storage/GameModeStorage.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.protocol.v26_1to1_21_11.storage; import com.viaversion.viaversion.api.connection.StorableObject; public final class GameModeStorage implements StorableObject { private int gameMode = -1; public int gameMode() { return gameMode; } public void setGameMode(final int gameMode) { this.gameMode = gameMode; } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/utils/BackwardsProtocolLogger.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.utils; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viaversion.api.protocol.Protocol; import com.viaversion.viaversion.util.ProtocolLogger; public final class BackwardsProtocolLogger extends ProtocolLogger { public BackwardsProtocolLogger(final Class protocol) { super(ViaBackwards.getPlatform().getLogger(), protocol); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/utils/ChatUtil.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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; import java.util.function.Consumer; import java.util.regex.Pattern; 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); 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); } public static String legacyToJsonString(String legacy, String translation, boolean itemData) { return legacyToJsonString(legacy, text -> { text.append(" "); text.append(new TranslationComponent(translation)); }, itemData); } public static String legacyToJsonString(String legacy, Consumer consumer, boolean itemData) { final TextComponent component = StringFormat.vanilla().fromString(legacy, ColorHandling.RESET, DeserializerUnknownHandling.WHITE); consumer.accept(component); if (itemData) { component.setParentStyle((new Style()).setItalic(false)); } return SerializerVersion.V1_12.toString(component); } private static class ChatFormattingState { private final Set formatting; private final char defaultColor; private char color; private ChatFormattingState(char defaultColor) { this(new HashSet<>(), defaultColor, defaultColor); } public ChatFormattingState(Set formatting, char defaultColor, char color) { this.formatting = formatting; this.defaultColor = defaultColor; this.color = color; } private void setColor(char newColor) { formatting.clear(); color = newColor; } public ChatFormattingState copy() { return new ChatFormattingState(new HashSet<>(formatting), defaultColor, color); } public void appendTo(StringBuilder builder) { builder.append('§').append(color); for (Character formatCharacter : formatting) { builder.append('§').append(formatCharacter); } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ChatFormattingState that = (ChatFormattingState) o; return defaultColor == that.defaultColor && color == that.color && Objects.equals(formatting, that.formatting); } @Override public int hashCode() { return Objects.hash(formatting, defaultColor, color); } public void processNextControlChar(char controlChar) { if (controlChar == 'r') { setColor(defaultColor); return; } if (controlChar == 'l' || controlChar == 'm' || controlChar == 'n' || controlChar == 'o') { formatting.add(controlChar); return; } setColor(controlChar); } } public static String fromLegacy(String legacy, char defaultColor, int limit) { return fromLegacy(legacy, defaultColor, limit, false); } public static String fromLegacyPrefix(String legacy, char defaultColor, int limit) { return fromLegacy(legacy, defaultColor, limit, true); } public static String fromLegacy(String legacy, char defaultColor, int limit, boolean isPrefix) { legacy = removeUnusedColor(legacy, defaultColor, isPrefix); if (legacy.length() > limit) legacy = legacy.substring(0, limit); if (legacy.endsWith("§")) legacy = legacy.substring(0, legacy.length() - 1); return legacy; } public static String removeUnusedColor(String legacy, char defaultColor, boolean isPrefix) { if (legacy == null) return null; Pattern pattern = isPrefix ? UNUSED_COLOR_PATTERN_PREFIX : UNUSED_COLOR_PATTERN; legacy = pattern.matcher(legacy).replaceAll("$1$2"); StringBuilder builder = new StringBuilder(); ChatFormattingState builderState = new ChatFormattingState(defaultColor); ChatFormattingState lastState = new ChatFormattingState(defaultColor); for (int i = 0; i < legacy.length(); i++) { char current = legacy.charAt(i); if (current != '§' || i == legacy.length() - 1) { if (!lastState.equals(builderState)) { lastState.appendTo(builder); builderState = lastState.copy(); } builder.append(current); continue; } current = legacy.charAt(++i); lastState.processNextControlChar(current); } if (isPrefix && !lastState.equals(builderState)) { lastState.appendTo(builder); } return builder.toString(); } } ================================================ FILE: common/src/main/java/com/viaversion/viabackwards/utils/VelocityUtil.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.utils; public class VelocityUtil { public static short toLegacyVelocity(double value) { return (short) Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, (long) (value * 8000))); } } ================================================ FILE: common/src/main/java-templates/com/viaversion/viabackwards/utils/VersionInfo.java ================================================ /* * 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 . */ package com.viaversion.viabackwards.utils; public final class VersionInfo { public static final String VERSION = "{{ version }}"; private static final String IMPLEMENTATION_VERSION = "{{ impl_version }}"; public static String getVersion() { return VERSION; } public static String getImplementationVersion() { return IMPLEMENTATION_VERSION; } } ================================================ FILE: common/src/main/resources/assets/viabackwards/config.yml ================================================ # If you need help, you can join our Discord - https://viaversion.com/discord # # Always shows a mapped mob's original name, and not only when hovering over it with the cursor. always-show-original-mob-name: true # # Writes name and level of custom enchantments into the item's lore. # Set this to false if you see the entries doubled/if the custom-enchant plugin already writes these into the lore manually. add-custom-enchants-into-lore: true # # Adds the color of a scoreboard team after its prefix for 1.12 clients on 1.13+ servers. add-teamcolor-to-prefix: true # # Converts the 1.13 face look-at packet for 1.12- players. Requires a bit of extra caching. fix-1_13-face-player: false # # Fixes 1.13 clients and lower not seeing color or formatting in inventory titles by converting them to legacy text. # If you have issues with translatable text being displayed wrongly, disable this. fix-formatted-inventory-titles: true # # Sends inventory acknowledgement packets to act as a replacement for ping packets for sub 1.17 clients. # This only takes effect for ids in the short range. Useful for anticheat compatibility. handle-pings-as-inv-acknowledgements: false # # Adds bedrock blocks at y=0 for sub 1.17 clients. This may allow for weird interactions due to sending fake blocks. bedrock-at-y-0: false # # Shows sculk shriekers as crying obsidian for 1.18.2 clients on 1.19+ servers. This fixes collision and block breaking issues. # If disabled, the client will see them as end portal frames. sculk-shriekers-to-crying-obsidian: true # # Shows scaffolding as water for 1.13.2 clients on 1.14+ servers. This fixes collision issues. # If disabled, the client will see them as hay blocks. scaffolding-to-water: false # # Maps the darkness effect to blindness for 1.18.2 clients on 1.19+ servers. map-darkness-effect: true # # If enabled, 1.21.3 clients will receive the first float of 1.21.4+ custom model data as int. Disable if you handle this change yourself. map-custom-model-data: true # # If enabled, 1.19.3 clients will receive display entities as armor stands with custom entity data on 1.19.4+ servers. Note that # this does not support all features display entities offer. map-display-entities: true # # Suppresses warnings of missing emulations for certain features that are not supported (e.g. world height in 1.17+). suppress-emulation-warnings: false # # If enabled, dialogs will be shown via chest inventories for 1.21.5 clients on 1.21.6+ servers. dialogs-via-chests: true # # Dialog styling. You can also use translation keys here. dialog-style: page-navigation-title: "&9&lPage navigation" page-navigation-next: "&9Left click: &6Go to next page" page-navigation-previous: "&9Right click: &6Go to previous page" toggle-value: "&9Left click: &6Toggle value" increase-value: "&9Left click: &6Increase value by %s" decrease-value: "&9Right click: &6Decrease value by %s" value-range: "&7(Value between &a%s &7and &a%s&7)" current-value: "&7Current value: &a%s" edit-value: "&9Left click: &6Edit text" next-option: "&9Left click: &6Go to next option" previous-option: "&9Right click: &6Go to previous option" set-text: "&9Left click/close: &6Set text" close: "&9Left click: &6Close" # # If enabled, the code of conduct will be shown as a dialog for 1.21.7 clients on 1.21.9+ servers. # Note that this is not supported for clients below 1.21.6 right now due to missing support for # dialogs during the configuration phase. code-of-conduct-as-dialog: true # # Passes the original vanilla 1.21.4+ item name into custom_model_data strings. # This allows client resource packs to restore the appearance of newer items entirely missing from this older version. # Disable if your server creates custom items using modern items as their base. # Tip: For server custom items, base them on items in the game before 1.21.4 (e.g. saddle) to ensure compatibility. pass-original-item-name-to-resource-packs: true ================================================ FILE: common/src/main/resources/assets/viabackwards/data/biome-mappings.json ================================================ { "nether_wastes": "nether", "soul_sand_valley": "nether", "crimson_forest": "nether", "warped_forest": "nether", "basalt_deltas": "nether", "dripstone_caves": "mountains", "lush_caves": "mountains", "meadow": "plains", "grove": "snowy_mountains", "snowy_slopes": "snowy_mountains", "frozen_peaks": "snowy_mountains", "jagged_peaks": "mountains", "stony_peaks": "mountains", "windswept_hills": "mountains", "snowy_plains": "snowy_tundra", "sparse_jungle": "jungle_edge", "stony_shore": "stone_shore", "old_growth_pine_taiga": "giant_spruce_taiga", "windswept_forest": "wooded_mountains", "wooded_badlands": "wooded_badlands_plateau", "windswept_gravelly_hills": "gravelly_mountains", "old_growth_birch_forest": "tall_birch_forest", "old_growth_spruce_taiga": "giant_spruce_taiga", "windswept_savanna": "shattered_savanna", "mangrove_swamp": "swamp", "deep_dark": "mountains", "cherry_grove": "flower_forest", "pale_garden": "gravelly_mountains" } ================================================ FILE: common/src/main/resources/assets/viabackwards/data/item-mappings-1.10.json ================================================ { "items": { "255": { "id": 217, "name": "1.10 Structure Block" } }, "block-items": { "217": { "id": 287, "name": "1.10 Structure Void" }, "213": { "id": 159, "data": 1, "name": "1.10 Magma Block" }, "214": { "id": 159, "data": 14, "name": "1.10 Nether Wart Block" }, "215": { "id": 112, "name": "1.10 Red Nether Brick" }, "216": { "id": 155, "name": "1.10 Bone Block" } } } ================================================ FILE: common/src/main/resources/assets/viabackwards/data/item-mappings-1.11.1.json ================================================ { "items": { "452": { "id": 265, "name": "1.11.1 Iron Nugget" } } } ================================================ FILE: common/src/main/resources/assets/viabackwards/data/item-mappings-1.11.json ================================================ { "items": { "449": { "id": 418, "name": "1.11 Totem of Undying" }, "450": { "id": 433, "name": "1.11 Shulker Shell" } }, "block-items": { "218": { "id": 23, "data": -1, "name": "1.11 Observer" }, "219-234": { "id": 158, "name": "1.11 %color% Shulker Box" } } } ================================================ FILE: common/src/main/resources/assets/viabackwards/data/item-mappings-1.12.json ================================================ { "items": { "453": { "id": 340, "name": "1.12 Knowledge Book" }, "355": { "id": 355, "name": "1.12 %vb_color% Bed" } }, "block-items": { "251": { "id": 159, "data": -1, "name": "1.12 %vb_color% Concrete" }, "252": { "id": 35, "data": -1, "name": "1.12 %vb_color% Concrete Powder" }, "235": { "id": 159, "data": 0, "name": "1.12 White Glazed Terracotta" }, "236": { "id": 159, "data": 1, "name": "1.12 Orange Glazed Terracotta" }, "237": { "id": 159, "data": 2, "name": "1.12 Magenta Glazed Terracotta" }, "238": { "id": 159, "data": 3, "name": "1.12 Light Blue Glazed Terracotta" }, "239": { "id": 159, "data": 4, "name": "1.12 Yellow Glazed Terracotta" }, "240": { "id": 159, "data": 5, "name": "1.12 Lime Glazed Terracotta" }, "241": { "id": 159, "data": 6, "name": "1.12 Pink Glazed Terracotta" }, "242": { "id": 159, "data": 7, "name": "1.12 Gray Glazed Terracotta" }, "243": { "id": 159, "data": 8, "name": "1.12 Light Gray Glazed Terracotta" }, "244": { "id": 159, "data": 9, "name": "1.12 Cyan Glazed Terracotta" }, "245": { "id": 159, "data": 10, "name": "1.12 Purple Glazed Terracotta" }, "246": { "id": 159, "data": 11, "name": "1.12 Blue Glazed Terracotta" }, "247": { "id": 159, "data": 12, "name": "1.12 Brown Glazed Terracotta" }, "248": { "id": 159, "data": 13, "name": "1.12 Green Glazed Terracotta" }, "249": { "id": 159, "data": 14, "name": "1.12 Red Glazed Terracotta" }, "250": { "id": 159, "data": 15, "name": "1.12 Black Glazed Terracotta" } } } ================================================ FILE: common/src/main/resources/assets/viabackwards/data/translation-mappings.json ================================================ { "26.1": { "block.minecraft.golden_dandelion": "Golden Dandelion", "block.minecraft.potted_golden_dandelion": "Potted Golden Dandelion", "build.tooLow": "Minimum height for building is %s", "chat_restriction.chat_and_commands_disabled_by_options": "Chat is restricted in client settings.", "chat_restriction.chat_disabled_by_options": "Player chat is restricted in client settings.", "chat_restriction.chat_disabled_by_options.action": "Go to the Chat Settings screen", "chat_restriction.disabled_by_launcher": "Chat is restricted by the launcher", "chat_restriction.disabled_by_profile": "Chat is restricted by profile settings.", "chat_restriction.disabled_by_profile.action": "Go to your profile settings", "chat_screen.commands_not_allowed": "Sending commands is not allowed", "chat_screen.messages_not_allowed": "Sending chat messages is not allowed", "chat_screen.restricted": "Chat is restricted. Click this message for details.", "chat_screen.restricted.narration": "Chat is restricted. Go to the Restrictions screen in World Options.", "command.trailing_data": "Trailing data found: %s", "commands.fetchprofile.entity.success": "Resolved profile for entity %s: %s", "commands.fetchprofile.no_profile": "Entity %s has no profile", "commands.swing.failed.notliving": "No living entities were found to swing", "commands.swing.success.multiple": "Made %s entities swing their arms", "commands.swing.success.single": "Made %s swing an arm", "commands.time.no_default_clock": "There is no default clock in dimension %s", "commands.time.no_time_marker_found": "Time marker %s does not exist for clock %s", "commands.time.pause": "Paused clock %s", "commands.time.query.absolute": "Clock %s is at %s tick(s)", "commands.time.query.gametime": "The game time is %s tick(s)", "commands.time.query.timeline": "Timeline %s is at %s tick(s)", "commands.time.query.timeline.repetitions": "Timeline %s has passed %s repetition(s)", "commands.time.resume": "Resumed clock %s", "commands.time.set.absolute": "Set %s to %s tick(s)", "commands.time.set.time_marker": "Set %s to time marker %s", "commands.time.wrong_timeline_for_clock": "Timeline %s is not valid for clock %s", "editGamerule.inGame.button": "Edit Game Rules...", "editGamerule.inGame.disabled.tooltip": "Updating game rules requires operator permissions.", "editGamerule.inGame.discardChanges.message": "Are you sure you want to discard your pending game rule changes?", "editGamerule.inGame.discardChanges.title": "Game rule Changes", "editGamerule.inGame.downloadingGamerules": "Retrieving game rules...", "gamerule.minecraft.universal_anger.description": "Angered neutral mobs attack any nearby player, not just the player that angered them. Works best if forgive_dead_players is disabled.", "gui.socialInteractions.tooltip.report.chat_disabled_or_blocked": "This player can't be reported because chat is disabled or blocked", "key.debug.lightmapTexture": "Lightmap Texture", "options.worldOptions.button": "World Options...", "restrictions_screen.button": "Restrictions...", "restrictions_screen.permission.receive_player_messages.allowed": "You can receive messages from players", "restrictions_screen.permission.receive_player_messages.denied": "You can't receive messages from players", "restrictions_screen.permission.receive_system_messages.allowed": "You can receive system messages from the server", "restrictions_screen.permission.receive_system_messages.denied": "You can't receive system messages from the server", "restrictions_screen.permission.send_commands.allowed": "You can send commands", "restrictions_screen.permission.send_commands.denied": "You can't send commands", "restrictions_screen.permission.send_messages.allowed": "You can send chat messages", "restrictions_screen.permission.send_messages.denied": "You can't send chat messages", "restrictions_screen.title": "Restrictions", "selecteWorld.backupRequiredTooltip": "Loading the world requires taking a backup first", "selectWorld.backupQuestion.file_fixing_required": "Create a backup before upgrading this world?", "selectWorld.backupWarning.file_fixing_required": "This world needs to be upgraded before you can play it. We strongly suggest creating a backup before continuing in case you experience world corruption.", "selectWorld.requiresFileFixingTooltip.edit": "This world needs to be upgraded to the latest version before you can edit it due to underlying changes to the world format.", "selectWorld.requiresFileFixingTooltip.play": "This world needs to be upgraded to the latest version before you can play it due to underlying changes to the world format.", "selectWorld.requiresFileFixingTooltip.recreate": "This world needs to be upgraded to the latest version before you can recreate it due to underlying changes to the world format.", "selectWorld.upgrade_and_play": "Upgrade and Play", "selectWorld.waitingForBackup.message": "Depending on the size of your world, this may take a while. Please do not close the game or shut off your device.", "selectWorld.waitingForBackup.title": "Creating Backup", "selectWorld.world_gen_settings_access": "Unable to read or access the world gen settings file! %s", "subtitles.entity.baby_cat.ambient": "Kitten meows", "subtitles.entity.baby_cat.beg_for_food": "Kitten begs", "subtitles.entity.baby_cat.death": "Kitten dies", "subtitles.entity.baby_cat.eat": "Kitten eats", "subtitles.entity.baby_cat.hiss": "Kitten hisses", "subtitles.entity.baby_cat.hurt": "Kitten hurts", "subtitles.entity.baby_cat.purr": "Kitten purrs", "subtitles.entity.baby_chicken.ambient": "Chick peeps", "subtitles.entity.baby_chicken.death": "Chick dies", "subtitles.entity.baby_chicken.hurts": "Chick hurts", "subtitles.entity.baby_horse.ambient": "Foal neighs", "subtitles.entity.baby_horse.angry": "Foal neighs", "subtitles.entity.baby_horse.breathe": "Foal breathes", "subtitles.entity.baby_horse.death": "Foal dies", "subtitles.entity.baby_horse.eat": "Foal eats", "subtitles.entity.baby_horse.hurt": "Foal hurts", "subtitles.entity.baby_horse.land": "Foal lands", "subtitles.entity.baby_pig.ambient": "Baby Pig oinks", "subtitles.entity.baby_pig.death": "Baby Pig dies", "subtitles.entity.baby_pig.eat": "Baby Pig eats", "subtitles.entity.baby_pig.hurt": "Baby Pig hurts", "subtitles.entity.baby_wolf.ambient": "Puppy yips", "subtitles.entity.baby_wolf.death": "Puppy dies", "subtitles.entity.baby_wolf.growl": "Puppy growls", "subtitles.entity.baby_wolf.hurt": "Puppy hurts", "subtitles.entity.baby_wolf.pant": "Puppy pants", "subtitles.entity.baby_wolf.whine": "Puppy whines", "subtitles.entity.pig.eat": "Pig eats", "subtitles.item.golden_dandelion.unuse": "Golden Dandelion starts aging", "subtitles.item.golden_dandelion.use": "Golden Dandelion halts aging", "test.error.expected_block_present": "Expected block %s to be present", "test.error.sequence.minimum_tick": "Succeeded before expected tick: expected to wait %s", "upgradeWorld.aborted.message": "This may happen if another program accessed the world during the upgrade.\nIt may help to restart your computer and try again. If the issue persists, please consider reporting a bug.", "upgradeWorld.aborted.title": "Failed to upgrade the world", "upgradeWorld.canceled.message": "Your world remains as it was before.", "upgradeWorld.canceled.title": "World upgrade canceled", "upgradeWorld.done": "Upgrading World Completed", "upgradeWorld.failed_cleanup.message": "The world was successfully upgraded to the current version and is now available in the world list.\nWe encountered issues when cleaning up outdated data, therefore the name of the world folder has changed to %s.", "upgradeWorld.failed_cleanup.title": "Successfully upgraded the world", "upgradeWorld.info.converted": "Upgraded: %s", "upgradeWorld.info.file_fix_stage": "Stage: %s/%s", "upgradeWorld.info.scanning": "Scanning files...", "upgradeWorld.info.total": "Total: %s", "upgradeWorld.joinNow": "Do you want to join the world now?", "upgradeWorld.progress.type.files": "Moving files", "upgradeWorld.progress.type.legacy_structures": "Upgrading legacy structures", "upgradeWorld.progress.type.region": "Upgrading regions", "upgradeWorld.symlink.message": "The selected world contains symbolic links.\nThe required steps to upgrade the world to the current version do not support the use of symbolic links. To upgrade the world, the links need to be resolved first.", "upgradeWorld.symlink.title": "Can't upgrade the world", "upgradeWorld.title": "Upgrading World Files" }, "1.21.11": { "advancements.adventure.spear_many_mobs.description": "Hit five mobs in the same Charge attack using the Spear", "advancements.adventure.spear_many_mobs.title": "Mob Kabob", "advMode.setCommand.disabled": "Command set: %s, but command blocks are still disabled", "chat.queue.tooltip": "Click to display next message", "commands.stopwatch.already_exists": "Stopwatch '%s' already exists", "commands.stopwatch.create.success": "Created Stopwatch '%s'", "commands.stopwatch.does_not_exist": "Stopwatch '%s' does not exist", "commands.stopwatch.query": "Stopwatch '%s' has run for %ss", "commands.stopwatch.remove.success": "Removed Stopwatch '%s'", "commands.stopwatch.restart.success": "Restarted Stopwatch '%s'", "death.attack.spear": "%1$s was speared by %2$s", "death.attack.spear.item": "%1$s was speared by %2$s using %3$s", "debug.crash.message.rebindable": "%s + %s is held down. This will crash the game unless released.", "debug.entry.currently.inOverlay": "%s: Currently only in debug overlay", "debug.entry.overlay": "In Overlay", "debug.profiling.start.rebindable": "Profiling started for %s seconds. Use %s + %s to stop early", "effect.minecraft.breath_of_the_nautilus": "Breath of the Nautilus", "enchantment.minecraft.lunge": "Lunge", "entity.minecraft.camel_husk": "Camel Husk", "entity.minecraft.nautilus": "Nautilus", "entity.minecraft.parched": "Parched", "entity.minecraft.zombie_nautilus": "Zombie Nautilus", "gamerule.minecraft.elytra_movement_check": "Do elytra movement check", "gamerule.minecraft.fire_spread_radius_around_player": "Fire spread radius", "gamerule.minecraft.fire_spread_radius_around_player.description": "The radius in blocks around a player in which fire can spread", "gamerule.minecraft.player_movement_check": "Do player movement check", "gamerule.minecraft.raids": "Do raids", "item.intangible": "Intangible", "item.minecraft.camel_husk_spawn_egg": "Camel Husk Spawn Egg", "item.minecraft.copper_nautilus_armor": "Copper Nautilus Armor", "item.minecraft.copper_spear": "Copper Spear", "item.minecraft.diamond_nautilus_armor": "Diamond Nautilus Armor", "item.minecraft.diamond_spear": "Diamond Spear", "item.minecraft.golden_nautilus_armor": "Golden Nautilus Armor", "item.minecraft.golden_spear": "Golden Spear", "item.minecraft.iron_nautilus_armor": "Iron Nautilus Armor", "item.minecraft.iron_spear": "Iron Spear", "item.minecraft.nautilus_spawn_egg": "Nautilus Spawn Egg", "item.minecraft.netherite_horse_armor": "Netherite Horse Armor", "item.minecraft.netherite_nautilus_armor": "Netherite Nautilus Armor", "item.minecraft.netherite_spear": "Netherite Spear", "item.minecraft.parched_spawn_egg": "Parched Spawn Egg", "item.minecraft.stone_spear": "Stone Spear", "item.minecraft.wooden_spear": "Wooden Spear", "item.minecraft.zombie_nautilus_spawn_egg": "Zombie Nautilus Spawn Egg", "key.category.minecraft.debug": "Debug", "key.debug.clearChat": "Clear Chat", "key.debug.copyLocation": "Copy Location", "key.debug.copyRecreateCommand": "Copy Data", "key.debug.crash": "Debug Crash", "key.debug.dumpDynamicTextures": "Dump Dynamic Textures", "key.debug.dumpVersion": "Dump Version Info", "key.debug.focusPause": "Toggle Lost Focus Pause", "key.debug.fpsCharts": "FPS Charts", "key.debug.modifier": "Debug Modifier Key", "key.debug.networkCharts": "Network Charts", "key.debug.overlay": "Toggle Overlay", "key.debug.profiling": "Start/Stop Profiling", "key.debug.profilingChart": "Profiling Chart", "key.debug.reloadChunk": "Reload Chunks", "key.debug.reloadResourcePacks": "Reload Resource Packs", "key.debug.showAdvancedTooltips": "Show Advanced Tooltips", "key.debug.showChunkBorders": "Show Chunk Boundaries", "key.debug.showHitboxes": "Show Hitboxes", "key.debug.spectate": "Cycle Spectator", "key.debug.switchGameMode": "Game Mode Switcher", "key.toggleGui": "Toggle GUI", "key.toggleSpectatorShaderEffects": "Toggle Spectator Shader Effects", "mco.backup.entry.worldType.adventureMap": "Adventure Map", "mco.backup.entry.worldType.experience": "Experience", "mco.configure.world.name.validation.whitespace": "Must not start or end with whitespace. It will be trimmed.", "narration.checkbox.usage.focused.check": "Press Enter to check", "narration.checkbox.usage.focused.uncheck": "Press Enter to uncheck", "narration.checkbox.usage.hovered.check": "Left click to check", "narration.checkbox.usage.hovered.uncheck": "Left click to uncheck", "options.blocks": "%s Blocks", "options.chunkFade": "Chunk Fade Time", "options.chunkFade.none": "Chunk Fade: None", "options.chunkFade.seconds": "Chunk Fade: %s second(s)", "options.chunkFade.tooltip": "How long in seconds chunks should fade in when they're first rendered, if at all.", "options.cutoutLeaves": "See-Through Leaves", "options.cutoutLeaves.tooltip": "Allows you to see through gaps in leaves. Disabling improves performance.", "options.graphics.preset": "Preset", "options.graphics.preset.tooltip": "Sets \"Quality & Performance\" settings to reasonable defaults corresponding to the desired quality.", "options.improvedTransparency": "Improved Transparency", "options.improvedTransparency.tooltip": "An experimental approach that uses screen shaders for drawing weather, clouds, and particles behind translucent blocks and water.\nThis will impact GPU performance.", "options.maxAnisotropy": "Anisotropic Filtering", "options.maxAnisotropy.tooltip": "Each level significantly improves how smooth textures look, but impacts performance and significantly impacts video memory usage. Requires Texture Filtering to be set to Anisotropic.", "options.musicToast": "Music Toast", "options.musicToast.never.tooltip": "No music toast is shown.", "options.musicToast.pauseMenu": "Pause Menu", "options.musicToast.pauseMenu.tooltip": "A music toast is constantly displayed in the in-game pause menu while a song is playing.", "options.musicToast.pauseMenuAndToast": "Pause Menu and Toast", "options.musicToast.pauseMenuAndToast.tooltip": "Displays a toast when a song starts playing. The same toast is constantly displayed in the in-game pause menu while a song is playing.", "options.textureFiltering": "Texture Filtering", "options.textureFiltering.anisotropic": "Anisotropic", "options.textureFiltering.anisotropic.tooltip": "A hardware based filtering method, but impacts performance and significantly impacts video memory usage. May not be supported on all hardware.", "options.textureFiltering.none.tooltip": "Textures are displayed without any filtering. Blocks may look blurry when viewed at an angle.", "options.textureFiltering.rgss": "RGSS", "options.textureFiltering.rgss.tooltip": "(Rotated Grid Super Sampling)\nA shader based filtering method that improves texture quality with a moderate performance impact.", "options.video.display.header": "Display", "options.video.interface.header": "Interface", "options.video.preferences.header": "Preferences", "options.video.quality.header": "Quality & Performance", "options.vignette": "Show Vignette", "options.vignette.tooltip": "This is a subtle texture over the game screen used for reducing brightness towards the edges of the screen and warning about the world border.", "options.weatherRadius": "Weather Effect Radius", "options.weatherRadius.tooltip": "Radius of the area where rain and snow effects are visible. Very low performance impact.", "stat.minecraft.nautilus_one_cm": "Distance by Nautilus", "subtitles.entity.baby_nautilus.ambient": "Baby Nautilus chitters", "subtitles.entity.baby_nautilus.ambient_land": "Baby Nautilus chitters", "subtitles.entity.baby_nautilus.death": "Baby Nautilus dies", "subtitles.entity.baby_nautilus.death_land": "Baby Nautilus dies", "subtitles.entity.baby_nautilus.eat": "Baby Nautilus eats", "subtitles.entity.baby_nautilus.hurt": "Baby Nautilus hurts", "subtitles.entity.baby_nautilus.hurt_land": "Baby Nautilus hurts", "subtitles.entity.baby_nautilus.swim": "Baby Nautilus swims", "subtitles.entity.camel_husk.ambient": "Camel Husk grumphs", "subtitles.entity.camel_husk.dash": "Camel Husk yeets", "subtitles.entity.camel_husk.dash_ready": "Camel Husk recovers", "subtitles.entity.camel_husk.death": "Camel Husk dies", "subtitles.entity.camel_husk.eat": "Camel Husk eats", "subtitles.entity.camel_husk.hurt": "Camel Husk hurts", "subtitles.entity.camel_husk.sit": "Camel Husk sits down", "subtitles.entity.camel_husk.stand": "Camel Husk stands up", "subtitles.entity.nautilus.ambient": "Nautilus clacks", "subtitles.entity.nautilus.ambient_land": "Nautilus clacks", "subtitles.entity.nautilus.dash": "Nautilus jets", "subtitles.entity.nautilus.dash_land": "Nautilus jets", "subtitles.entity.nautilus.dash_ready": "Nautilus recovers", "subtitles.entity.nautilus.dash_ready_land": "Nautilus recovers", "subtitles.entity.nautilus.death": "Nautilus dies", "subtitles.entity.nautilus.death_land": "Nautilus dies", "subtitles.entity.nautilus.eat": "Nautilus eats", "subtitles.entity.nautilus.hurt": "Nautilus hurts", "subtitles.entity.nautilus.hurt_land": "Nautilus hurts", "subtitles.entity.nautilus.swim": "Nautilus swims", "subtitles.entity.parched.ambient": "Parched crackles", "subtitles.entity.parched.death": "Parched dies", "subtitles.entity.parched.hurt": "Parched hurts", "subtitles.entity.parrot.imitate.camel_husk": "Parrot grumphs", "subtitles.entity.parrot.imitate.parched": "Parrot crackles", "subtitles.entity.parrot.imitate.zombie_nautilus": "Parrot gargles", "subtitles.entity.zombie_horse.angry": "Zombie Horse neighs", "subtitles.entity.zombie_horse.eat": "Zombie Horse eats", "subtitles.entity.zombie_nautilus.ambient": "Zombie Nautilus burbles", "subtitles.entity.zombie_nautilus.ambient_land": "Zombie Nautilus burbles", "subtitles.entity.zombie_nautilus.dash": "Zombie Nautilus jets", "subtitles.entity.zombie_nautilus.dash_land": "Zombie Nautilus jets", "subtitles.entity.zombie_nautilus.dash_ready": "Zombie Nautilus recovers", "subtitles.entity.zombie_nautilus.dash_ready_land": "Zombie Nautilus recovers", "subtitles.entity.zombie_nautilus.death": "Zombie Nautilus dies", "subtitles.entity.zombie_nautilus.death_land": "Zombie Nautilus dies", "subtitles.entity.zombie_nautilus.eat": "Zombie Nautilus eats", "subtitles.entity.zombie_nautilus.hurt": "Zombie Nautilus hurts", "subtitles.entity.zombie_nautilus.hurt_land": "Zombie Nautilus hurts", "subtitles.entity.zombie_nautilus.swim": "Zombie Nautilus swims", "subtitles.item.armor.equip_nautilus": "Nautilus Armor equips", "subtitles.item.armor.unequip_nautilus": "Nautilus Armor unequips", "subtitles.item.spear_wood.attack": "Spear jabs", "subtitles.item.spear_wood.hit": "Spear hits", "subtitles.item.spear_wood.use": "Charges with Spear", "subtitles.item.spear.attack": "Spear jabs", "subtitles.item.spear.hit": "Spear hits", "subtitles.item.spear.lunge": "Spear lunges", "subtitles.item.spear.use": "Charges with Spear" }, "1.21.9": { "advMode.notEnabled.spawner": "Spawner blocks are not enabled", "block.minecraft.acacia_shelf": "Acacia Shelf", "block.minecraft.bamboo_shelf": "Bamboo Shelf", "block.minecraft.birch_shelf": "Birch Shelf", "block.minecraft.cherry_shelf": "Cherry Shelf", "block.minecraft.copper_bars": "Copper Bars", "block.minecraft.copper_chain": "Copper Chain", "block.minecraft.copper_chest": "Copper Chest", "block.minecraft.copper_golem_statue": "Copper Golem Statue", "block.minecraft.copper_lantern": "Copper Lantern", "block.minecraft.copper_torch": "Copper Torch", "block.minecraft.copper_wall_torch": "Copper Wall Torch", "block.minecraft.crimson_shelf": "Crimson Shelf", "block.minecraft.dark_oak_shelf": "Dark Oak Shelf", "block.minecraft.exposed_copper_bars": "Exposed Copper Bars", "block.minecraft.exposed_copper_chain": "Exposed Copper Chain", "block.minecraft.exposed_copper_chest": "Exposed Copper Chest", "block.minecraft.exposed_copper_golem_statue": "Exposed Copper Golem Statue", "block.minecraft.exposed_copper_lantern": "Exposed Copper Lantern", "block.minecraft.exposed_lightning_rod": "Exposed Lightning Rod", "block.minecraft.iron_chain": "Iron Chain", "block.minecraft.jungle_shelf": "Jungle Shelf", "block.minecraft.mangrove_shelf": "Mangrove Shelf", "block.minecraft.oak_shelf": "Oak Shelf", "block.minecraft.oxidized_copper_bars": "Oxidized Copper Bars", "block.minecraft.oxidized_copper_chain": "Oxidized Copper Chain", "block.minecraft.oxidized_copper_chest": "Oxidized Copper Chest", "block.minecraft.oxidized_copper_golem_statue": "Oxidized Copper Golem Statue", "block.minecraft.oxidized_copper_lantern": "Oxidized Copper Lantern", "block.minecraft.oxidized_lightning_rod": "Oxidized Lightning Rod", "block.minecraft.pale_oak_shelf": "Pale Oak Shelf", "block.minecraft.spruce_shelf": "Spruce Shelf", "block.minecraft.warped_shelf": "Warped Shelf", "block.minecraft.waxed_copper_bars": "Waxed Copper Bars", "block.minecraft.waxed_copper_chain": "Waxed Copper Chain", "block.minecraft.waxed_copper_chest": "Waxed Copper Chest", "block.minecraft.waxed_copper_golem_statue": "Waxed Copper Golem Statue", "block.minecraft.waxed_copper_lantern": "Waxed Copper Lantern", "block.minecraft.waxed_exposed_copper_bars": "Waxed Exposed Copper Bars", "block.minecraft.waxed_exposed_copper_chain": "Waxed Exposed Copper Chain", "block.minecraft.waxed_exposed_copper_chest": "Waxed Exposed Copper Chest", "block.minecraft.waxed_exposed_copper_golem_statue": "Waxed Exposed Copper Golem Statue", "block.minecraft.waxed_exposed_copper_lantern": "Waxed Exposed Copper Lantern", "block.minecraft.waxed_exposed_lightning_rod": "Waxed Exposed Lightning Rod", "block.minecraft.waxed_lightning_rod": "Waxed Lightning Rod", "block.minecraft.waxed_oxidized_copper_bars": "Waxed Oxidized Copper Bars", "block.minecraft.waxed_oxidized_copper_chain": "Waxed Oxidized Copper Chain", "block.minecraft.waxed_oxidized_copper_chest": "Waxed Oxidized Copper Chest", "block.minecraft.waxed_oxidized_copper_golem_statue": "Waxed Oxidized Copper Golem Statue", "block.minecraft.waxed_oxidized_copper_lantern": "Waxed Oxidized Copper Lantern", "block.minecraft.waxed_oxidized_lightning_rod": "Waxed Oxidized Lightning Rod", "block.minecraft.waxed_weathered_copper_bars": "Waxed Weathered Copper Bars", "block.minecraft.waxed_weathered_copper_chain": "Waxed Weathered Copper Chain", "block.minecraft.waxed_weathered_copper_chest": "Waxed Weathered Copper Chest", "block.minecraft.waxed_weathered_copper_golem_statue": "Waxed Weathered Copper Golem Statue", "block.minecraft.waxed_weathered_copper_lantern": "Waxed Weathered Copper Lantern", "block.minecraft.waxed_weathered_lightning_rod": "Waxed Weathered Lightning Rod", "block.minecraft.weathered_copper_bars": "Weathered Copper Bars", "block.minecraft.weathered_copper_chain": "Weathered Copper Chain", "block.minecraft.weathered_copper_chest": "Weathered Copper Chest", "block.minecraft.weathered_copper_golem_statue": "Weathered Copper Golem Statue", "block.minecraft.weathered_copper_lantern": "Weathered Copper Lantern", "block.minecraft.weathered_lightning_rod": "Weathered Lightning Rod", "commands.fetchprofile.copy_component": "Copy Component", "commands.fetchprofile.copy_text": "Copy %s", "commands.fetchprofile.failed_to_serialize": "Failed to serialize profile: %s", "commands.fetchprofile.give_item": "Give Item", "commands.fetchprofile.id.failure": "Failed to resolve profile for ID %s", "commands.fetchprofile.id.success": "Resolved profile for ID %s: %s", "commands.fetchprofile.name.failure": "Failed to resolve profile for name %s", "commands.fetchprofile.name.success": "Resolved profile for name %s: %s", "commands.fetchprofile.summon_mannequin": "Summon Mannequin", "commands.profile_fetch.copy_component": "Copy Component", "commands.profile_fetch.failed_to_serialize": "Failed to serialize profile: %s", "commands.profile_fetch.give_item": "Give Item", "commands.profile_fetch.id.failure": "Failed to resolved profile for id %s", "commands.profile_fetch.id.success": "Resolved profile for id %s: %s", "commands.profile_fetch.name.failure": "Failed to resolve profile for name %s", "commands.profile_fetch.name.success": "Resolved profile for name %s: %s", "commands.setworldspawn.success.new": "Set the world spawn point to %s, %s, %s [%s, %s] in %s", "commands.spawnpoint.success.multiple.new": "Set spawn point to %s, %s, %s [%s, %s] in %s for %s players", "commands.spawnpoint.success.single.new": "Set spawn point to %s, %s, %s [%s, %s] in %s for %s", "commands.spectate.cannot_spectate": "%s cannot be spectated", "commands.summon.failed.peaceful": "Monsters cannot be summoned in Peaceful difficulty", "component.profile.dynamic": "Dynamic", "debug.entry.currently.alwaysOn": "%s: Currently always on", "debug.entry.currently.inF3": "%s: Currently only in F3", "debug.entry.currently.never": "%s: Currently off", "debug.entry.f3": "In F3", "debug.options.category.renderer": "Debug Renderers", "debug.options.category.text": "Debug Screen Text", "debug.options.help": "F3 + F6 = Edit debug options", "debug.options.notAllowed.tooltip": "Not visible when debug info is reduced", "debug.options.profile.default": "Default profile", "debug.options.profile.performance": "Performance profile", "debug.options.title": "Debug Options", "debug.options.warning": "These options are for testing purposes only. They may slow down your computer, crash the game, or eat your pet rock.", "entity.minecraft.copper_golem": "Copper Golem", "entity.minecraft.mannequin": "Mannequin", "entity.minecraft.mannequin.label": "NPC", "gamerule.allowEnteringNetherUsingPortals": "Allow Nether", "gamerule.allowEnteringNetherUsingPortals.description": "Controls whether players are allowed to enter the Nether.", "gamerule.commandBlocksEnabled": "Enable Command Blocks", "gamerule.enableCommandBlocks": "Enable Command Blocks", "gamerule.pvp": "Enable pvp", "gamerule.pvp.description": "Controls whether players are allowed to damage other players.", "gamerule.spawnerBlocksEnabled": "Enable Spawner Blocks", "gamerule.spawnMonsters.description": "Controls whether monsters naturally spawn.", "gui.stats.none_found": "No statistics found.", "item.minecraft.copper_axe": "Copper Axe", "item.minecraft.copper_boots": "Copper Boots", "item.minecraft.copper_chestplate": "Copper Chestplate", "item.minecraft.copper_golem_spawn_egg": "Copper Golem Spawn Egg", "item.minecraft.copper_helmet": "Copper Helmet", "item.minecraft.copper_hoe": "Copper Hoe", "item.minecraft.copper_horse_armor": "Copper Horse Armor", "item.minecraft.copper_leggings": "Copper Leggings", "item.minecraft.copper_nugget": "Copper Nugget", "item.minecraft.copper_pickaxe": "Copper Pickaxe", "item.minecraft.copper_shovel": "Copper Shovel", "item.minecraft.copper_sword": "Copper Sword", "item.spawn_egg.peaceful": "Disabled in Peaceful", "key.spectatorHotbar": "Select On Hotbar", "mco.configure.world.players.invite.duplicate": "A player with the provided name has already been invited to the Realm", "multiplayer.codeOfConduct.check": "Do not notify again for this Code of Conduct", "multiplayer.codeOfConduct.title": "Server Code of Conduct", "multiplayer.confirm_command.run_command": "Run Command", "multiplayer.confirm_command.signature_required": "You are trying to execute a command that will send chat messages using your name.\nIt can only be run from the chat screen\nCommand: %s", "multiplayer.confirm_command.suggest_command": "Copy to Chat Screen", "multiplayer.disconnect.banned.reason.default": "Banned by an operator.", "multiplayer.disconnect.code_of_conduct": "Server requires accepting the Code of Conduct", "multiplayer.disconnect.configuration_error": "Unexpected error during configuration", "multiplayer.status.anonymous_player": "Anonymous Player", "narration.slider.usage.focused.keyboard_cannot_change_value": "Press Enter to start changing the slider value", "options.allowCursorChanges": "Allow Cursor Changes", "options.allowCursorChanges.tooltip": "Allows the mouse cursor to change shape when over certain UI elements.", "options.chat.drafts": "Save Unsent Chats", "options.chat.drafts.tooltip": "Unsent messages will be saved and can be sent the next time chat is opened.", "options.invertMouseX": "Invert Mouse X", "options.invertMouseY": "Invert Mouse Y", "options.showSubtitles.tooltip": "Enables captions for sounds played in the game.", "options.sprintWindow": "Sprint Window", "options.sprintWindow.tooltip": "Time window in ticks where double-tapping the forward key activates sprint.", "pack.incompatible.confirm.unknown": "This pack is broken or made for an unknown version of Minecraft and may not work correctly.", "pack.incompatible.unknown": "(Broken or incompatible)", "subtitles.block.shelf.activate": "Shelf activates", "subtitles.block.shelf.deactivate": "Shelf deactivates", "subtitles.block.shelf.multi_swap": "Items swap", "subtitles.block.shelf.place_item": "Item placed", "subtitles.block.shelf.single_swap": "Item swaps", "subtitles.block.shelf.take_item": "Item taken", "subtitles.entity.copper_golem_become_statue": "Copper Golem is petrified", "subtitles.entity.copper_golem_oxidized.death": "Copper Golem dies", "subtitles.entity.copper_golem_oxidized.hurt": "Copper Golem hurts", "subtitles.entity.copper_golem_oxidized.spin": "Copper Golem's head spins", "subtitles.entity.copper_golem_weathered.death": "Copper Golem dies", "subtitles.entity.copper_golem_weathered.hurt": "Copper Golem hurts", "subtitles.entity.copper_golem_weathered.spin": "Copper Golem's head spins", "subtitles.entity.copper_golem.death": "Copper Golem dies", "subtitles.entity.copper_golem.hurt": "Copper Golem hurts", "subtitles.entity.copper_golem.item_drop": "Copper Golem is placing an item", "subtitles.entity.copper_golem.item_no_drop": "Copper Golem can't place item", "subtitles.entity.copper_golem.no_item_get": "Copper Golem is picking up item", "subtitles.entity.copper_golem.no_item_no_get": "Copper Golem can't pick up item", "subtitles.entity.copper_golem.spawn": "Copper Golem appears", "subtitles.entity.copper_golem.spin": "Copper Golem's head spins", "subtitles.item.armor.equip_copper": "Copper armor clonks", "subtitles.weather.end_flash": "End Flash rumbles" }, "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", "advancements.husbandry.place_dried_ghast_in_water.description": "Place a Dried Ghast block into water", "advancements.husbandry.place_dried_ghast_in_water.title": "Stay Hydrated!", "argument.hexcolor.invalid": "Invalid hex color code '%s'", "argument.resource_or_id.no_such_element": "Can't find element '%s' in registry '%s'", "argument.waypoint.invalid": "Selected entity is not a waypoint", "attribute.name.camera_distance": "Camera Distance", "attribute.name.waypoint_receive_range": "Waypoint Receive Range", "attribute.name.waypoint_transmit_range": "Waypoint Transmit Range", "block.minecraft.dried_ghast": "Dried Ghast", "book.edit.title": "Book Edit Screen", "book.sign.title": "Book Sign Screen", "book.sign.titlebox": "Title", "book.view.title": "Book View Screen", "commands.datapack.create.already_exists": "Pack with name '%s' already exists", "commands.datapack.create.invalid_full_name": "Invalid new pack name '%s'", "commands.datapack.create.invalid_name": "Invalid characters in new pack name '%s'", "commands.datapack.create.io_failure": "Can't create pack with name '%s', check logs", "commands.datapack.create.metadata_encode_failure": "Failed to encode metadata for pack with name '%s': %s", "commands.datapack.create.success": "Created new empty pack with name '%s'", "commands.dialog.clear.multiple": "Cleared dialog for %s players", "commands.dialog.clear.single": "Cleared dialog for %s", "commands.dialog.show.multiple": "Displayed dialog to %s players", "commands.dialog.show.single": "Displayed dialog to %s", "commands.version.build_time": "build_time = %s", "commands.version.data": "data = %s", "commands.version.header": "Server version info:", "commands.version.id": "id = %s", "commands.version.name": "name = %s", "commands.version.pack.data": "pack_data = %s", "commands.version.pack.resource": "pack_resource = %s", "commands.version.protocol": "protocol = %s (%s)", "commands.version.series": "series = %s", "commands.version.stable.no": "stable = no", "commands.version.stable.yes": "stable = yes", "commands.waypoint.list.empty": "No waypoints in %s", "commands.waypoint.list.success": "%s waypoint(s) in %s: %s", "commands.waypoint.modify.color": "Waypoint color is now %s", "commands.waypoint.modify.color.reset": "Reset waypoint color", "commands.waypoint.modify.style": "Waypoint style changed", "dataPack.locator_bar.description": "Show the direction of other players in multiplayer", "dataPack.locator_bar.name": "Locator Bar", "debug.version.header": "Client version info:", "debug.version.help": "F3 + V = Client version info", "entity.minecraft.happy_ghast": "Happy Ghast", "gamerule.locatorBar": "Enable player Locator Bar", "gamerule.locatorBar.description": "When enabled, a bar is shown on the screen to indicate the direction of players.", "gui.waitingForResponse.button.inactive": "Back (%ss)", "gui.waitingForResponse.title": "Waiting for Server", "item.minecraft.black_harness": "Black Harness", "item.minecraft.blue_harness": "Blue Harness", "item.minecraft.brown_harness": "Brown Harness", "item.minecraft.cyan_harness": "Cyan Harness", "item.minecraft.gray_harness": "Gray Harness", "item.minecraft.green_harness": "Green Harness", "item.minecraft.happy_ghast_spawn_egg": "Happy Ghast Spawn Egg", "item.minecraft.harness": "Harness", "item.minecraft.light_blue_harness": "Light Blue Harness", "item.minecraft.light_gray_harness": "Light Gray Harness", "item.minecraft.lime_harness": "Lime Harness", "item.minecraft.magenta_harness": "Magenta Harness", "item.minecraft.music_disc_tears.desc": "Amos Roddy - Tears", "item.minecraft.orange_harness": "Orange Harness", "item.minecraft.pink_harness": "Pink Harness", "item.minecraft.purple_harness": "Purple Harness", "item.minecraft.red_harness": "Red Harness", "item.minecraft.white_harness": "White Harness", "item.minecraft.yellow_harness": "Yellow Harness", "jukebox_song.minecraft.tears": "Amos Roddy - Tears", "key.quickActions": "Quick Actions", "mco.configure.world.buttons.region_preference": "Select Region...", "mco.configure.world.close.question.title": "Need to make changes without disruption?", "mco.configure.world.loading": "Loading Realm", "mco.configure.world.region_preference": "Region Preference", "mco.configure.world.region_preference.title": "Region Preference Selection", "mco.configure.world.settings.expired": "You cannot edit settings of an expired Realm", "mco.connect.region": "Server region: %s", "mco.errorMessage.realmsService.configurationError": "An unexpected error occurred while editing world options", "mco.play.button.realm.closed": "Realm is closed", "menu.custom_options": "Custom Options...", "menu.custom_options.title": "Custom Options", "menu.custom_options.tooltip": "Note: Custom options are provided by third-party servers and/or content.\nHandle with care!", "menu.custom_screen_info.button_narration": "This is a custom screen. Learn more.", "menu.custom_screen_info.contents": "The contents of this screen are controlled by third-party servers and maps that are not owned, operated, or supervised by Mojang Studios or Microsoft.\n\nHandle with care! Always be careful when following links and never give away your personal information, including login details.\n\nIf this screen prevents you from playing, you can also disconnect from the current server by using the button below.", "menu.custom_screen_info.disconnect": "Custom screen rejected", "menu.custom_screen_info.title": "Note about custom screens", "menu.custom_screen_info.tooltip": "This is a custom screen. Click here to learn more.", "menu.quick_actions": "Quick Actions...", "menu.quick_actions.title": "Quick Actions", "multiplayer.confirm_command.parse_errors": "You are trying to execute an unrecognized or invalid command.\nAre you sure?\nCommand: %s", "multiplayer.confirm_command.permissions_required": "You are trying to execute a command that requires elevated permissions.\nThis might negatively affect your game.\nAre you sure?\nCommand: %s", "multiplayer.confirm_command.title": "Confirm Command Execution", "music.game.a_familiar_room": "Aaron Cherof - A Familiar Room", "music.game.an_ordinary_day": "Kumi Tanioka - An Ordinary Day", "music.game.ancestry": "Lena Raine - Ancestry", "music.game.below_and_above": "Amos Roddy - Below and Above", "music.game.broken_clocks": "Amos Roddy - Broken Clocks", "music.game.bromeliad": "Aaron Cherof - Bromeliad", "music.game.clark": "C418 - Clark", "music.game.comforting_memories": "Kumi Tanioka - Comforting Memories", "music.game.creative.aria_math": "C418 - Aria Math", "music.game.creative.biome_fest": "C418 - Biome Fest", "music.game.creative.blind_spots": "C418 - Blind Spots", "music.game.creative.dreiton": "C418 - Dreiton", "music.game.creative.haunt_muskie": "C418 - Haunt Muskie", "music.game.creative.taswell": "C418 - Taswell", "music.game.crescent_dunes": "Aaron Cherof - Crescent Dunes", "music.game.danny": "C418 - Danny", "music.game.deeper": "Lena Raine - Deeper", "music.game.dry_hands": "C418 - Dry Hands", "music.game.echo_in_the_wind": "Aaron Cherof - Echo in the Wind", "music.game.eld_unknown": "Lena Raine - Eld Unknown", "music.game.end.alpha": "C418 - Alpha", "music.game.end.boss": "C418 - Boss", "music.game.end.the_end": "C418 - The End", "music.game.endless": "Lena Raine - Endless", "music.game.featherfall": "Aaron Cherof - Featherfall", "music.game.fireflies": "Amos Roddy - Fireflies", "music.game.floating_dream": "Kumi Tanioka - Floating Dream", "music.game.haggstrom": "C418 - Haggstrom", "music.game.infinite_amethyst": "Lena Raine - Infinite Amethyst", "music.game.key": "C418 - Key", "music.game.komorebi": "Kumi Tanioka - komorebi", "music.game.left_to_bloom": "Lena Raine - Left to Bloom", "music.game.lilypad": "Amos Roddy - Lilypad", "music.game.living_mice": "C418 - Living Mice", "music.game.mice_on_venus": "C418 - Mice on Venus", "music.game.minecraft": "C418 - Minecraft", "music.game.nether.ballad_of_the_cats": "C418 - Ballad of the Cats", "music.game.nether.concrete_halls": "C418 - Concrete Halls", "music.game.nether.crimson_forest.chrysopoeia": "Lena Raine - Chrysopoeia", "music.game.nether.dead_voxel": "C418 - Dead Voxel", "music.game.nether.nether_wastes.rubedo": "Lena Raine - Rubedo", "music.game.nether.soulsand_valley.so_below": "Lena Raine - So Below", "music.game.nether.warmth": "C418 - Warmth", "music.game.one_more_day": "Lena Raine - One More Day", "music.game.os_piano": "Amos Roddy - O's Piano", "music.game.oxygene": "C418 - Oxygène", "music.game.pokopoko": "Kumi Tanioka - pokopoko", "music.game.puzzlebox": "Aaron Cherof - Puzzlebox", "music.game.stand_tall": "Lena Raine - Stand Tall", "music.game.subwoofer_lullaby": "C418 - Subwoofer Lullaby", "music.game.swamp.aerie": "Lena Raine - Aerie", "music.game.swamp.firebugs": "Lena Raine - Firebugs", "music.game.swamp.labyrinthine": "Lena Raine - Labyrinthine", "music.game.sweden": "C418 - Sweden", "music.game.watcher": "Aaron Cherof - Watcher", "music.game.water.axolotl": "C418 - Axolotl", "music.game.water.dragon_fish": "C418 - Dragon Fish", "music.game.water.shuniji": "C418 - Shuniji", "music.game.wending": "Lena Raine - Wending", "music.game.wet_hands": "C418 - Wet Hands", "music.game.yakusoku": "Kumi Tanioka - yakusoku", "music.menu.beginning_2": "C418 - Beginning 2", "music.menu.floating_trees": "C418 - Floating Trees", "music.menu.moog_city_2": "C418 - Moog City 2", "music.menu.mutation": "C418 - Mutation", "narration.item": "Item: %s", "options.music_frequency": "Music Frequency", "options.music_frequency.constant": "Constant", "options.music_frequency.frequent": "Frequent", "options.music_frequency.tooltip": "Changes how frequently music plays while in a game world.", "options.renderCloudsDistance": "Cloud Distance", "options.showNowPlayingToast": "Show Music Toast", "options.showNowPlayingToast.tooltip": "Displays a toast whenever a song starts playing. The same toast is constantly displayed in the in-game pause menu while a song is playing.", "realms.configuration.region_preference.automatic_owner": "Automatic (Realm owner ping)", "realms.configuration.region_preference.automatic_player": "Automatic (first to join session)", "realms.configuration.region.australia_east": "New South Wales, Australia", "realms.configuration.region.australia_southeast": "Victoria, Australia", "realms.configuration.region.brazil_south": "Brazil", "realms.configuration.region.central_india": "India", "realms.configuration.region.central_us": "Iowa, USA", "realms.configuration.region.east_asia": "Hong Kong", "realms.configuration.region.east_us": "Virginia, USA", "realms.configuration.region.east_us_2": "North Carolina, USA", "realms.configuration.region.france_central": "France", "realms.configuration.region.japan_east": "Eastern Japan", "realms.configuration.region.japan_west": "Western Japan", "realms.configuration.region.korea_central": "South Korea", "realms.configuration.region.north_central_us": "Illinois, USA", "realms.configuration.region.north_europe": "Ireland", "realms.configuration.region.south_central_us": "Texas, USA", "realms.configuration.region.southeast_asia": "Singapore", "realms.configuration.region.sweden_central": "Sweden", "realms.configuration.region.uae_north": "United Arab Emirates (UAE)", "realms.configuration.region.uk_south": "Southern England", "realms.configuration.region.west_central_us": "Utah, USA", "realms.configuration.region.west_europe": "Netherlands", "realms.configuration.region.west_us": "California, USA", "realms.configuration.region.west_us_2": "Washington, USA", "soundCategory.ui": "UI", "stat.minecraft.happy_ghast_one_cm": "Distance by Happy Ghast", "subtitles.block.dried_ghast.ambient": "Sounds of dryness", "subtitles.block.dried_ghast.ambient_water": "Dried Ghast rehydrates", "subtitles.block.dried_ghast.place_in_water": "Dried Ghast soaks", "subtitles.block.dried_ghast.transition": "Dried Ghast feels better", "subtitles.entity.ghastling.ambient": "Ghastling coos", "subtitles.entity.ghastling.death": "Ghastling dies", "subtitles.entity.ghastling.hurt": "Ghastling hurts", "subtitles.entity.ghastling.spawn": "Ghastling appears", "subtitles.entity.happy_ghast.ambient": "Happy Ghast croons", "subtitles.entity.happy_ghast.death": "Happy Ghast dies", "subtitles.entity.happy_ghast.equip": "Harness equips", "subtitles.entity.happy_ghast.harness_goggles_down": "Happy Ghast is ready", "subtitles.entity.happy_ghast.harness_goggles_up": "Happy Ghast stops", "subtitles.entity.happy_ghast.hurt": "Happy Ghast hurts", "subtitles.entity.happy_ghast.unequip": "Harness unequips", "subtitles.item.horse_armor.unequip": "Horse Armor snips away", "subtitles.item.lead.break": "Lead snaps", "subtitles.item.lead.tied": "Lead tied", "subtitles.item.lead.untied": "Lead untied", "subtitles.item.llama_carpet.unequip": "Carpet snips away", "subtitles.item.saddle.unequip": "Saddle snips away", "subtitles.item.shears.snip": "Shears snip" }, "1.21.5": { "argument.nbt.expected.compound": "Expected compound tag", "argument.resource_selector.not_found": "No matches for selector '%s' of type '%s'", "block.minecraft.bush": "Bush", "block.minecraft.cactus_flower": "Cactus Flower", "block.minecraft.firefly_bush": "Firefly Bush", "block.minecraft.leaf_litter": "Leaf Litter", "block.minecraft.short_dry_grass": "Short Dry Grass", "block.minecraft.tall_dry_grass": "Tall Dry Grass", "block.minecraft.test_block": "Test Block", "block.minecraft.test_instance_block": "Test Instance Block", "block.minecraft.tnt.disabled": "TNT explosions are disabled", "block.minecraft.wildflowers": "Wildflowers", "commands.test.batch.starting": "Starting environment %s batch %s", "commands.test.clear.error.no_tests": "Could not find any tests to clear", "commands.test.clear.success": "Cleared %s structure(s)", "commands.test.coordinates.copy": "Click to copy to clipboard", "commands.test.create.success": "Created test setup for test %s", "commands.test.error.no_test_containing_pos": "Can't find a test instance that contains %s, %s, %s", "commands.test.error.no_test_instances": "Found no test instances", "commands.test.error.non_existant_test": "Test %s could not be found", "commands.test.error.structure_not_found": "Test structure %s could not be found", "commands.test.error.test_instance_not_found": "Test instance block entity could not be found", "commands.test.error.test_instance_not_found.position": "Test instance block entity could not be found for test at %s, %s, %s", "commands.test.error.too_large": "The structure size must be less than %s blocks along each axis", "commands.test.locate.done": "Finished locating, found %s structure(s)", "commands.test.locate.found": "Found structure at: %s (distance: %s)", "commands.test.locate.started": "Started locating test structures, this might take a while...", "commands.test.no_tests": "No tests to run", "commands.test.relative_position": "Position relative to %s: %s", "commands.test.reset.error.no_tests": "Could not find any tests to reset", "commands.test.reset.success": "Reset %s structure(s)", "commands.test.run.no_tests": "No tests found", "commands.test.run.running": "Running %s test(s)...", "commands.test.summary": "Game Test complete! %s test(s) were run", "commands.test.summary.all_required_passed": "All required tests passed :)", "commands.test.summary.failed": "%s required test(s) failed :(", "commands.test.summary.optional_failed": "%s optional test(s) failed", "gamerule.allowFireTicksAwayFromPlayer": "Tick fire away from players", "gamerule.allowFireTicksAwayFromPlayer.description": "Controls whether or not fire and lava should be able to tick further than 8 chunks away from any player", "gamerule.tntExplodes": "Allow TNT to be activated and to explode", "item.minecraft.blue_egg": "Blue Egg", "item.minecraft.brown_egg": "Brown Egg", "item.minecraft.crossbow.projectile.multiple": "Projectile: %s x %s", "item.minecraft.crossbow.projectile.single": "Projectile: %s", "item.minecraft.firework_rocket.multiple_stars": "%s x %s", "item.modifiers.saddle": "When saddled:", "multiplayer.disconnect.bad_chat_index": "Detected missed or reordered chat message from server", "snbt.parser.empty_key": "Key cannot be empty", "snbt.parser.expected_binary_numeral": "Expected a binary number", "snbt.parser.expected_decimal_numeral": "Expected a decimal number", "snbt.parser.expected_float_type": "Expected a floating point number", "snbt.parser.expected_hex_escape": "Expected a character literal of length %s", "snbt.parser.expected_hex_numeral": "Expected a hexadecimal number", "snbt.parser.expected_integer_type": "Expected an integer number", "snbt.parser.expected_non_negative_number": "Expected a non-negative number", "snbt.parser.expected_number_or_boolean": "Expected a number or a boolean", "snbt.parser.expected_string_uuid": "Expected a string representing a valid UUID", "snbt.parser.expected_unquoted_string": "Expected a valid unquoted string", "snbt.parser.infinity_not_allowed": "Non-finite numbers are not allowed", "snbt.parser.invalid_array_element_type": "Invalid array element type", "snbt.parser.invalid_character_name": "Invalid Unicode character name", "snbt.parser.invalid_codepoint": "Invalid Unicode character value: %s", "snbt.parser.invalid_string_contents": "Invalid string contents", "snbt.parser.invalid_unquoted_start": "Unquoted strings can't start with digits 0-9, + or -", "snbt.parser.leading_zero_not_allowed": "Decimal numbers can't start with 0", "snbt.parser.no_such_operation": "No such operation: %s", "snbt.parser.number_parse_failure": "Failed to parse number: %s", "snbt.parser.undescore_not_allowed": "Underscore characters are not allowed at the start or end of a number", "structure_block.strict": "Strict Placement:", "subtitles.block.deadbush.idle": "Dry sounds", "subtitles.block.firefly_bush.idle": "Fireflies buzz", "subtitles.block.sand.idle": "Sandy sounds", "subtitles.block.sand.wind": "Windy sounds", "subtitles.entity.wolf.bark": "Wolf barks", "subtitles.entity.wolf.whine": "Wolf whines", "test_block.error.missing": "Test structure missing %s block", "test_block.error.too_many": "Too many %s blocks", "test_block.invalid_timeout": "Invalid timeout (%s) - must be a positive number of ticks", "test_block.message": "Message:", "test_block.mode_info.accept": "Accept Mode - Accept success for (part of) a test", "test_block.mode_info.fail": "Fail Mode - Fail the test", "test_block.mode_info.log": "Log Mode - Log a message", "test_block.mode_info.start": "Start Mode - The starting point for a test", "test_block.mode.fail": "Fail", "test_block.mode.log": "Log", "test_block.mode.start": "Start", "test_instance_block.entities": "Entities:", "test_instance_block.error.no_test": "Unable to run test instance at %s, %s, %s since it has an undefined test", "test_instance_block.error.no_test_structure": "Unable to run test instance at %s, %s, %s since it has no test structure", "test_instance_block.error.unable_to_save": "Unable to save test structure template for test instance at %s, %s, %s", "test_instance_block.invalid": "[invalid]", "test_instance_block.reset_success": "Reset succeeded for test: %s", "test_instance_block.rotation": "Rotation:", "test_instance_block.size": "Test Structure Size", "test_instance_block.starting": "Starting test %s", "test_instance_block.test_id": "Test Instance ID", "test_instance.action.reset": "Reset and Load", "test_instance.action.run": "Load and Run", "test_instance.action.save": "Save Structure", "test_instance.description.batch": "Batch: %s", "test_instance.description.failed": "Failed: %s", "test_instance.description.function": "Function: %s", "test_instance.description.invalid_id": "Invalid test ID", "test_instance.description.no_test": "No such test", "test_instance.description.structure": "Structure: %s", "test_instance.type.block_based": "Block-Based Test", "test_instance.type.function": "Built-in Function Test", "test.error.block_property_mismatch": "Expected property %s to be %s, was %s", "test.error.block_property_missing": "Block property missing, expected property %s to be %s", "test.error.entity_property": "Entity %s failed test: %s", "test.error.entity_property_details": "Entity %s failed test: %s, expected: %s, was: %s", "test.error.expected_block": "Expected block %s, got %s", "test.error.expected_block_tag": "Expected block in #%s, got %s", "test.error.expected_container_contents": "Container should contain: %s", "test.error.expected_container_contents_single": "Container should contain a single: %s", "test.error.expected_empty_container": "Container should be empty", "test.error.expected_entity": "Expected %s", "test.error.expected_entity_around": "Expected %s to exist around %s, %s, %s", "test.error.expected_entity_count": "Expected %s entities of type %s, found %s", "test.error.expected_entity_data": "Expected entity data to be: %s, was: %s", "test.error.expected_entity_data_predicate": "Entity data mismatch for %s", "test.error.expected_entity_effect": "Expected %s to have effect %s %s", "test.error.expected_entity_having": "Entity inventory should contain %s", "test.error.expected_entity_holding": "Entity should be holding %s", "test.error.expected_entity_in_test": "Expected %s to exist in test", "test.error.expected_entity_not_touching": "Did not expect %s touching %s, %s, %s (relative: %s, %s, %s)", "test.error.expected_entity_touching": "Expected %s touching %s, %s, %s (relative: %s, %s, %s)", "test.error.expected_item": "Expected item of type %s", "test.error.expected_items_count": "Expected %s items of type %s, found %s", "test.error.fail": "Fail conditions met", "test.error.invalid_block_type": "Unexpected block type found: %s", "test.error.missing_block_entity": "Missing block entity", "test.error.position": "%s at %s, %s, %s (relative: %s, %s, %s) on tick %s", "test.error.sequence.condition_already_triggered": "Condition already triggered at %s", "test.error.sequence.condition_not_triggered": "Condition not triggered", "test.error.sequence.invalid_tick": "Succeeded in invalid tick: expected %s", "test.error.sequence.not_completed": "Test timed out before sequence completed", "test.error.set_biome": "Failed to set biome for test", "test.error.spawn_failure": "Failed to create entity %s", "test.error.state_not_equal": "Incorrect state. Expected %s, was %s", "test.error.structure.failure": "Failed to place test structure for %s", "test.error.tick": "%s on tick %s", "test.error.ticking_without_structure": "Ticking test before placing structure", "test.error.timeout.no_result": "Didn't succeed or fail within %s ticks", "test.error.timeout.no_sequences_finished": "No sequences finished within %s ticks", "test.error.too_many_entities": "Expected only one %s to exist around %s, %s, %s but found %s", "test.error.unexpected_block": "Did not expect block to be %s", "test.error.unexpected_entity": "Did not expect %s to exist", "test.error.unexpected_item": "Did not expect item of type %s", "test.error.unknown": "Unknown internal error: %s", "test.error.value_not_equal": "Expected %s to be %s, was %s", "test.error.wrong_block_entity": "Wrong block entity type: %s" }, "1.21.4": { "block.minecraft.chiseled_resin_bricks": "Chiseled Resin Bricks", "block.minecraft.closed_eyeblossom": "Closed Eyeblossom", "block.minecraft.open_eyeblossom": "Open Eyeblossom", "block.minecraft.potted_closed_eyeblossom": "Potted Closed Eyeblossom", "block.minecraft.potted_open_eyeblossom": "Potted Open Eyeblossom", "block.minecraft.resin_block": "Block of Resin", "block.minecraft.resin_brick_slab": "Resin Brick Slab", "block.minecraft.resin_brick_stairs": "Resin Brick Stairs", "block.minecraft.resin_brick_wall": "Resin Brick Wall", "block.minecraft.resin_bricks": "Resin Bricks", "block.minecraft.resin_clump": "Resin Clump", "commands.attribute.base_value.reset.success": "Base value for attribute %s for entity %s reset to default %s", "item.minecraft.resin_brick": "Resin Brick", "item.minecraft.resin_clump": "Resin Clump", "item.op_block_warning.line1": "Warning:", "item.op_block_warning.line2": "Use of this item might lead to command execution", "item.op_block_warning.line3": "Do not use unless you know the exact contents!", "subtitles.block.eyeblossom.close": "Eyeblossom closes", "subtitles.block.eyeblossom.idle": "Eyeblossom whispers", "subtitles.block.eyeblossom.open": "Eyeblossom opens", "subtitles.block.generic.fall": "Something falls on a block", "subtitles.entity.creaking.twitch": "Creaking twitches", "subtitles.entity.fish.swim": "Splashes", "subtitles.entity.minecart.inside": "Minecart jangles", "subtitles.entity.minecart.inside_underwater": "Minecart jangles underwater", "subtitles.entity.skeleton_horse.jump_water": "Skeleton Horse jumps", "subtitles.item.elytra.flying": "Swoosh", "trim_material.minecraft.resin": "Resin Material" }, "1.21.2": { "attribute.name.tempt_range": "Mob Tempt Range", "block.minecraft.creaking_heart": "Creaking Heart", "block.minecraft.pale_hanging_moss": "Pale Hanging Moss", "block.minecraft.pale_moss_block": "Pale Moss Block", "block.minecraft.pale_moss_carpet": "Pale Moss Carpet", "block.minecraft.pale_oak_button": "Pale Oak Button", "block.minecraft.pale_oak_door": "Pale Oak Door", "block.minecraft.pale_oak_fence": "Pale Oak Fence", "block.minecraft.pale_oak_fence_gate": "Pale Oak Fence Gate", "block.minecraft.pale_oak_hanging_sign": "Pale Oak Hanging Sign", "block.minecraft.pale_oak_leaves": "Pale Oak Leaves", "block.minecraft.pale_oak_log": "Pale Oak Log", "block.minecraft.pale_oak_planks": "Pale Oak Planks", "block.minecraft.pale_oak_pressure_plate": "Pale Oak Pressure Plate", "block.minecraft.pale_oak_sapling": "Pale Oak Sapling", "block.minecraft.pale_oak_sign": "Pale Oak Sign", "block.minecraft.pale_oak_slab": "Pale Oak Slab", "block.minecraft.pale_oak_stairs": "Pale Oak Stairs", "block.minecraft.pale_oak_trapdoor": "Pale Oak Trapdoor", "block.minecraft.pale_oak_wood": "Pale Oak Wood", "block.minecraft.potted_pale_oak_sapling": "Potted Pale Oak Sapling", "block.minecraft.stripped_pale_oak_log": "Stripped Pale Oak Log", "block.minecraft.stripped_pale_oak_wood": "Stripped Pale Oak Wood", "commands.drop.no_loot_table.block": "Block %s has no loot table", "commands.rotate.success": "Rotated %s", "commands.schedule.macro": "Can't schedule a macro", "commands.setidletimeout.success.disabled": "The player idle timeout is now disabled", "container.beehive.bees": "Bees: %s / %s", "container.beehive.honey": "Honey: %s / %s", "dataPack.minecart_improvements.description": "Improved movement for Minecarts", "dataPack.minecart_improvements.name": "Minecart Improvements", "dataPack.redstone_experiments.description": "Experimental Redstone changes", "dataPack.redstone_experiments.name": "Redstone Experiments", "dataPack.winter_drop.description": "New features and content for the Winter Drop", "dataPack.winter_drop.name": "Winter Drop", "death.attack.mace_smash": "%1$s was smashed by %2$s", "death.attack.mace_smash.item": "%1$s was smashed by %2$s with %3$s", "entity.minecraft.creaking": "Creaking", "entity.minecraft.creaking_transient": "Creaking", "entity.minecraft.pale_oak_boat": "Pale Oak Boat", "entity.minecraft.pale_oak_chest_boat": "Pale Oak Boat with Chest", "gamerule.disablePlayerMovementCheck": "Disable player movement check", "gamerule.minecartMaxSpeed": "Minecart max speed", "gamerule.minecartMaxSpeed.description": "Maximum default speed of a moving Minecart on land.", "gui.abuseReport.name.comment_box_label": "Please describe why you want to report this name:", "gui.abuseReport.reason.sexually_inappropriate": "Sexually inappropriate", "gui.abuseReport.reason.sexually_inappropriate.description": "Skins that are graphic in nature relating to sexual acts, sexual organs, and sexual violence.", "item.minecraft.black_bundle": "Black Bundle", "item.minecraft.blue_bundle": "Blue Bundle", "item.minecraft.bordure_indented_banner_pattern": "Bordure Indented Banner Pattern", "item.minecraft.brown_bundle": "Brown Bundle", "item.minecraft.bundle.empty.description": "Can hold a mixed stack of items", "item.minecraft.bundle.full": "Full", "item.minecraft.creaking_spawn_egg": "Creaking Spawn Egg", "item.minecraft.creeper_banner_pattern.new": "Creeper Charge Banner Pattern", "item.minecraft.cyan_bundle": "Cyan Bundle", "item.minecraft.field_masoned_banner_pattern": "Field Masoned Banner Pattern", "item.minecraft.flow_banner_pattern.new": "Flow Banner Pattern", "item.minecraft.flower_banner_pattern.new": "Flower Charge Banner Pattern", "item.minecraft.globe_banner_pattern.new": "Globe Banner Pattern", "item.minecraft.gray_bundle": "Gray Bundle", "item.minecraft.green_bundle": "Green Bundle", "item.minecraft.guster_banner_pattern.new": "Guster Banner Pattern", "item.minecraft.light_blue_bundle": "Light Blue Bundle", "item.minecraft.light_gray_bundle": "Light Gray Bundle", "item.minecraft.lime_bundle": "Lime Bundle", "item.minecraft.magenta_bundle": "Magenta Bundle", "item.minecraft.mojang_banner_pattern.new": "Thing Banner Pattern", "item.minecraft.orange_bundle": "Orange Bundle", "item.minecraft.pale_oak_boat": "Pale Oak Boat", "item.minecraft.pale_oak_chest_boat": "Pale Oak Boat with Chest", "item.minecraft.piglin_banner_pattern.new": "Snout Banner Pattern", "item.minecraft.pink_bundle": "Pink Bundle", "item.minecraft.purple_bundle": "Purple Bundle", "item.minecraft.red_bundle": "Red Bundle", "item.minecraft.skull_banner_pattern.new": "Skull Charge Banner Pattern", "item.minecraft.white_bundle": "White Bundle", "item.minecraft.yellow_bundle": "Yellow Bundle", "mco.create.world.failed": "Failed to create world!", "mco.errorMessage.initialize.failed": "Failed to initialize Realm", "mco.upload.failed.too_big.description": "The selected world is too big. The maximum allowed size is %s.", "mco.upload.failed.too_big.title": "World too big", "optimizeWorld.confirm.proceed": "Create Backup and Optimize", "options.accessibility.high_contrast_block_outline": "High Contrast Block Outlines", "options.accessibility.high_contrast_block_outline.tooltip": "Enhances the block outline contrast of targeted block.", "options.inactivityFpsLimit": "Reduce FPS when", "options.inactivityFpsLimit.afk": "AFK", "options.inactivityFpsLimit.afk.tooltip": "Limits framerate to 30 when the game is not getting any player input for more than a minute. Further limits it to 10 after 9 more minutes.", "options.inactivityFpsLimit.minimized": "Minimized", "options.inactivityFpsLimit.minimized.tooltip": "Limits framerate only when the game window is minimized.", "options.rotateWithMinecart": "Rotate with Minecarts", "options.rotateWithMinecart.tooltip": "Whether the player's view should rotate with a turning Minecart. Only available in worlds with the 'Minecart Improvements' experimental setting turned on.", "resourcePack.runtime_failure": "Resource pack error detected", "subtitles.block.creaking_heart.hurt": "Creaking Heart screams", "subtitles.block.creaking_heart.spawn": "Creaking Heart awakens", "subtitles.entity.creaking.activate": "Creaking activates", "subtitles.entity.creaking.ambient": "Creaking creaks", "subtitles.entity.creaking.angry": "Creaking sees player", "subtitles.entity.creaking.attack": "Creaking attacks", "subtitles.entity.creaking.deactivate": "Creaking deactivates", "subtitles.entity.creaking.death": "Creaking dies", "subtitles.entity.creaking.freeze": "Creaking stops", "subtitles.entity.creaking.spawn": "Creaking lives", "subtitles.entity.creaking.sway": "Creaking is shielded", "subtitles.entity.creaking.unfreeze": "Creaking moves", "subtitles.entity.parrot.imitate.creaking": "Parrot creaks", "subtitles.item.bundle.insert_fail": "Bundle full", "subtitles.ui.hud.bubble_pop": "Breath meter dropping" }, "1.21": { "argument.entity.selector.nearestEntity": "Nearest entity", "attribute.name.generic.burning_time": "Burning Time", "attribute.name.generic.explosion_knockback_resistance": "Explosion Knockback Resistance", "attribute.name.generic.movement_efficiency": "Movement Efficiency", "attribute.name.generic.oxygen_bonus": "Oxygen Bonus", "attribute.name.generic.water_movement_efficiency": "Water Movement Efficiency", "attribute.name.player.mining_efficiency": "Mining Efficiency", "attribute.name.player.sneaking_speed": "Sneaking Speed", "attribute.name.player.submerged_mining_speed": "Submerged Mining Speed", "attribute.name.player.sweeping_damage_ratio": "Sweeping Damage Ratio", "gamerule.entitiesWithPassengersCanUsePortals": "Entities with passengers can use portals", "gamerule.entitiesWithPassengersCanUsePortals.description": "Allow entities with passengers to teleport through Nether Portals, End Portals, and End Gateways.", "gui.abuseReport.attestation": "By submitting this report, you confirm that the information you have provided is accurate and complete to the best of your knowledge.", "gui.abuseReport.send.not_attested": "Please read the text above and tick the checkbox to be able to send the report", "gui.fileDropFailure.detail": "Rejected %d files", "gui.fileDropFailure.title": "Failed to add files", "gui.open_report_dir": "Open Report Directory", "gui.report_to_server": "Report To Server", "item.minecraft.music_disc_creator_music_box.desc": "Lena Raine - Creator (Music Box)", "item.minecraft.music_disc_creator.desc": "Lena Raine - Creator", "item.minecraft.music_disc_precipice.desc": "Aaron Cherof - Precipice", "item.modifiers.armor": "When worn:", "item.modifiers.hand": "When held:", "jukebox_song.minecraft.creator": "Lena Raine - Creator", "jukebox_song.minecraft.creator_music_box": "Lena Raine - Creator (Music Box)", "jukebox_song.minecraft.precipice": "Aaron Cherof - Precipice", "known_server_link.announcements": "Announcements", "known_server_link.community": "Community", "known_server_link.community_guidelines": "Community Guidelines", "known_server_link.feedback": "Feedback", "known_server_link.forums": "Forums", "known_server_link.news": "News", "known_server_link.report_bug": "Report Server Bug", "known_server_link.support": "Support", "known_server_link.website": "Website", "mco.compatibility.incompatible.releaseType.popup.message": "The world you are trying to join is incompatible with the version you are on.", "mco.compatibility.incompatible.series.popup.message": "This world was last played in version %s; you are on version %s.\n\nThese series are not compatible with each other. A new world is needed to play on this version.", "mco.compatibility.upgrade.friend.description": "This world was last played in version %s; you are on version %s.\n\nA backup of the world will be saved under \"World Backups\".\n\nThe owner of the Realm can restore the world if needed.", "mco.configure.world.resourcepack.question": "You need a custom resource pack to play on this realm\n\nDo you want to download it and play?", "mco.download.confirmation.oversized": "The world you are going to download is larger than %s\n\nYou won't be able to upload this world to your realm again", "mco.onlinePlayers": "Online Players", "menu.feedback": "Feedback...", "menu.feedback.title": "Feedback", "menu.server_links": "Server Links...", "menu.server_links.title": "Server Links", "options.realmsNotifications.tooltip": "Fetches Realms news and invites in the title screen and displays their respective icon on the Realms button.", "painting.minecraft.backyard.title": "Backyard", "painting.minecraft.baroque.author": "Sarah Boeving", "painting.minecraft.baroque.title": "Baroque", "painting.minecraft.bouquet.title": "Bouquet", "painting.minecraft.cavebird.title": "Cavebird", "painting.minecraft.changing.title": "Changing", "painting.minecraft.cotan.title": "Cot�n", "painting.minecraft.endboss.title": "Endboss", "painting.minecraft.finding.title": "Finding", "painting.minecraft.humble.author": "Sarah Boeving", "painting.minecraft.humble.title": "Humble", "painting.minecraft.lowmist.title": "Lowmist", "painting.minecraft.meditative.author": "Sarah Boeving", "painting.minecraft.meditative.title": "Meditative", "painting.minecraft.orb.title": "Orb", "painting.minecraft.owlemons.title": "Owlemons", "painting.minecraft.passage.title": "Passage", "painting.minecraft.pond.title": "Pond", "painting.minecraft.prairie_ride.author": "Sarah Boeving", "painting.minecraft.prairie_ride.title": "Prairie Ride", "painting.minecraft.sunflowers.title": "Sunflowers", "painting.minecraft.tides.title": "Tides", "painting.minecraft.unpacked.author": "Sarah Boeving", "painting.minecraft.unpacked.title": "Unpacked", "subtitles.block.trial_spawner.ambient_ominous": "Ominous crackling", "subtitles.block.vault.reject_rewarded_player": "Vault rejects player" }, "1.20.5": { "advancements.adventure.blowback.description": "Kill a Breeze with a deflected Breeze-shot Wind Charge", "advancements.adventure.blowback.title": "Blowback", "advancements.adventure.brush_armadillo.description": "Get Armadillo Scutes from an Armadillo using a Brush", "advancements.adventure.brush_armadillo.title": "Isn't It Scute?", "advancements.adventure.crafters_crafting_crafters.description": "Be near a Crafter when it crafts a Crafter", "advancements.adventure.crafters_crafting_crafters.title": "Crafters Crafting Crafters", "advancements.adventure.lighten_up.description": "Scrape a Copper Bulb with an Axe to make it brighter", "advancements.adventure.lighten_up.title": "Lighten Up", "advancements.adventure.minecraft_trials_edition.description": "Step foot in a Trial Chamber", "advancements.adventure.minecraft_trials_edition.title": "Minecraft: Trial(s) Edition", "advancements.adventure.overoverkill.description": "Deal 50 hearts of damage in a single hit using the Mace", "advancements.adventure.overoverkill.title": "Over-Overkill", "advancements.adventure.revaulting.description": "Unlock an Ominous Vault with an Ominous Trial Key", "advancements.adventure.revaulting.title": "Revaulting", "advancements.adventure.under_lock_and_key.description": "Unlock a Vault with a Trial Key", "advancements.adventure.under_lock_and_key.title": "Under Lock and Key", "advancements.adventure.who_needs_rockets.description": "Use a Wind Charge to launch yourself upward 8 blocks", "advancements.adventure.who_needs_rockets.title": "Who Needs Rockets?", "advancements.husbandry.remove_wolf_armor.description": "Remove Wolf Armor from a Wolf using Shears", "advancements.husbandry.remove_wolf_armor.title": "Shear Brilliance", "advancements.husbandry.repair_wolf_armor.description": "Repair a damaged Wolf Armor using Armadillo Scutes", "advancements.husbandry.repair_wolf_armor.title": "Good as New", "advancements.husbandry.whole_pack.description": "Tame one of each Wolf variant", "advancements.husbandry.whole_pack.title": "The Whole Pack", "argument.message.too_long": "Chat message was too long (%s > maximum %s characters)", "argument.resource_or_id.failed_to_parse": "Failed to parse structure: %s", "argument.resource_or_id.invalid": "Invalid id or tag", "arguments.item.component.expected": "Expected item component", "arguments.item.component.malformed": "Malformed '%s' component: '%s'", "arguments.item.component.repeated": "Item component '%s' was repeated, but only one value can be specified", "arguments.item.component.unknown": "Unknown item component '%s'", "arguments.item.malformed": "Malformed item: '%s'", "arguments.item.predicate.malformed": "Malformed '%s' predicate: '%s'", "arguments.item.predicate.unknown": "Unknown item predicate '%s'", "attribute.name.generic.block_interaction_range": "Block Interaction Range", "attribute.name.generic.entity_interaction_range": "Entity Interaction Range", "attribute.name.generic.fall_damage_multiplier": "Fall Damage Multiplier", "attribute.name.generic.gravity": "Gravity", "attribute.name.generic.jump_strength": "Jump Strength", "attribute.name.generic.safe_fall_distance": "Safe Fall Distance", "attribute.name.generic.scale": "Scale", "attribute.name.generic.step_height": "Step Height", "attribute.name.player.block_break_speed": "Block Break Speed", "attribute.name.player.block_interaction_range": "Block Interaction Range", "attribute.name.player.entity_interaction_range": "Entity Interaction Range", "block.minecraft.banner.flow.black": "Black Flow", "block.minecraft.banner.flow.blue": "Blue Flow", "block.minecraft.banner.flow.brown": "Brown Flow", "block.minecraft.banner.flow.cyan": "Cyan Flow", "block.minecraft.banner.flow.gray": "Gray Flow", "block.minecraft.banner.flow.green": "Green Flow", "block.minecraft.banner.flow.light_blue": "Light Blue Flow", "block.minecraft.banner.flow.light_gray": "Light Gray Flow", "block.minecraft.banner.flow.lime": "Lime Flow", "block.minecraft.banner.flow.magenta": "Magenta Flow", "block.minecraft.banner.flow.orange": "Orange Flow", "block.minecraft.banner.flow.pink": "Pink Flow", "block.minecraft.banner.flow.purple": "Purple Flow", "block.minecraft.banner.flow.red": "Red Flow", "block.minecraft.banner.flow.white": "White Flow", "block.minecraft.banner.flow.yellow": "Yellow Flow", "block.minecraft.banner.guster.black": "Black Guster", "block.minecraft.banner.guster.blue": "Blue Guster", "block.minecraft.banner.guster.brown": "Brown Guster", "block.minecraft.banner.guster.cyan": "Cyan Guster", "block.minecraft.banner.guster.gray": "Gray Guster", "block.minecraft.banner.guster.green": "Green Guster", "block.minecraft.banner.guster.light_blue": "Light Blue Guster", "block.minecraft.banner.guster.light_gray": "Light Gray Guster", "block.minecraft.banner.guster.lime": "Lime Guster", "block.minecraft.banner.guster.magenta": "Magenta Guster", "block.minecraft.banner.guster.orange": "Orange Guster", "block.minecraft.banner.guster.pink": "Pink Guster", "block.minecraft.banner.guster.purple": "Purple Guster", "block.minecraft.banner.guster.red": "Red Guster", "block.minecraft.banner.guster.white": "White Guster", "block.minecraft.banner.guster.yellow": "Yellow Guster", "block.minecraft.heavy_core": "Heavy Core", "block.minecraft.vault": "Vault", "chat.disabled.invalid_command_signature": "Command had unexpected or missing command argument signatures.", "chat.disabled.invalid_signature": "Chat had an invalid signature. Please try reconnecting.", "chat.disabled.out_of_order_chat": "Chat received out-of-order. Did your system time change?", "chunk.toast.checkLog": "See log for more details", "chunk.toast.loadFailure": "Failed to load chunk at %s", "chunk.toast.lowDiskSpace": "Low disk space!", "chunk.toast.lowDiskSpace.description": "Might not be able to save the world.", "chunk.toast.saveFailure": "Failed to save chunk at %s", "commands.datapack.disable.failed.feature": "Pack '%s' cannot be disabled, since it is part of an enabled flag!", "commands.setworldspawn.failure.not_overworld": "Can only set the world spawn for overworld", "commands.transfer.error.no_players": "Must specify at least one player to transfer", "commands.transfer.success.multiple": "Transferring %s players to %s:%s", "commands.transfer.success.single": "Transferring %s to %s:%s", "connect.failed.transfer": "Connection failed while transferring to the server", "connect.transferring": "Transferring to new server...", "disconnect.packetError": "Network Protocol Error", "disconnect.transfer": "Transferred to another server", "effect.minecraft.infested": "Infested", "effect.minecraft.oozing": "Oozing", "effect.minecraft.raid_omen": "Raid Omen", "effect.minecraft.trial_omen": "Trial Omen", "effect.minecraft.weaving": "Weaving", "effect.minecraft.wind_charged": "Wind Charged", "enchantment.minecraft.breach": "Breach", "enchantment.minecraft.density": "Density", "enchantment.minecraft.wind_burst": "Wind Burst", "entity.minecraft.armadillo": "Armadillo", "entity.minecraft.bogged": "Bogged", "entity.minecraft.ominous_item_spawner": "Ominous Item Spawner", "filled_map.trial_chambers": "Trial Chambers Map", "gamerule.spawnChunkRadius": "Spawn chunk radius", "gamerule.spawnChunkRadius.description": "Amount of chunks that stay loaded around the overworld spawn position.", "gamerule.spawnRadius.description": "Controls the size of the area around the spawn point that players can spawn in.", "item.components": "%s component(s)", "item.minecraft.armadillo_scute": "Armadillo Scute", "item.minecraft.armadillo_spawn_egg": "Armadillo Spawn Egg", "item.minecraft.bogged_spawn_egg": "Bogged Spawn Egg", "item.minecraft.breeze_rod": "Breeze Rod", "item.minecraft.flow_banner_pattern.desc": "Flow", "item.minecraft.flow_pottery_sherd": "Flow Pottery Sherd", "item.minecraft.guster_banner_pattern.desc": "Guster", "item.minecraft.guster_pottery_sherd": "Guster Pottery Sherd", "item.minecraft.lingering_potion.effect.infested": "Lingering Potion of Infestation", "item.minecraft.lingering_potion.effect.oozing": "Lingering Potion of Oozing", "item.minecraft.lingering_potion.effect.weaving": "Lingering Potion of Weaving", "item.minecraft.lingering_potion.effect.wind_charged": "Lingering Potion of Wind Charging", "item.minecraft.mace": "Mace", "item.minecraft.ominous_bottle": "Ominous Bottle", "item.minecraft.ominous_trial_key": "Ominous Trial Key", "item.minecraft.potion.effect.infested": "Potion of Infestation", "item.minecraft.potion.effect.oozing": "Potion of Oozing", "item.minecraft.potion.effect.weaving": "Potion of Weaving", "item.minecraft.potion.effect.wind_charged": "Potion of Wind Charging", "item.minecraft.scrape_pottery_sherd": "Scrape Pottery Sherd", "item.minecraft.splash_potion.effect.infested": "Splash Potion of Infestation", "item.minecraft.splash_potion.effect.oozing": "Splash Potion of Oozing", "item.minecraft.splash_potion.effect.weaving": "Splash Potion of Weaving", "item.minecraft.splash_potion.effect.wind_charged": "Splash Potion of Wind Charging", "item.minecraft.tipped_arrow.effect.infested": "Arrow of Infestation", "item.minecraft.tipped_arrow.effect.oozing": "Arrow of Oozing", "item.minecraft.tipped_arrow.effect.weaving": "Arrow of Weaving", "item.minecraft.tipped_arrow.effect.wind_charged": "Arrow of Wind Charging", "item.minecraft.turtle_scute": "Turtle Scute", "item.minecraft.wolf_armor": "Wolf Armor", "item.modifiers.body": "When equipped:", "mco.backup.narration": "Backup from %s", "mco.client.outdated.stable.version": "Your client version (%s) is not compatible with Realms.\n\nPlease use the most recent version of Minecraft.", "mco.client.unsupported.snapshot.version": "Your client version (%s) is not compatible with Realms.\n\nRealms is not available for this snapshot version.", "mco.invited.player.narration": "Invited player %s", "multiplayer.disconnect.transfers_disabled": "Server does not accept transfers", "optimizeWorld.stage.finished.chunks": "Finishing up upgrading chunks...", "optimizeWorld.stage.finished.entities": "Finishing up upgrading entities...", "optimizeWorld.stage.finished.poi": "Finishing up upgrading points of interest...", "optimizeWorld.stage.upgrading.entities": "Upgrading all entities...", "optimizeWorld.stage.upgrading.poi": "Upgrading all points of interest...", "options.accessibility.menu_background_blurriness": "Menu Background Blur", "options.accessibility.menu_background_blurriness.tooltip": "Changes the blurriness of menu backgrounds", "options.font": "Font Settings...", "options.font.title": "Font Settings", "options.japaneseGlyphVariants": "Japanese Glyph Variants", "options.japaneseGlyphVariants.tooltip": "Uses Japanese variants of CJK characters in the default font", "options.telemetry.disabled": "Telemetry is disabled.", "particle.invalidOptions": "Can't parse particle options: %s", "selectWorld.allowCommands.new": "Allow Commands", "selectWorld.commands": "Commands", "selectWorld.warning.lowDiskSpace.description": "There is not much space left on your device.\nRunning out of disk space while in game can lead to your world being damaged.", "selectWorld.warning.lowDiskSpace.title": "Warning! Low disk space!", "slot.only_single_allowed": "Only single slots allowed, got '%s'", "subtitles.block.candle.extinguish": "Candle extinguishes", "subtitles.block.trial_spawner.about_to_spawn_item": "Ominous item prepares", "subtitles.block.trial_spawner.ambient_charged": "Ominous Trial Spawner crackles", "subtitles.block.trial_spawner.charge_activate": "Omen engulfs Trial Spawner", "subtitles.block.trial_spawner.spawn_item": "Ominous item drops", "subtitles.block.trial_spawner.spawn_item_begin": "Ominous item appears", "subtitles.block.vault.activate": "Vault ignites", "subtitles.block.vault.ambient": "Vault crackles", "subtitles.block.vault.close_shutter": "Vault closes", "subtitles.block.vault.deactivate": "Vault extinguishes", "subtitles.block.vault.eject_item": "Vault ejects item", "subtitles.block.vault.insert_item": "Vault unlocks", "subtitles.block.vault.insert_item_fail": "Vault fails unlocking", "subtitles.block.vault.open_shutter": "Vault opens", "subtitles.block.wet_sponge.dries": "Sponge dries", "subtitles.entity.armadillo.ambient": "Armadillo grunts", "subtitles.entity.armadillo.brush": "Scute is brushed off", "subtitles.entity.armadillo.death": "Armadillo dies", "subtitles.entity.armadillo.eat": "Armadillo eats", "subtitles.entity.armadillo.hurt": "Armadillo hurts", "subtitles.entity.armadillo.hurt_reduced": "Armadillo shields itself", "subtitles.entity.armadillo.land": "Armadillo lands", "subtitles.entity.armadillo.peek": "Armadillo peeks", "subtitles.entity.armadillo.roll": "Armadillo rolls up", "subtitles.entity.armadillo.scute_drop": "Armadillo sheds scute", "subtitles.entity.armadillo.unroll_finish": "Armadillo unrolls", "subtitles.entity.armadillo.unroll_start": "Armadillo peeks", "subtitles.entity.bogged.ambient": "Bogged rattles", "subtitles.entity.bogged.death": "Bogged dies", "subtitles.entity.bogged.hurt": "Bogged hurts", "subtitles.entity.breeze.charge": "Breeze charges", "subtitles.entity.breeze.deflect": "Breeze deflects", "subtitles.entity.breeze.whirl": "Breeze whirls", "subtitles.entity.donkey.jump": "Donkey jumps", "subtitles.entity.mule.jump": "Mule jumps", "subtitles.entity.wind_charge.throw": "Wind Charge flies", "subtitles.event.mob_effect.bad_omen": "Omen takes hold", "subtitles.event.mob_effect.raid_omen": "Raid looms nearby", "subtitles.event.mob_effect.trial_omen": "Ominous trial looms nearby", "subtitles.item.armor.equip_wolf": "Wolf Armor is fastened", "subtitles.item.armor.unequip_wolf": "Wolf Armor snips away", "subtitles.item.mace.smash_air": "Mace smashes", "subtitles.item.mace.smash_ground": "Mace smashes", "subtitles.item.ominous_bottle.dispose": "Bottle breaks", "subtitles.item.wolf_armor.break": "Wolf Armor breaks", "subtitles.item.wolf_armor.crack": "Wolf Armor cracks", "subtitles.item.wolf_armor.damage": "Wolf Armor takes damage", "subtitles.item.wolf_armor.repair": "Wolf Armor is repaired", "trim_pattern.minecraft.bolt": "Bolt Armor Trim", "trim_pattern.minecraft.flow": "Flow Armor Trim" }, "1.20.3": { "accessibility.onboarding.accessibility.button": "Accessibility Settings...", "argument.style.invalid": "Invalid style: %s", "block.minecraft.chiseled_copper": "Chiseled Copper", "block.minecraft.chiseled_tuff": "Chiseled Tuff", "block.minecraft.chiseled_tuff_bricks": "Chiseled Tuff Bricks", "block.minecraft.copper_bulb": "Copper Bulb", "block.minecraft.copper_door": "Copper Door", "block.minecraft.copper_grate": "Copper Grate", "block.minecraft.copper_trapdoor": "Copper Trapdoor", "block.minecraft.crafter": "Crafter", "block.minecraft.exposed_chiseled_copper": "Exposed Chiseled Copper", "block.minecraft.exposed_copper_bulb": "Exposed Copper Bulb", "block.minecraft.exposed_copper_door": "Exposed Copper Door", "block.minecraft.exposed_copper_grate": "Exposed Copper Grate", "block.minecraft.exposed_copper_trapdoor": "Exposed Copper Trapdoor", "block.minecraft.oxidized_chiseled_copper": "Oxidized Chiseled Copper", "block.minecraft.oxidized_copper_bulb": "Oxidized Copper Bulb", "block.minecraft.oxidized_copper_door": "Oxidized Copper Door", "block.minecraft.oxidized_copper_grate": "Oxidized Copper Grate", "block.minecraft.oxidized_copper_trapdoor": "Oxidized Copper Trapdoor", "block.minecraft.polished_tuff": "Polished Tuff", "block.minecraft.polished_tuff_slab": "Polished Tuff Slab", "block.minecraft.polished_tuff_stairs": "Polished Tuff Stairs", "block.minecraft.polished_tuff_wall": "Polished Tuff Wall", "block.minecraft.short_grass": "Short Grass", "block.minecraft.trial_spawner": "Trial Spawner", "block.minecraft.tuff_brick_slab": "Tuff Brick Slab", "block.minecraft.tuff_brick_stairs": "Tuff Brick Stairs", "block.minecraft.tuff_brick_wall": "Tuff Brick Wall", "block.minecraft.tuff_bricks": "Tuff Bricks", "block.minecraft.tuff_slab": "Tuff Slab", "block.minecraft.tuff_stairs": "Tuff Stairs", "block.minecraft.tuff_wall": "Tuff Wall", "block.minecraft.waxed_chiseled_copper": "Waxed Chiseled Copper", "block.minecraft.waxed_copper_bulb": "Waxed Copper Bulb", "block.minecraft.waxed_copper_door": "Waxed Copper Door", "block.minecraft.waxed_copper_grate": "Waxed Copper Grate", "block.minecraft.waxed_copper_trapdoor": "Waxed Copper Trapdoor", "block.minecraft.waxed_exposed_chiseled_copper": "Waxed Exposed Chiseled Copper", "block.minecraft.waxed_exposed_copper_bulb": "Waxed Exposed Copper Bulb", "block.minecraft.waxed_exposed_copper_door": "Waxed Exposed Copper Door", "block.minecraft.waxed_exposed_copper_grate": "Waxed Exposed Copper Grate", "block.minecraft.waxed_exposed_copper_trapdoor": "Waxed Exposed Copper Trapdoor", "block.minecraft.waxed_oxidized_chiseled_copper": "Waxed Oxidized Chiseled Copper", "block.minecraft.waxed_oxidized_copper_bulb": "Waxed Oxidized Copper Bulb", "block.minecraft.waxed_oxidized_copper_door": "Waxed Oxidized Copper Door", "block.minecraft.waxed_oxidized_copper_grate": "Waxed Oxidized Copper Grate", "block.minecraft.waxed_oxidized_copper_trapdoor": "Waxed Oxidized Copper Trapdoor", "block.minecraft.waxed_weathered_chiseled_copper": "Waxed Weathered Chiseled Copper", "block.minecraft.waxed_weathered_copper_bulb": "Waxed Weathered Copper Bulb", "block.minecraft.waxed_weathered_copper_door": "Waxed Weathered Copper Door", "block.minecraft.waxed_weathered_copper_grate": "Waxed Weathered Copper Grate", "block.minecraft.waxed_weathered_copper_trapdoor": "Waxed Weathered Copper Trapdoor", "block.minecraft.weathered_chiseled_copper": "Weathered Chiseled Copper", "block.minecraft.weathered_copper_bulb": "Weathered Copper Bulb", "block.minecraft.weathered_copper_door": "Weathered Copper Door", "block.minecraft.weathered_copper_grate": "Weathered Copper Grate", "block.minecraft.weathered_copper_trapdoor": "Weathered Copper Trapdoor", "command.forkLimit": "Maximum number of contexts (%s) reached", "commands.debug.function.noReturnRun": "Tracing can't be used with return run", "commands.execute.function.instantiationFailure": "Failed to instantiate function %s: %s", "commands.function.instantiationFailure": "Failed to instantiate function %s: %s", "commands.function.result": "Function %s returned %s", "commands.function.scheduled.multiple": "Running functions %s", "commands.function.scheduled.no_functions": "Can't find any functions for name %s", "commands.function.scheduled.single": "Running function %s", "commands.kick.owner.failed": "Cannot kick server owner in LAN game", "commands.kick.singleplayer.failed": "Cannot kick in an offline singleplayer game", "commands.scoreboard.objectives.modify.displayAutoUpdate.disable": "Disabled display auto-update for objective %s", "commands.scoreboard.objectives.modify.displayAutoUpdate.enable": "Enabled display auto-update for objective %s", "commands.scoreboard.objectives.modify.objectiveFormat.clear": "Cleared default number format of objective %s", "commands.scoreboard.objectives.modify.objectiveFormat.set": "Changed default number format of objective %s", "commands.scoreboard.players.display.name.clear.success.multiple": "Cleared display name for %s entities in %s", "commands.scoreboard.players.display.name.clear.success.single": "Cleared display name for %s in %s", "commands.scoreboard.players.display.name.set.success.multiple": "Changed display name to %s for %s entities in %s", "commands.scoreboard.players.display.name.set.success.single": "Changed display name to %s for %s in %s", "commands.scoreboard.players.display.numberFormat.clear.success.multiple": "Cleared number format for %s entities in %s", "commands.scoreboard.players.display.numberFormat.clear.success.single": "Cleared number format for %s in %s", "commands.scoreboard.players.display.numberFormat.set.success.multiple": "Changed number format for %s entities in %s", "commands.scoreboard.players.display.numberFormat.set.success.single": "Changed number format for %s in %s", "commands.tick.query.percentiles": "Percentiles: P50: %sms P95: %sms P99: %sms, sample: %s", "commands.tick.query.rate.running": "Target tick rate: %s per second.\nAverage time per tick: %sms (Target: %sms)", "commands.tick.query.rate.sprinting": "Target tick rate: %s per second (ignored, reference only).\nAverage time per tick: %sms", "commands.tick.rate.success": "Set the target tick rate to %s per second", "commands.tick.sprint.report": "Sprint completed with %s ticks per second, or %s ms per tick", "commands.tick.sprint.stop.fail": "No tick sprint in progress", "commands.tick.sprint.stop.success": "A tick sprint interrupted", "commands.tick.status.frozen": "The game is frozen", "commands.tick.status.lagging": "The game is running, but can't keep up with the target tick rate", "commands.tick.status.running": "The game runs normally", "commands.tick.status.sprinting": "The game is sprinting", "commands.tick.step.fail": "Unable to step the game - the game must be frozen first", "commands.tick.step.stop.fail": "No tick step in progress", "commands.tick.step.stop.success": "A tick step interrupted", "commands.tick.step.success": "Stepping %s tick(s)", "container.crafter": "Crafter", "dataPack.update_1_21.description": "New features and content for Minecraft 1.21", "dataPack.update_1_21.name": "Update 1.21", "download.pack.failed": "%s out of %s packs failed to download", "download.pack.progress.bytes": "Progress: %s (total size unknown)", "download.pack.progress.percent": "Progress: %s%%", "download.pack.title": "Downloading resource pack %s/%s", "entity.minecraft.breeze": "Breeze", "entity.minecraft.wind_charge": "Wind Charge", "gamerule.maxCommandForkCount": "Command context limit", "gamerule.maxCommandForkCount.description": "Maximum number of contexts that can be used by commands like 'execute as'.", "gamerule.playersNetherPortalCreativeDelay": "Player's Nether portal delay in creative mode", "gamerule.playersNetherPortalCreativeDelay.description": "Time (in ticks) that a creative mode player needs to stand in a Nether portal before changing dimensions.", "gamerule.playersNetherPortalDefaultDelay": "Player's Nether portal delay in non-creative mode", "gamerule.playersNetherPortalDefaultDelay.description": "Time (in ticks) that a non-creative mode player needs to stand in a Nether portal before changing dimensions.", "gamerule.projectilesCanBreakBlocks": "Projectiles can break blocks", "gamerule.projectilesCanBreakBlocks.description": "Controls whether impact projectiles will destroy blocks that are destructible by them.", "gui.loadingMinecraft": "Loading Minecraft", "gui.togglable_slot": "Click to disable slot", "item.minecraft.breeze_spawn_egg": "Breeze Spawn Egg", "item.minecraft.trial_key": "Trial Key", "jigsaw_block.placement_priority": "Placement Priority:", "jigsaw_block.placement_priority.tooltip": "When this Jigsaw block connects to a piece, this is the order in which that piece is processed for connections in the wider structure.\n\nPieces will be processed in descending priority with insertion order breaking ties.", "jigsaw_block.selection_priority": "Selection Priority:", "jigsaw_block.selection_priority.tooltip": "When the parent piece is being processed for connections, this is the order in which this Jigsaw block attempts to connect to its target piece.\n\nJigsaws will be processed in descending priority with random ordering breaking ties.", "mco.account.privacy.info.button": "Read more about GDPR", "mco.account.privacy.information": "Mojang implements certain procedures to help protect children and their privacy including complying with the Children�s Online Privacy Protection Act (COPPA) and General Data Protection Regulation (GDPR).\n\nYou may need to obtain parental consent before accessing your Realms account.", "mco.compatibility.downgrade": "Downgrade", "mco.compatibility.downgrade.description": "This world was last played in version %s; you are on version %s. Downgrading a world could cause corruption - we cannot guarantee that it will load or work.\n\nA backup of your world will be saved under \"World backups\". Please restore your world if needed.", "mco.compatibility.unverifiable.message": "The version this world was last played in could not be verified. If the world gets upgraded or downgraded, a backup will be automatically created and saved under \"World backups\".", "mco.compatibility.unverifiable.title": "Compatibility not verifiable", "mco.compatibility.upgrade": "Upgrade", "mco.compatibility.upgrade.description": "This world was last played in version %s; you are on version %s.\n\nA backup of your world will be saved under \"World backups\". Please restore your world if needed.", "mco.compatibility.upgrade.title": "Do you really want to upgrade your world?", "mco.notification.transferSubscription.buttonText": "Transfer Now", "mco.notification.transferSubscription.message": "Java Realms subscriptions are moving to the Microsoft Store. Do not let your subscription expire!\nTransfer now and get 30 days of Realms for free.\nGo to Profile on minecraft.net to transfer your subscription.", "mco.selectServer.minigameName": "Minigame: %s", "mco.snapshot.createSnapshotPopup.text": "You are about to create a free Snapshot Realm that will be paired with your paid Realms subscription. This new Snapshot Realm will be accessible for as long as the paid subscription is active. Your paid Realm will not be affected.", "mco.snapshot.createSnapshotPopup.title": "Create Snapshot Realm?", "mco.snapshot.creating": "Creating Snapshot Realm...", "mco.snapshot.description": "Paired with \"%s\"", "mco.snapshot.friendsRealm.downgrade": "You need to be on version %s to join this Realm", "mco.snapshot.friendsRealm.upgrade": "%s needs to upgrade their Realm before you can play from this version", "mco.snapshot.paired": "This Snapshot Realm is paired with \"%s\"", "mco.snapshot.parent.tooltip": "Use the latest release of Minecraft to play on this Realm", "mco.snapshot.start": "Start free Snapshot Realm", "mco.snapshot.subscription.info": "This is a Snapshot Realm that is paired to the subscription of your Realm '%s'. It will stay active for as long as its paired Realm is.", "mco.snapshot.tooltip": "Use Snapshot Realms to get a sneak peek at upcoming versions of Minecraft, which might include new features and other changes.\n\nYou can find your normal Realms in the release version of the game.", "mco.snapshotRealmsPopup.message": "Realms are now available in Snapshots starting with Snapshot 23w41a. Every Realms subscription comes with a free Snapshot Realm that is separate from your normal Java Realm!", "mco.snapshotRealmsPopup.title": "Realms now available in Snapshots", "mco.snapshotRealmsPopup.urlText": "Learn More", "mco.version": "Version: %s", "options.accessibility.narrator_hotkey.mac.tooltip": "Allows the Narrator to be toggled on and off with 'Cmd+B'", "options.hideSplashTexts": "Hide Splash Texts", "options.hideSplashTexts.tooltip": "Hides the yellow splash text in the main menu.", "recover_world.bug_tracker": "Report a Bug", "recover_world.button": "Attempt to Recover", "recover_world.done.failed": "Failed to recover from previous state.", "recover_world.done.success": "Recovery was successful!", "recover_world.done.title": "Recovery done", "recover_world.issue.missing_file": "Missing file", "recover_world.issue.none": "No issues", "recover_world.message": "The following issues occurred while trying to read world folder \"%s\".\nIt might be possible to restore the world from an older state or you can report this issue on the bug tracker.", "recover_world.no_fallback": "No state to recover from available", "recover_world.restore": "Attempt to Restore", "recover_world.restoring": "Attempting to restore world...", "recover_world.state_entry": "State from %s: ", "recover_world.state_entry.unknown": "unknown", "recover_world.title": "Failed to load world", "recover_world.warning": "Failed to load world summary", "selectWorld.incompatible.description": "This world cannot be opened in this version.\nIt was last played in version %s.", "selectWorld.incompatible.info": "Incompatible version: %s", "selectWorld.incompatible.title": "Incompatible version", "selectWorld.incompatible.tooltip": "This world cannot be opened because it was created by an incompatible version.", "selectWorld.resource_load": "Preparing Resources...", "subtitles.block.copper_bulb.turn_off": "Copper Bulb turns off", "subtitles.block.copper_bulb.turn_on": "Copper Bulb turns on", "subtitles.block.copper_trapdoor.close": "Trapdoor closes", "subtitles.block.copper_trapdoor.open": "Trapdoor opens", "subtitles.block.crafter.craft": "Crafter crafts", "subtitles.block.crafter.fail": "Crafter fails crafting", "subtitles.block.decorated_pot.insert": "Decorated Pot fills", "subtitles.block.decorated_pot.insert_fail": "Decorated Pot wobbles", "subtitles.block.hanging_sign.waxed_interact_fail": "Sign wobbles", "subtitles.block.trial_spawner.ambient": "Trial Spawner crackles", "subtitles.block.trial_spawner.close_shutter": "Trial Spawner closes", "subtitles.block.trial_spawner.detect_player": "Trial Spawner charges up", "subtitles.block.trial_spawner.eject_item": "Trial Spawner ejects items", "subtitles.block.trial_spawner.open_shutter": "Trial Spawner opens", "subtitles.block.trial_spawner.spawn_mob": "Mob spawns", "subtitles.entity.breeze.death": "Breeze dies", "subtitles.entity.breeze.hurt": "Breeze hurts", "subtitles.entity.breeze.idle_air": "Breeze flies", "subtitles.entity.breeze.idle_ground": "Breeze whirs", "subtitles.entity.breeze.inhale": "Breeze inhales", "subtitles.entity.breeze.jump": "Breeze jumps", "subtitles.entity.breeze.land": "Breeze lands", "subtitles.entity.breeze.shoot": "Breeze shoots", "subtitles.entity.breeze.slide": "Breeze slides", "subtitles.entity.generic.wind_burst": "Wind Charge bursts", "subtitles.entity.parrot.imitate.breeze": "Parrot whirs", "subtitles.entity.player.teleport": "Player teleports", "symlink_warning.more_info": "More Information", "telemetry_info.opt_in.description": "I consent to sending optional telemetry data", "telemetry.event.optional.disabled": "%s (Optional) - Disabled" }, "1.20": { "advancements.adventure.craft_decorated_pot_using_only_sherds.description": "Make a Decorated Pot out of 4 Pottery Sherds", "advancements.adventure.craft_decorated_pot_using_only_sherds.title": "Careful Restoration", "advancements.adventure.read_power_from_chiseled_bookshelf.description": "Read the power signal of a Chiseled Bookshelf using a Comparator", "advancements.adventure.read_power_from_chiseled_bookshelf.title": "The Power of Books", "advancements.adventure.salvage_sherd.description": "Brush a Suspicious block to obtain a Pottery Sherd", "advancements.adventure.salvage_sherd.title": "Respecting the Remnants", "advancements.adventure.trim_with_all_exclusive_armor_patterns.description": "Apply these smithing templates at least once: Spire, Snout, Rib, Ward, Silence, Vex, Tide, Wayfinder", "advancements.adventure.trim_with_all_exclusive_armor_patterns.title": "Smithing with Style", "advancements.adventure.trim_with_any_armor_pattern.description": "Craft a trimmed armor at a Smithing Table", "advancements.adventure.trim_with_any_armor_pattern.title": "Crafting a New Look", "advancements.husbandry.feed_snifflet.description": "Feed a Snifflet", "advancements.husbandry.feed_snifflet.title": "Little Sniffs", "advancements.husbandry.obtain_sniffer_egg.description": "Obtain a Sniffer Egg", "advancements.husbandry.obtain_sniffer_egg.title": "Smells Interesting", "advancements.husbandry.plant_any_sniffer_seed.description": "Plant any Sniffer seed", "advancements.husbandry.plant_any_sniffer_seed.title": "Planting the Past", "block.minecraft.calibrated_sculk_sensor": "Calibrated Sculk Sensor", "block.minecraft.pitcher_crop": "Pitcher Crop", "block.minecraft.pitcher_plant": "Pitcher Plant", "block.minecraft.sniffer_egg": "Sniffer Egg", "block.minecraft.suspicious_gravel": "Suspicious Gravel", "commands.data.modify.invalid_substring": "Invalid substring indices: %s to %s", "commands.function.success.multiple.result": "Executed %s functions", "commands.function.success.single.result": "Function '%2$s' returned %1$s", "death.attack.genericKill": "%1$s was killed", "death.attack.genericKill.player": "%1$s was killed whilst fighting %2$s", "death.attack.outsideBorder": "%1$s left the confines of this world", "death.attack.outsideBorder.player": "%1$s left the confines of this world whilst fighting %2$s", "disconnect.ignoring_status_request": "Ignoring status request", "gui.toRealms": "Back to Realms List", "gui.toWorld": "Back to World List", "item.minecraft.angler_pottery_shard": "Angler Pottery Shard", "item.minecraft.angler_pottery_sherd": "Angler Pottery Sherd", "item.minecraft.archer_pottery_shard": "Archer Pottery Shard", "item.minecraft.archer_pottery_sherd": "Archer Pottery Sherd", "item.minecraft.arms_up_pottery_shard": "Arms Up Pottery Shard", "item.minecraft.arms_up_pottery_sherd": "Arms Up Pottery Sherd", "item.minecraft.blade_pottery_shard": "Blade Pottery Shard", "item.minecraft.blade_pottery_sherd": "Blade Pottery Sherd", "item.minecraft.brewer_pottery_shard": "Brewer Pottery Shard", "item.minecraft.brewer_pottery_sherd": "Brewer Pottery Sherd", "item.minecraft.burn_pottery_shard": "Burn Pottery Shard", "item.minecraft.burn_pottery_sherd": "Burn Pottery Sherd", "item.minecraft.danger_pottery_shard": "Danger Pottery Shard", "item.minecraft.danger_pottery_sherd": "Danger Pottery Sherd", "item.minecraft.explorer_pottery_shard": "Explorer Pottery Shard", "item.minecraft.explorer_pottery_sherd": "Explorer Pottery Sherd", "item.minecraft.friend_pottery_shard": "Friend Pottery Shard", "item.minecraft.friend_pottery_sherd": "Friend Pottery Sherd", "item.minecraft.heart_pottery_shard": "Heart Pottery Shard", "item.minecraft.heart_pottery_sherd": "Heart Pottery Sherd", "item.minecraft.heartbreak_pottery_shard": "Heartbreak Pottery Shard", "item.minecraft.heartbreak_pottery_sherd": "Heartbreak Pottery Sherd", "item.minecraft.howl_pottery_shard": "Howl Pottery Shard", "item.minecraft.howl_pottery_sherd": "Howl Pottery Sherd", "item.minecraft.miner_pottery_shard": "Miner Pottery Shard", "item.minecraft.miner_pottery_sherd": "Miner Pottery Sherd", "item.minecraft.mourner_pottery_shard": "Mourner Pottery Shard", "item.minecraft.mourner_pottery_sherd": "Mourner Pottery Sherd", "item.minecraft.music_disc_relic": "Music Disc", "item.minecraft.music_disc_relic.desc": "Aaron Cherof - Relic", "item.minecraft.pitcher_plant": "Pitcher Plant", "item.minecraft.pitcher_pod": "Pitcher Pod", "item.minecraft.plenty_pottery_shard": "Plenty Pottery Shard", "item.minecraft.plenty_pottery_sherd": "Plenty Pottery Sherd", "item.minecraft.prize_pottery_shard": "Prize Pottery Shard", "item.minecraft.prize_pottery_sherd": "Prize Pottery Sherd", "item.minecraft.sheaf_pottery_shard": "Sheaf Pottery Shard", "item.minecraft.sheaf_pottery_sherd": "Sheaf Pottery Sherd", "item.minecraft.shelter_pottery_shard": "Shelter Pottery Shard", "item.minecraft.shelter_pottery_sherd": "Shelter Pottery Sherd", "item.minecraft.skull_pottery_shard": "Skull Pottery Shard", "item.minecraft.skull_pottery_sherd": "Skull Pottery Sherd", "item.minecraft.snort_pottery_shard": "Snort Pottery Shard", "item.minecraft.snort_pottery_sherd": "Snort Pottery Sherd", "mco.backup.entry": "Backup (%s)", "mco.backup.entry.description": "Description", "mco.backup.entry.enabledPack": "Enabled Pack", "mco.backup.entry.gameDifficulty": "Game Difficulty", "mco.backup.entry.gameMode": "Game Mode", "mco.backup.entry.gameServerVersion": "Game Server Version", "mco.backup.entry.name": "Name", "mco.backup.entry.seed": "Seed", "mco.backup.entry.templateName": "Template Name", "mco.backup.entry.undefined": "Undefined Change", "mco.backup.entry.uploaded": "Uploaded", "mco.backup.entry.worldType": "World Type", "mco.backup.info.title": "Changes from last backup", "mco.backup.unknown": "UNKNOWN", "mco.configure.world.invited.number": "Invited (%s)", "mco.configure.world.minigame": "Current: %s", "mco.configure.world.subscription.remaining.months.days": "%1$s month(s), %2$s day(s)", "mco.configure.world.subscription.remaining.months": "%1$s month(s)", "mco.configure.world.subscription.remaining.days": "%1$s day(s)", "mco.configure.world.uninvite.player": "Are you sure that you want to uninvite '%s'?", "mco.download.percent": "%s %%", "mco.download.resourcePack.fail": "Failed to download resource pack!", "mco.download.speed": "(%s/s)", "mco.errorMessage.6005": "World locked", "mco.errorMessage.6006": "World is out of date", "mco.errorMessage.6007": "User in too many Realms", "mco.errorMessage.6008": "Invalid Realm name", "mco.errorMessage.6009": "Invalid Realm description", "mco.errorMessage.generic": "An error occurred: ", "mco.errorMessage.realmsService": "An error occurred (%s):", "mco.errorMessage.realmsService.realmsError": "Realms (%s):", "mco.info": "Info!", "mco.question": "Question", "mco.upload.entry.id": "%1$s (%2$s)", "mco.upload.entry.cheats": "%1$s, %2$s", "mco.time.now": "right now", "mco.time.secondsAgo": "%1$s second(s) ago", "mco.time.minutesAgo": "%1$s minute(s) ago", "mco.time.hoursAgo": "%1$s hour(s) ago", "mco.time.daysAgo": "%1$s day(s) ago", "mco.warning": "Warning!", "mco.worldSlot.minigame": "Minigame", "multiplayer.disconnect.invalid_public_key_signature.new": "Invalid signature for profile public key.\nTry restarting your game.", "quickplay.error.invalid_identifier": "Could not find world with the provided identifier", "quickplay.error.realm_connect": "Could not connect to Realm", "quickplay.error.realm_permission": "Lacking permission to connect to this Realm", "quickplay.error.title": "Failed to Quick Play", "subtitles.block.amethyst_block.resonate": "Amethyst resonates", "subtitles.block.sign.waxed_interact_fail": "Sign wobbles", "subtitles.block.sniffer_egg.crack": "Sniffer Egg cracks", "subtitles.block.sniffer_egg.hatch": "Sniffer Egg hatches", "subtitles.block.sniffer_egg.plop": "Sniffer plops", "subtitles.entity.sniffer.egg_crack": "Sniffer Egg cracks", "subtitles.entity.sniffer.egg_hatch": "Sniffer Egg hatches", "subtitles.item.brush.brushing.generic": "Brushing", "subtitles.item.brush.brushing.gravel": "Brushing Gravel", "subtitles.item.brush.brushing.gravel.complete": "Brushing Gravel completed", "subtitles.item.brush.brushing.sand": "Brushing Sand", "subtitles.item.brush.brushing.sand.complete": "Brushing Sand completed", "telemetry.event.advancement_made.description": "Understanding the context behind receiving an advancement can help us better understand and improve the progression of the game.", "telemetry.event.advancement_made.title": "Advancement Made", "telemetry.event.game_load_times.description": "This event can help us figure out where startup performance improvements are needed by measuring the execution times of the startup phases.", "telemetry.event.game_load_times.title": "Game Load Times", "telemetry.property.advancement_game_time.title": "Game Time (Ticks)", "telemetry.property.advancement_id.title": "Advancement ID", "telemetry.property.launcher_name.title": "Launcher Name", "telemetry.property.load_time_bootstrap_ms.title": "Bootstrap Time (Milliseconds)", "telemetry.property.load_time_loading_overlay_ms.title": "Time in Loading Screen (Milliseconds)", "telemetry.property.load_time_pre_window_ms.title": "Time Before Window Opens (Milliseconds)", "telemetry.property.load_time_total_time_ms.title": "Total Load Time (Milliseconds)", "telemetry.property.realms_map_content.title": "Realms Map Content (Minigame Name)", "trim_pattern.minecraft.host": "Host Armor Trim", "trim_pattern.minecraft.raiser": "Raiser Armor Trim", "trim_pattern.minecraft.shaper": "Shaper Armor Trim", "trim_pattern.minecraft.silence": "Silence Armor Trim", "trim_pattern.minecraft.wayfinder": "Wayfinder Armor Trim" }, "1.19.4": { "accessibility.onboarding.screen.narrator": "Press enter to enable the narrator", "accessibility.onboarding.screen.title": "Welcome to Minecraft!\n\nWould you like to enable the Narrator or visit the Accessibility Settings?", "argument.time.tick_count_too_low": "Tick count must not be less than %s, found %s", "biome.minecraft.cherry_grove": "Cherry Grove", "block.minecraft.cherry_button": "Cherry Button", "block.minecraft.cherry_door": "Cherry Door", "block.minecraft.cherry_fence": "Cherry Fence", "block.minecraft.cherry_fence_gate": "Cherry Fence Gate", "block.minecraft.cherry_hanging_sign": "Cherry Hanging Sign", "block.minecraft.cherry_leaves": "Cherry Leaves", "block.minecraft.cherry_log": "Cherry Log", "block.minecraft.cherry_planks": "Cherry Planks", "block.minecraft.cherry_pressure_plate": "Cherry Pressure Plate", "block.minecraft.cherry_sapling": "Cherry Sapling", "block.minecraft.cherry_sign": "Cherry Sign", "block.minecraft.cherry_slab": "Cherry Slab", "block.minecraft.cherry_stairs": "Cherry Stairs", "block.minecraft.cherry_trapdoor": "Cherry Trapdoor", "block.minecraft.cherry_wall_hanging_sign": "Cherry Wall Hanging Sign", "block.minecraft.cherry_wall_sign": "Cherry Wall Sign", "block.minecraft.cherry_wood": "Cherry Wood", "block.minecraft.decorated_pot": "Decorated Pot", "block.minecraft.pink_petals": "Pink Petals", "block.minecraft.potted_cherry_sapling": "Potted Cherry Sapling", "block.minecraft.potted_torchflower": "Potted Torchflower", "block.minecraft.stripped_cherry_log": "Stripped Cherry Log", "block.minecraft.stripped_cherry_wood": "Stripped Cherry Wood", "block.minecraft.suspicious_sand": "Suspicious Sand", "block.minecraft.torchflower": "Torchflower", "block.minecraft.torchflower_crop": "Torchflower Crop", "commands.damage.invulnerable": "Target is invulnerable to the given damage type", "commands.damage.success": "Applied %s damage to %s", "commands.data.modify.expected_value": "Expected value, got: %s", "commands.ride.already_riding": "%s is already riding %s", "commands.ride.dismount.success": "%s stopped riding %s", "commands.ride.mount.failure.cant_ride_players": "Players can't be ridden", "commands.ride.mount.failure.generic": "%s couldn't start riding %s", "commands.ride.mount.failure.loop": "Can't mount entity on itself or any of its passengers", "commands.ride.mount.failure.wrong_dimension": "Can't mount entity in different dimension", "commands.ride.mount.success": "%s started riding %s", "commands.ride.not_riding": "%s is not riding any vehicle", "container.upgrade.error_tooltip": "Your item cannot be upgraded in this way", "container.upgrade.missing_template_tooltip": "Put a Smithing Template here", "controls.keybinds.duplicateKeybinds": "This key is also used for:\n%s", "createWorld.tab.game.title": "Game", "createWorld.tab.more.title": "More", "createWorld.tab.world.title": "World", "credits_and_attribution.button.attribution": "Attribution", "credits_and_attribution.button.credits": "Credits", "credits_and_attribution.button.licenses": "Licenses", "credits_and_attribution.screen.title": "Credits and Attribution", "dataPack.bundle.name": "Bundles", "dataPack.update_1_20.name": "Update 1.20", "datapackFailure.safeMode.failed.title": "Failed to load world in Safe Mode.", "datapackFailure.safeMode.failed.description": "This world contains invalid or corrupted save data.", "debug.dump_dynamic_textures": "Saved dynamic textures to %s", "debug.dump_dynamic_textures.help": "F3 + S = Dump dynamic textures", "effect.duration.infinite": "?", "entity.minecraft.block_display": "Block Display", "entity.minecraft.falling_block_type": "Falling %s", "entity.minecraft.interaction": "Interaction", "entity.minecraft.item_display": "Item Display", "entity.minecraft.sniffer": "Sniffer", "entity.minecraft.text_display": "Text Display", "gamerule.commandModificationBlockLimit": "Command Modification Block Limit", "gamerule.commandModificationBlockLimit.description": "Number of blocks that can be changed at once by one command, such as fill or clone.", "gamerule.doVinesSpread": "Vines spread", "gamerule.doVinesSpread.description": "Controls whether or not the Vines block spreads randomly to adjacent blocks. Does not affect other type of vine blocks such as Weeping Vines, Twisting Vines, etc.", "gui.banned.reason.defamation_impersonation_false_information": "Impersonation or sharing information to exploit or mislead others", "gui.banned.reason.drugs": "References to illegal drugs", "gui.banned.reason.extreme_violence_or_gore": "Depictions of real-life excessive violence or gore", "gui.banned.reason.false_reporting": "Excessive false or inaccurate reports", "gui.banned.reason.fraud": "Fraudulent acquisition or use of content", "gui.banned.reason.generic_violation": "Violating Community Standards", "gui.banned.reason.harassment_or_bullying": "Abusive language used in a directed, harmful manner", "gui.banned.reason.hate_speech": "Hate speech or discrimination", "gui.banned.reason.hate_terrorism_notorious_figure": "References to hate groups, terrorist organizations, or notorious figures", "gui.banned.reason.imminent_harm_to_person_or_property": "Intent to cause real-life harm to persons or property", "gui.banned.reason.nudity_or_pornography": "Displaying lewd or pornographic material", "gui.banned.reason.sexually_inappropriate": "Topics or content of a sexual nature", "gui.banned.reason.spam_or_advertising": "Spam or advertising", "gui.continue": "Continue", "gui.narrate.tab": "%s tab", "item.minecraft.brush": "Brush", "item.minecraft.cherry_boat": "Cherry Boat", "item.minecraft.cherry_chest_boat": "Cherry Boat with Chest", "item.minecraft.pottery_shard_archer": "Archer Pottery Shard", "item.minecraft.pottery_shard_arms_up": "Arms Up Pottery Shard", "item.minecraft.pottery_shard_prize": "Prize Pottery Shard", "item.minecraft.pottery_shard_skull": "Skull Pottery Shard", "item.minecraft.smithing_template": "Smithing Template", "item.minecraft.smithing_template.applies_to": "Applies to:", "item.minecraft.smithing_template.armor_trim.additions_slot_description": "Put an ingot or crystal here", "item.minecraft.smithing_template.armor_trim.applies_to": "Armor", "item.minecraft.smithing_template.armor_trim.base_slot_description": "Put a piece of armor here", "item.minecraft.smithing_template.armor_trim.ingredients": "Ingots & Crystals", "item.minecraft.smithing_template.ingredients": "Ingredients:", "item.minecraft.smithing_template.netherite_upgrade.additions_slot_description": "Put a Netherite Ingot here", "item.minecraft.smithing_template.netherite_upgrade.applies_to": "Diamond Equipment", "item.minecraft.smithing_template.netherite_upgrade.base_slot_description": "Put a piece of Diamond armor, weapon or tool here", "item.minecraft.smithing_template.netherite_upgrade.ingredients": "Netherite Ingot", "item.minecraft.smithing_template.upgrade": "Upgrade: ", "item.minecraft.sniffer_spawn_egg": "Sniffer Spawn Egg", "item.minecraft.torchflower_seeds": "Torchflower Seeds", "mco.notification.dismiss": "Dismiss", "mco.notification.visitUrl.message.default": "Please visit the link below", "mco.notification.visitUrl.buttonText.default": "Open link", "multiplayer.lan.server_found": "New server found: %s", "multiplayer.player.list.narration": "Online players: %s", "multiplayer.status.motd.narration": "Message of the day: %s", "multiplayer.status.online": "Online", "multiplayer.status.ping.narration": "Ping %s milliseconds", "multiplayer.status.player_count.narration": "%s out of %s players online", "multiplayer.status.version.narration": "Server version: %s", "narration.tab_navigation.usage": "Press Ctrl and Tab to switch between tabs", "narrator.position.tab": "Selected tab %s out of %s", "narrator.ready_to_play": "Ready to play", "options.accessibility.high_contrast": "High Contrast", "options.accessibility.high_contrast.tooltip": "Enhances the contrast of UI elements", "options.accessibility.high_contrast.error.tooltip": "High Contrast resource pack is not available", "options.credits_and_attribution": "Credits & Attribution...", "options.damageTiltStrength": "Damage Tilt", "options.damageTiltStrength.tooltip": "The amount of camera shake caused by being hurt.", "options.difficulty.easy.info": "Hostile mobs spawn but deal less damage. Hunger bar depletes and drains health down to 5 hearts.", "options.difficulty.hard.info": "Hostile mobs spawn and deal more damage. Hunger bar depletes and drains all health.", "options.difficulty.normal.info": "Hostile mobs spawn and deal standard damage. Hunger bar depletes and drains health down to half a heart.", "options.difficulty.peaceful.info": "No hostile mobs and only some neutral mobs spawn. Hunger bar doesn't deplete and health replenishes over time.", "options.glintSpeed": "Glint Speed", "options.glintSpeed.tooltip": "Controls how fast the visual glint shimmers across enchanted items.", "options.glintStrength": "Glint Strength", "options.glintStrength.tooltip": "Controls how transparent the visual glint is on enchanted items.", "options.multiplier": "%sx", "options.notifications.display_time": "Notification Time", "options.notifications.display_time.tooltip": "Affects the length of time that all notifications stay visible on the screen.", "painting.dimensions": "%sx%s", "painting.minecraft.alban.author": "Kristoffer Zetterstrand", "painting.minecraft.alban.title": "Albanian", "painting.minecraft.aztec.author": "Kristoffer Zetterstrand", "painting.minecraft.aztec.title": "de_aztec", "painting.minecraft.aztec2.author": "Kristoffer Zetterstrand", "painting.minecraft.aztec2.title": "de_aztec", "painting.minecraft.bomb.author": "Kristoffer Zetterstrand", "painting.minecraft.bomb.title": "Target Successfully Bombed", "painting.minecraft.burning_skull.author": "Kristoffer Zetterstrand", "painting.minecraft.burning_skull.title": "Skull On Fire", "painting.minecraft.bust.author": "Kristoffer Zetterstrand", "painting.minecraft.bust.title": "Bust", "painting.minecraft.courbet.author": "Kristoffer Zetterstrand", "painting.minecraft.courbet.title": "Bonjour Monsieur Courbet", "painting.minecraft.creebet.author": "Kristoffer Zetterstrand", "painting.minecraft.creebet.title": "Creebet", "painting.minecraft.donkey_kong.author": "Kristoffer Zetterstrand", "painting.minecraft.donkey_kong.title": "Kong", "painting.minecraft.earth.author": "Mojang", "painting.minecraft.earth.title": "Earth", "painting.minecraft.fighters.author": "Kristoffer Zetterstrand", "painting.minecraft.fighters.title": "Fighters", "painting.minecraft.fire.author": "Mojang", "painting.minecraft.fire.title": "Fire", "painting.minecraft.graham.author": "Kristoffer Zetterstrand", "painting.minecraft.graham.title": "Graham", "painting.minecraft.kebab.author": "Kristoffer Zetterstrand", "painting.minecraft.kebab.title": "Kebab med tre pepperoni", "painting.minecraft.match.author": "Kristoffer Zetterstrand", "painting.minecraft.match.title": "Match", "painting.minecraft.pigscene.author": "Kristoffer Zetterstrand", "painting.minecraft.pigscene.title": "Pigscene", "painting.minecraft.plant.author": "Kristoffer Zetterstrand", "painting.minecraft.plant.title": "Paradistr�d", "painting.minecraft.pointer.author": "Kristoffer Zetterstrand", "painting.minecraft.pointer.title": "Pointer", "painting.minecraft.pool.author": "Kristoffer Zetterstrand", "painting.minecraft.pool.title": "The Pool", "painting.minecraft.sea.author": "Kristoffer Zetterstrand", "painting.minecraft.sea.title": "Seaside", "painting.minecraft.skeleton.author": "Kristoffer Zetterstrand", "painting.minecraft.skeleton.title": "Mortal Coil", "painting.minecraft.skull_and_roses.author": "Kristoffer Zetterstrand", "painting.minecraft.skull_and_roses.title": "Skull and Roses", "painting.minecraft.stage.author": "Kristoffer Zetterstrand", "painting.minecraft.stage.title": "The Stage Is Set", "painting.minecraft.sunset.author": "Kristoffer Zetterstrand", "painting.minecraft.sunset.title": "sunset_dense", "painting.minecraft.void.author": "Kristoffer Zetterstrand", "painting.minecraft.void.title": "The void", "painting.minecraft.wanderer.author": "Kristoffer Zetterstrand", "painting.minecraft.wanderer.title": "Wanderer", "painting.minecraft.wasteland.author": "Kristoffer Zetterstrand", "painting.minecraft.wasteland.title": "Wasteland", "painting.minecraft.water.author": "Mojang", "painting.minecraft.water.title": "Water", "painting.minecraft.wind.author": "Mojang", "painting.minecraft.wind.title": "Wind", "painting.minecraft.wither.author": "Mojang", "painting.minecraft.wither.title": "Wither", "painting.random": "Random variant", "resourcePack.high_contrast.name": "High Contrast", "selectWorld.experiments": "Experiments", "selectWorld.experiments.info": "Experiments are potential new features. Be careful as things might break. Experiments can't be turned off after world creation.", "selectWorld.gameMode.adventure.info": "Same as Survival Mode, but blocks can't be added or removed.", "selectWorld.gameMode.creative.info": "Create, build, and explore without limits. You can fly, have endless materials, and can't be hurt by monsters.", "selectWorld.gameMode.hardcore.info": "Survival Mode locked to 'Hard' difficulty. You can't respawn if you die.", "selectWorld.gameMode.spectator.info": "You can look but don't touch.", "selectWorld.gameMode.survival.info": "Explore a mysterious world where you build, collect, craft, and fight monsters.", "selectWorld.targetFolder": "Save folder: %s", "subtitles.block.decorated_pot.shatter": "Pot shatters", "subtitles.entity.sniffer.death": "Sniffer dies", "subtitles.entity.sniffer.digging": "Sniffer digs", "subtitles.entity.sniffer.digging_stop": "Sniffer stands up", "subtitles.entity.sniffer.drop_seed": "Sniffer drops seed", "subtitles.entity.sniffer.eat": "Sniffer eats", "subtitles.entity.sniffer.happy": "Sniffer delights", "subtitles.entity.sniffer.hurt": "Sniffer hurts", "subtitles.entity.sniffer.idle": "Sniffer grunts", "subtitles.entity.sniffer.scenting": "Sniffer scents", "subtitles.entity.sniffer.searching": "Sniffer searches", "subtitles.entity.sniffer.sniffing": "Sniffer sniffs", "subtitles.entity.sniffer.step": "Sniffer steps", "subtitles.item.brush.brush_sand_completed": "Brushing sand completed", "subtitles.item.brush.brushing": "Brushing", "trim_material.minecraft.amethyst": "Amethyst Material", "trim_material.minecraft.copper": "Copper Material", "trim_material.minecraft.diamond": "Diamond Material", "trim_material.minecraft.emerald": "Emerald Material", "trim_material.minecraft.gold": "Gold Material", "trim_material.minecraft.iron": "Iron Material", "trim_material.minecraft.lapis": "Lapis Material", "trim_material.minecraft.netherite": "Netherite Material", "trim_material.minecraft.quartz": "Quartz Material", "trim_material.minecraft.redstone": "Redstone Material", "trim_pattern.minecraft.coast": "Coast Armor Trim", "trim_pattern.minecraft.dune": "Dune Armor Trim", "trim_pattern.minecraft.eye": "Eye Armor Trim", "trim_pattern.minecraft.rib": "Rib Armor Trim", "trim_pattern.minecraft.sentry": "Sentry Armor Trim", "trim_pattern.minecraft.snout": "Snout Armor Trim", "trim_pattern.minecraft.spire": "Spire Armor Trim", "trim_pattern.minecraft.tide": "Tide Armor Trim", "trim_pattern.minecraft.vex": "Vex Armor Trim", "trim_pattern.minecraft.ward": "Ward Armor Trim", "trim_pattern.minecraft.wild": "Wild Armor Trim", "upgrade.minecraft.netherite_upgrade": "Netherite Upgrade" }, "1.19.3": { "gui.chatReport.discard.draft": "Save as Draft", "gui.chatReport.draft.title": "Edit draft chat report?", "gui.chatReport.draft.content": "Would you like to continue editing the existing report or discard it and create a new one?", "gui.chatReport.draft.quittotitle.title": "You have a draft chat report that will be lost if you quit", "gui.chatReport.draft.quittotitle.content": "Would you like to continue editing it or discard it?", "gui.chatReport.draft.discard": "Discard", "gui.chatReport.draft.edit": "Continue Editing", "gui.chatSelection.join": "%s joined the chat", "selectWorld.experimental": "Experimental", "selectWorld.warning.experimental.title": "Warning! These settings are using experimental features", "selectWorld.warning.experimental.question": "These settings are experimental and could one day stop working. Do you wish to proceed?", "selectWorld.warning.deprecated.title": "Warning! These settings are using deprecated features", "selectWorld.warning.deprecated.question": "Some features used are deprecated and will stop working in the future. Do you wish to proceed?", "selectWorld.experimental.title": "Experimental Features Warning", "selectWorld.experimental.message": "Be careful!\nSome of the selected packs require features that are still under development. Your world might crash, break or not work with future updates.", "selectWorld.experimental.details": "Details", "selectWorld.experimental.details.title": "Experimental feature requirements", "selectWorld.experimental.details.entry": "Required experimental features: %s", "lanServer.port": "Port Number", "lanServer.port.unavailable": "Port not available.\nLeave the edit box empty or enter a different number between 1024 and 65535.", "lanServer.port.unavailable.new": "Port not available.\nLeave the edit box empty or enter a different number between %s and %s.", "lanServer.port.invalid": "Not a valid port.\nLeave the edit box empty or enter a number between 1024 and 65535.", "lanServer.port.invalid.new": "Not a valid port.\nLeave the edit box empty or enter a number between %s and %s.", "multiplayer.disconnect.expired_public_key": "Expired profile public key. Check that your system time is synchronized, and try restarting your game.", "chat.disabled.expiredProfileKey": "Chat disabled due to expired profile public key. Please try reconnecting.", "chat.disabled.chain_broken": "Chat disabled due to broken chain. Please try reconnecting.", "chat.disabled.missingProfileKey": "Chat disabled due to missing profile public key. Please try reconnecting.", "chat.tag.system": "Server message. Cannot be reported.", "chat.tag.system_single_player": "Server message.", "chat.filtered": "Filtered by the server.", "chat.deleted_marker": "This chat message has been deleted by the server.", "options.accessibility.panorama_speed": "Panorama Scroll Speed", "options.telemetry": "Telemetry Data", "options.telemetry.button": "Data Collection", "options.telemetry.state.none": "None", "options.telemetry.state.minimal": "Minimal", "options.telemetry.state.all": "All", "options.telemetry.button.tooltip": "\"%s\" includes only the required data.\n\"%s\" includes optional, as well as the required data.", "options.operatorItemsTab": "Operator Items Tab", "resourcePack.programmer_art.name": "Programmer Art", "resourcePack.vanilla.name": "Default", "dataPack.vanilla.name": "Default", "dataPack.bundle.description": "Enables experimental Bundle item", "dataPack.update_1_20.description": "New features and content for Minecraft 1.20", "hanging_sign.edit": "Edit Hanging Sign Message", "block.minecraft.bamboo_planks": "Bamboo Planks", "block.minecraft.bamboo_mosaic": "Bamboo Mosaic", "block.minecraft.bamboo_door": "Bamboo Door", "block.minecraft.bamboo_block": "Block of Bamboo", "block.minecraft.stripped_bamboo_block": "Block of Stripped Bamboo", "block.minecraft.bamboo_slab": "Bamboo Slab", "block.minecraft.bamboo_mosaic_slab": "Bamboo Mosaic Slab", "block.minecraft.chiseled_bookshelf": "Chiseled Bookshelf", "block.minecraft.spawner.desc1": "Interact with Spawn Egg:", "block.minecraft.spawner.desc2": "Sets Mob Type", "block.minecraft.bamboo_stairs": "Bamboo Stairs", "block.minecraft.bamboo_mosaic_stairs": "Bamboo Mosaic Stairs", "block.minecraft.bamboo_sign": "Bamboo Sign", "block.minecraft.bamboo_wall_sign": "Bamboo Wall Sign", "block.minecraft.oak_hanging_sign": "Oak Hanging Sign", "block.minecraft.spruce_hanging_sign": "Spruce Hanging Sign", "block.minecraft.birch_hanging_sign": "Birch Hanging Sign", "block.minecraft.acacia_hanging_sign": "Acacia Hanging Sign", "block.minecraft.jungle_hanging_sign": "Jungle Hanging Sign", "block.minecraft.crimson_hanging_sign": "Crimson Hanging Sign", "block.minecraft.warped_hanging_sign": "Warped Hanging Sign", "block.minecraft.dark_oak_hanging_sign": "Dark Oak Hanging Sign", "block.minecraft.mangrove_hanging_sign": "Mangrove Hanging Sign", "block.minecraft.bamboo_hanging_sign": "Bamboo Hanging Sign", "block.minecraft.oak_wall_hanging_sign": "Oak Wall Hanging Sign", "block.minecraft.spruce_wall_hanging_sign": "Spruce Wall Hanging Sign", "block.minecraft.birch_wall_hanging_sign": "Birch Wall Hanging Sign", "block.minecraft.acacia_wall_hanging_sign": "Acacia Wall Hanging Sign", "block.minecraft.jungle_wall_hanging_sign": "Jungle Wall Hanging Sign", "block.minecraft.dark_oak_wall_hanging_sign": "Dark Oak Wall Hanging Sign", "block.minecraft.mangrove_wall_hanging_sign": "Mangrove Wall Hanging Sign", "block.minecraft.crimson_wall_hanging_sign": "Crimson Wall Hanging Sign", "block.minecraft.warped_wall_hanging_sign": "Warped Wall Hanging Sign", "block.minecraft.bamboo_wall_hanging_sign": "Bamboo Wall Hanging Sign", "block.minecraft.bamboo_pressure_plate": "Bamboo Pressure Plate", "block.minecraft.bamboo_button": "Bamboo Button", "block.minecraft.bamboo_fence": "Bamboo Fence", "block.minecraft.bamboo_fence_gate": "Bamboo Fence Gate", "block.minecraft.bamboo_trapdoor": "Bamboo Trapdoor", "block.minecraft.piglin_wall_head": "Piglin Wall Head", "block.minecraft.piglin_head": "Piglin Head", "item.minecraft.bamboo_raft": "Bamboo Raft", "item.minecraft.bamboo_chest_raft": "Bamboo Raft with Chest", "item.minecraft.camel_spawn_egg": "Camel Spawn Egg", "item.minecraft.ender_dragon_spawn_egg": "Ender Dragon Spawn Egg", "item.minecraft.iron_golem_spawn_egg": "Iron Golem Spawn Egg", "item.minecraft.snow_golem_spawn_egg": "Snow Golem Spawn Egg", "item.minecraft.wither_spawn_egg": "Wither Spawn Egg", "item.disabled": "Disabled item", "entity.minecraft.camel": "Camel", "itemGroup.coloredBlocks": "Colored Blocks", "itemGroup.natural": "Natural Blocks", "itemGroup.functional": "Functional Blocks", "itemGroup.op": "Operator Utilities", "itemGroup.spawnEggs": "Spawn Eggs", "itemGroup.consumables": "Consumables", "itemGroup.foodAndDrink": "Food & Drinks", "itemGroup.crafting": "Crafting", "itemGroup.ingredients": "Ingredients", "subtitles.chiseled_bookshelf.insert": "Book placed", "subtitles.chiseled_bookshelf.insert_enchanted": "Enchanted book placed", "subtitles.chiseled_bookshelf.take": "Book taken", "subtitles.chiseled_bookshelf.take_enchanted": "Enchanted book taken", "subtitles.entity.camel.ambient": "Camel grunts", "subtitles.entity.camel.dash": "Camel yeets", "subtitles.entity.camel.dash_ready": "Camel recovers", "subtitles.entity.camel.death": "Camel dies", "subtitles.entity.camel.eat": "Camel eats", "subtitles.entity.camel.hurt": "Camel hurts", "subtitles.entity.camel.saddle": "Saddle equips", "subtitles.entity.camel.sit": "Camel sits down", "subtitles.entity.camel.stand": "Camel stands up", "subtitles.entity.camel.step": "Camel steps", "subtitles.entity.camel.step_sand": "Camel sands", "subtitles.entity.enderman.scream": "Enderman screams", "subtitles.entity.tadpole.grow_up": "Tadpole grows up", "telemetry_info.screen.title": "Telemetry Data Collection", "telemetry_info.screen.description": "Collecting this data helps us improve Minecraft by guiding us in directions that are relevant to our players.\nYou can also send in additional feedback to help us keep improving Minecraft.", "telemetry_info.button.show_data": "Open My Data", "telemetry_info.button.give_feedback": "Give Feedback", "telemetry_info.property_title": "Included Data", "telemetry.property.user_id.title": "User ID", "telemetry.property.client_id.title": "Client ID", "telemetry.property.minecraft_session_id.title": "Minecraft Session ID", "telemetry.property.game_version.title": "Game Version", "telemetry.property.operating_system.title": "Operating System", "telemetry.property.platform.title": "Platform", "telemetry.property.client_modded.title": "Client Modded", "telemetry.property.event_timestamp_utc.title": "Event Timestamp (UTC)", "telemetry.property.opt_in.title": "Opt-In", "telemetry.property.world_session_id.title": "World Session ID", "telemetry.property.server_modded.title": "Server Modded", "telemetry.property.server_type.title": "Server Type", "telemetry.property.frame_rate_samples.title": "Frame Rate Samples (FPS)", "telemetry.property.render_time_samples.title": "Render Time Samples", "telemetry.property.used_memory_samples.title": "Used Random Access Memory", "telemetry.property.number_of_samples.title": "Sample Count", "telemetry.property.render_distance.title": "Render Distance", "telemetry.property.dedicated_memory_kb.title": "Dedicated Memory (kB)", "telemetry.property.game_mode.title": "Game Mode", "telemetry.property.seconds_since_load.title": "Time Since Load (Seconds)", "telemetry.property.ticks_since_load.title": "Time Since Load (Ticks)", "telemetry.property.world_load_time_ms.title": "World Load Time (Milliseconds)", "telemetry.property.new_world.title": "New World", "telemetry.event.required": "%s (Required)", "telemetry.event.optional": "%s (Optional)", "telemetry.event.world_loaded.title": "World Loaded", "telemetry.event.world_loaded.description": "Knowing how players play Minecraft (such as Game Mode, client or server modded, and game version) allows us to focus game updates to improve the areas that players care about most.\nThe World Loaded event is paired with the World Unloaded event to calculate how long the play session has lasted.", "telemetry.event.world_unloaded.title": "World Unloaded", "telemetry.event.world_unloaded.description": "This event is paired with the World Loaded event to calculate how long the world session has lasted.\nThe duration (in seconds and ticks) is measured when a world session has ended (quitting to title, disconnecting from a server).", "telemetry.event.performance_metrics.title": "Performance Metrics", "telemetry.event.performance_metrics.description": "Knowing the overall performance profile of Minecraft helps us tune and optimize the game for a wide range of machine specifications and operating systems. \nGame version is included to help us compare the performance profile for new versions of Minecraft.", "telemetry.event.world_load_times.title": "World Load Times", "telemetry.event.world_load_times.description": "It�s important for us to understand how long it takes to join a world, and how that changes over time. For example, when we add new features or do larger technical changes, we need to see what impact that had on load times.", "argument.resource.not_found": "Can't find element '%s' of type '%s'", "argument.resource.invalid_type": "Element '%s' has wrong type '%s' (expected '%s')", "argument.resource_tag.not_found": "Can't find tag '%s' of type '%s'", "argument.resource_tag.invalid_type": "Tag '%s' has wrong type '%s' (expected '%s')", "arguments.nbtpath.too_deep": "Resulting NBT too deeply nested", "arguments.nbtpath.too_large": "Resulting NBT too large", "argument.gamemode.invalid": "Unknown gamemode: %s", "entity.not_summonable": "Can't summon entity of type %s", "commands.datapack.enable.failed.no_flags": "Pack '%s' cannot be enabled, since required flags are not enabled in this world: %s!", "commands.fillbiome.toobig": "Too many blocks in the specified volume (maximum %s, specified %s)", "commands.fillbiome.success": "Biomes set between %s, %s, %s and %s, %s, %s", "commands.fillbiome.success.count": "%s biome entries set between %s, %s, %s and %s, %s, %s", "gamerule.blockExplosionDropDecay": "In block interaction explosions, some blocks won't drop their loot", "gamerule.blockExplosionDropDecay.description": "Some of the drops from blocks destroyed by explosions caused by block interactions are lost in the explosion.", "gamerule.mobExplosionDropDecay": "In mob explosions, some blocks won't drop their loot", "gamerule.mobExplosionDropDecay.description": "Some of the drops from blocks destroyed by explosions caused by mobs are lost in the explosion.", "gamerule.tntExplosionDropDecay": "In TNT explosions, some blocks won't drop their loot", "gamerule.tntExplosionDropDecay.description": "Some of the drops from blocks destroyed by explosions caused by TNT are lost in the explosion.", "gamerule.snowAccumulationHeight": "Snow accumulation height", "gamerule.snowAccumulationHeight.description": "When it snows, layers of snow form on the ground up to at most this number of layers.", "gamerule.waterSourceConversion": "Water converts to source", "gamerule.waterSourceConversion.description": "When flowing water is surrounded on two sides by water sources it converts into a source.", "gamerule.lavaSourceConversion": "Lava converts to source", "gamerule.lavaSourceConversion.description": "When flowing lava is surrounded on two sides by lava sources it converts into a source.", "gamerule.globalSoundEvents": "Global sound events", "gamerule.globalSoundEvents.description": "When certain game events happen, like a boss spawning, the sound is heard everywhere.", "pack.source.feature": "feature", "mco.gui.ok": "Ok", "mco.gui.button": "Button", "mco.terms.buttons.agree": "Agree", "mco.terms.buttons.disagree": "Don't agree", "mco.terms.title": "Realms Terms of Service", "mco.terms.sentence.1": "I agree to the Minecraft Realms", "mco.terms.sentence.2": "Terms of Service", "mco.selectServer.play": "Play", "mco.selectServer.configure": "Configure", "mco.selectServer.configureRealm": "Configure realm", "mco.selectServer.leave": "Leave realm", "mco.selectServer.create": "Create realm", "mco.selectServer.purchase": "Add Realm", "mco.selectServer.buy": "Buy a realm!", "mco.selectServer.trial": "Get a trial!", "mco.selectServer.close": "Close", "mco.selectServer.expiredTrial": "Your trial has ended", "mco.selectServer.expiredList": "Your subscription has expired", "mco.selectServer.expiredSubscribe": "Subscribe", "mco.selectServer.expiredRenew": "Renew", "mco.selectServer.expired": "Expired realm", "mco.selectServer.open": "Open realm", "mco.selectServer.closed": "Closed realm", "mco.selectServer.openserver": "Open realm", "mco.selectServer.closeserver": "Close realm", "mco.selectServer.minigame": "Minigame:", "mco.selectServer.uninitialized": "Click to start your new realm!", "mco.selectServer.expires.days": "Expires in %s days", "mco.selectServer.expires.day": "Expires in a day", "mco.selectServer.expires.soon": "Expires soon", "mco.selectServer.note": "Note:", "mco.selectServer.mapOnlySupportedForVersion": "This map is unsupported in %s", "mco.selectServer.minigameNotSupportedInVersion": "Can't play this minigame in %s", "mco.selectServer.popup": "Realms is a safe, simple way to enjoy an online Minecraft world with up to ten friends at a time. It supports loads of minigames and plenty of custom worlds! Only the owner of the realm needs to pay.", "mco.configure.world.settings.title": "Settings", "mco.configure.world.title": "Configure realm:", "mco.configure.worlds.title": "Worlds", "mco.configure.world.name": "Realm name", "mco.configure.world.description": "Realm description", "mco.configure.world.location": "Location", "mco.configure.world.invited": "Invited", "mco.configure.world.invite.narration": "You have %s new invites", "mco.configure.world.buttons.edit": "Settings", "mco.configure.world.buttons.done": "Done", "mco.configure.world.buttons.delete": "Delete", "mco.configure.world.buttons.open": "Open realm", "mco.configure.world.buttons.close": "Close realm", "mco.configure.world.buttons.invite": "Invite player", "mco.configure.world.buttons.activity": "Player activity", "mco.configure.world.activityfeed.disabled": "Player feed temporarily disabled", "mco.configure.world.buttons.moreoptions": "More options", "mco.configure.world.invite.profile.name": "Name", "mco.configure.world.uninvite.question": "Are you sure that you want to uninvite", "mco.configure.world.status": "Status", "mco.configure.world.invites.ops.tooltip": "Operator", "mco.configure.world.invites.normal.tooltip": "Normal user", "mco.configure.world.invites.remove.tooltip": "Remove", "mco.configure.world.closing": "Closing the realm...", "mco.configure.world.opening": "Opening the realm...", "mco.configure.world.buttons.players": "Players", "mco.configure.world.buttons.settings": "Settings", "mco.configure.world.buttons.subscription": "Subscription", "mco.configure.world.buttons.options": "World options", "mco.configure.world.backup": "World backups", "mco.configure.world.buttons.resetworld": "Reset world", "mco.configure.world.buttons.switchminigame": "Switch minigame", "mco.configure.current.minigame": "Current", "mco.configure.world.subscription.title": "Your subscription", "mco.configure.world.subscription.timeleft": "Time left", "mco.configure.world.subscription.unknown": "Unknown", "mco.configure.world.subscription.recurring.daysleft": "Renewed automatically in", "mco.configure.world.subscription.less_than_a_day": "Less than a day", "mco.configure.world.subscription.expired": "Expired", "mco.configure.world.subscription.start": "Start date", "mco.configure.world.subscription.extend": "Extend subscription", "mco.configure.world.subscription.day": "day", "mco.configure.world.subscription.month": "month", "mco.configure.world.subscription.days": "days", "mco.configure.world.subscription.months": "months", "mco.configure.world.pvp": "PVP", "mco.configure.world.spawn_toggle.title": "Warning!", "mco.configure.world.spawn_toggle.message": "Turning this option off will REMOVE ALL existing entities of that type", "mco.configure.world.spawn_toggle.message.npc": "Turning this option off will REMOVE ALL existing entities of that type, like Villagers", "mco.configure.world.spawnAnimals": "Spawn animals", "mco.configure.world.spawnNPCs": "Spawn NPCs", "mco.configure.world.spawnMonsters": "Spawn monsters", "mco.configure.world.spawnProtection": "Spawn protection", "mco.configure.world.commandBlocks": "Command blocks", "mco.configure.world.forceGameMode": "Force game mode", "mco.configure.world.slot": "World %s", "mco.configure.world.slot.empty": "Empty", "mco.create.world.wait": "Creating the realm...", "mco.create.world.error": "You must enter a name!", "mco.create.world.subtitle": "Optionally, select what world to put on your new realm", "mco.create.world.skip": "Skip", "mco.reset.world.title": "Reset world", "mco.reset.world.warning": "This will replace the current world of your realm", "mco.reset.world.seed": "Seed (Optional)", "mco.reset.world.resetting.screen.title": "Resetting world...", "mco.reset.world.generate": "New world", "mco.reset.world.upload": "Upload world", "mco.reset.world.adventure": "Adventures", "mco.reset.world.template": "World templates", "mco.reset.world.experience": "Experiences", "mco.reset.world.inspiration": "Inspiration", "mco.minigame.world.title": "Switch realm to minigame", "mco.minigame.world.info.line1": "This will temporarily replace your world with a minigame!", "mco.minigame.world.info.line2": "You can later return to your original world without losing anything.", "mco.minigame.world.selected": "Selected minigame:", "mco.minigame.world.noSelection": "Please make a selection", "mco.minigame.world.startButton": "Switch", "mco.minigame.world.starting.screen.title": "Starting minigame...", "mco.minigame.world.changeButton": "Select another minigame", "mco.minigame.world.stopButton": "End minigame", "mco.minigame.world.switch.title": "Switch minigame", "mco.minigame.world.switch.new": "Select another minigame?", "mco.minigame.world.restore.question.line1": "The minigame will end and your realm will be restored.", "mco.minigame.world.restore.question.line2": "Are you sure you want to continue?", "mco.minigame.world.restore": "Ending minigame...", "mco.configure.world.slot.tooltip": "Switch to world", "mco.configure.world.slot.tooltip.minigame": "Switch to minigame", "mco.configure.world.slot.tooltip.active": "Join", "mco.configure.world.close.question.line1": "Your realm will become unavailable.", "mco.configure.world.close.question.line2": "Are you sure you want to continue?", "mco.configure.world.leave.question.line1": "If you leave this realm you won't see it unless you are invited again", "mco.configure.world.leave.question.line2": "Are you sure you want to continue?", "mco.configure.world.resourcepack.question.line1": "You need a custom resource pack to play on this realm", "mco.configure.world.resourcepack.question.line2": "Do you want to download it and play?", "mco.configure.world.reset.question.line1": "Your world will be regenerated and your current world will be lost", "mco.configure.world.reset.question.line2": "Are you sure you want to continue?", "mco.configure.world.restore.question.line1": "Your world will be restored to date '%s' (%s)", "mco.configure.world.restore.question.line2": "Are you sure you want to continue?", "mco.configure.world.restore.download.question.line1": "The world will be downloaded and added to your single player worlds.", "mco.configure.world.restore.download.question.line2": "Do you want to continue?", "mco.configure.world.slot.switch.question.line1": "Your realm will be switched to another world", "mco.configure.world.slot.switch.question.line2": "Are you sure you want to continue?", "mco.configure.world.switch.slot": "Create world", "mco.configure.world.switch.slot.subtitle": "This world is empty, choose how to create your world", "mco.minigame.world.slot.screen.title": "Switching world...", "mco.configure.world.edit.subscreen.adventuremap": "Some settings are disabled since your current world is an adventure", "mco.configure.world.edit.subscreen.experience": "Some settings are disabled since your current world is an experience", "mco.configure.world.edit.subscreen.inspiration": "Some settings are disabled since your current world is an inspiration", "mco.configure.world.edit.slot.name": "World name", "mco.configure.world.players.title": "Players", "mco.configure.world.players.error": "A player with the provided name does not exist", "mco.configure.world.delete.button": "Delete realm", "mco.configure.world.delete.question.line1": "Your realm will be permanently deleted", "mco.configure.world.delete.question.line2": "Are you sure you want to continue?", "mco.connect.connecting": "Connecting to the realm...", "mco.connect.authorizing": "Logging in...", "mco.connect.failed": "Failed to connect to the realm", "mco.connect.success": "Done", "mco.create.world": "Create", "mco.create.world.reset.title": "Creating world...", "mco.client.incompatible.title": "Client incompatible!", "mco.client.incompatible.msg.line1": "Your client is not compatible with Realms.", "mco.client.incompatible.msg.line2": "Please use the most recent version of Minecraft.", "mco.client.incompatible.msg.line3": "Realms is not compatible with snapshot versions.", "mco.backup.button.restore": "Restore", "mco.backup.generate.world": "Generate world", "mco.backup.restoring": "Restoring your realm", "mco.backup.button.download": "Download latest", "mco.backup.changes.tooltip": "Changes", "mco.backup.nobackups": "This realm doesn't have any backups currently.", "mco.backup.button.upload": "Upload world", "mco.backup.button.reset": "Reset world", "mco.download.title": "Downloading latest world", "mco.download.cancelled": "Download cancelled", "mco.download.failed": "Download failed", "mco.download.done": "Download done", "mco.download.downloading": "Downloading", "mco.download.extracting": "Extracting", "mco.download.preparing": "Preparing download", "mco.download.confirmation.line1": "The world you are going to download is larger than %s", "mco.download.confirmation.line2": "You won't be able to upload this world to your realm again", "mco.template.title": "World templates", "mco.template.title.minigame": "Minigames", "mco.template.button.select": "Select", "mco.template.button.trailer": "Trailer", "mco.template.button.publisher": "Publisher", "mco.template.default.name": "World template", "mco.template.name": "Template", "mco.template.info.tooltip": "Publisher website", "mco.template.trailer.tooltip": "Map trailer", "mco.template.select.none": "Oops, it looks like this content category is currently empty.\nPlease check back later for new content, or if you're a creator,\n%s.", "mco.template.select.none.linkTitle": "consider submitting something yourself", "mco.template.select.failure": "We couldn't retrieve the list of content for this category.\nPlease check your internet connection, or try again later.", "mco.template.select.narrate.authors": "Authors: %s", "mco.template.select.narrate.version": "version %s", "mco.invites.button.accept": "Accept", "mco.invites.button.reject": "Reject", "mco.invites.title": "Pending Invites", "mco.invites.pending": "New invites!", "mco.invites.nopending": "No pending invites!", "mco.upload.select.world.title": "Upload world", "mco.upload.select.world.subtitle": "Please select a singleplayer world to upload", "mco.upload.select.world.none": "No singleplayer worlds found!", "mco.upload.button.name": "Upload", "mco.upload.verifying": "Verifying your world", "mco.upload.preparing": "Preparing your world", "mco.upload.close.failure": "Could not close your realm, please try again later", "mco.upload.size.failure.line1": "'%s' is too big!", "mco.upload.size.failure.line2": "It is %s. The maximum allowed size is %s.", "mco.upload.uploading": "Uploading '%s'", "mco.upload.done": "Upload done", "mco.upload.failed": "Upload failed! (%s)", "mco.upload.cancelled": "Upload cancelled", "mco.upload.hardcore": "Hardcore worlds can't be uploaded!", "mco.activity.title": "Player activity", "mco.activity.noactivity": "No activity for the past %s days", "mco.errorMessage.6001": "Client outdated", "mco.errorMessage.6002": "Terms of service not accepted", "mco.errorMessage.6003": "Download limit reached", "mco.errorMessage.6004": "Upload limit reached", "mco.errorMessage.connectionFailure": "An error occurred, please try again later.", "mco.errorMessage.serviceBusy": "Realms is busy at the moment.\nPlease try connecting to your Realm again in a couple of minutes.", "mco.trial.message.line1": "Want to get your own realm?", "mco.trial.message.line2": "Click here for more info!", "mco.brokenworld.play": "Play", "mco.brokenworld.download": "Download", "mco.brokenworld.downloaded": "Downloaded", "mco.brokenworld.reset": "Reset", "mco.brokenworld.title": "Your current world is no longer supported", "mco.brokenworld.message.line1": "Please reset or select another world.", "mco.brokenworld.message.line2": "You can also choose to download the world to singleplayer.", "mco.brokenworld.minigame.title": "This minigame is no longer supported", "mco.brokenworld.nonowner.title": "World is out of date", "mco.brokenworld.nonowner.error": "Please wait for the realm owner to reset the world", "mco.error.invalid.session.title": "Invalid session", "mco.error.invalid.session.message": "Please try restarting Minecraft", "mco.news": "Realms news", "mco.account.privacyinfo": "Mojang implements certain procedures to help protect children and their privacy including complying with the Children�s Online Privacy Protection Act (COPPA) and General Data Protection Regulation (GDPR).\n\nYou may need to obtain parental consent before accessing your Realms account.\n\nIf you have an older Minecraft account (you log in with your username), you need to migrate the account to a Mojang account in order to access Realms.", "mco.account.update": "Update account", "mco.account.privacy.info": "Read more about Mojang and privacy laws" }, "1.19.1": { "gui.acknowledge": "Acknowledge", "gui.socialInteractions.report": "Report", "gui.socialInteractions.tooltip.report": "Report player", "gui.socialInteractions.tooltip.report.disabled": "The reporting service is unavailable", "gui.socialInteractions.tooltip.report.no_messages": "No reportable messages from player %s", "gui.socialInteractions.tooltip.report.not_reportable": "This player can't be reported, because their chat messages can't be verified on this server", "gui.socialInteractions.narration.hide": "Hide messages from %s", "gui.socialInteractions.narration.show": "Show messages from %s", "gui.socialInteractions.narration.report": "Report player %s", "gui.chatReport.title": "Report Player", "gui.chatReport.send": "Send Report", "gui.chatReport.send.comments_too_long": "Please shorten the comment", "gui.chatReport.send.no_reason": "Please select a report category", "gui.chatReport.send.no_reported_messages": "Please select at least one chat message to report", "gui.chatReport.send.too_many_messages": "Trying to include too many messages in the report", "gui.chatReport.observed_what": "Why are you reporting this?", "gui.chatReport.select_reason": "Select Report Category", "gui.chatReport.more_comments": "Please describe what happened:", "gui.chatReport.describe": "Sharing details will help us make a well-informed decision.", "gui.chatReport.comments": "Comments edit box", "gui.chatReport.read_info": "Learn About Reporting", "gui.chatReport.select_chat": "Select Chat Messages to Report", "gui.chatReport.selected_chat": "%s Chat Message(s) Selected to Report", "gui.chatReport.report_sent_msg": "We�ve successfully received your report. Thank you!\n\nOur team will review it as soon as possible.", "gui.chatReport.discard.title": "Discard report and comments?", "gui.chatReport.discard.content": "If you leave, you'll lose this report and your comments.\nAre you sure you want to leave?", "gui.chatReport.discard.discard": "Leave and Discard Report", "gui.chatReport.discard.return": "Continue Editing", "gui.abuseReport.reason.title": "Select Report Category", "gui.abuseReport.reason.description": "Description:", "gui.abuseReport.reason.narration": "%s: %s", "gui.abuseReport.reason.false_reporting": "False Reporting", "gui.abuseReport.reason.child_sexual_exploitation_or_abuse": "Child sexual exploitation or abuse", "gui.abuseReport.reason.child_sexual_exploitation_or_abuse.description": "Someone is talking about or otherwise promoting indecent behavior involving children.", "gui.abuseReport.reason.terrorism_or_violent_extremism": "Terrorism or violent extremism", "gui.abuseReport.reason.terrorism_or_violent_extremism.description": "Someone is talking about, promoting, or threatening to commit acts of terrorism or violent extremism for political, religious, ideological, or other reasons.", "gui.abuseReport.reason.hate_speech": "Hate speech", "gui.abuseReport.reason.hate_speech.description": "Someone is attacking you or another player based on characteristics of their identity, like religion, race, or sexuality.", "gui.abuseReport.reason.harassment_or_bullying": "Harassment or bullying", "gui.abuseReport.reason.harassment_or_bullying.description": "Someone is shaming, attacking, or bullying you or someone else. This includes when someone is repeatedly trying to contact you or someone else without consent or posting private personal information about you or someone else without consent (\"doxing\").", "gui.abuseReport.reason.imminent_harm": "Imminent harm - Threat to harm others", "gui.abuseReport.reason.imminent_harm.description": "Someone is threatening to harm you or someone else in real life.", "gui.abuseReport.reason.defamation_impersonation_false_information": "Defamation, impersonation, or false information", "gui.abuseReport.reason.defamation_impersonation_false_information.description": "Someone is damaging someone else's reputation, pretending to be someone they're not, or sharing false information with the aim to exploit or mislead others.", "gui.abuseReport.reason.self_harm_or_suicide": "Imminent harm - Self-harm or suicide", "gui.abuseReport.reason.self_harm_or_suicide.description": "Someone is threatening to harm themselves in real life or talking about harming themselves in real life.", "gui.abuseReport.reason.alcohol_tobacco_drugs": "Drugs or alcohol", "gui.abuseReport.reason.alcohol_tobacco_drugs.description": "Someone is encouraging others to partake in illegal drug related activities or encouraging underage drinking.", "gui.abuseReport.reason.non_consensual_intimate_imagery": "Non-consensual intimate imagery", "gui.abuseReport.reason.non_consensual_intimate_imagery.description": "Someone is talking about, sharing, or otherwise promoting private and intimate images.", "gui.abuseReport.sending.title": "Sending your report...", "gui.abuseReport.sent.title": "Report sent", "gui.abuseReport.error.title": "Problem sending your report", "gui.abuseReport.send.generic_error": "Encountered an unexpected error while sending your report.", "gui.abuseReport.send.error_message": "An error was returned while sending your report:\n'%s'", "gui.abuseReport.send.service_unavailable": "Unable to reach the Abuse Reporting service. Please make sure you are connected to the internet and try again.", "gui.abuseReport.send.http_error": "An unexpected HTTP error occurred while sending your report.", "gui.abuseReport.send.json_error": "Encountered malformed payload while sending your report.", "gui.chatSelection.title": "Select Chat Messages to Report", "gui.chatSelection.context": "Messages surrounding this selection will be included to provide additional context", "gui.chatSelection.selected": "%s/%s message(s) selected", "gui.chatSelection.heading": "%s %s", "gui.chatSelection.message.narrate": "%s said: %s at %s", "gui.chatSelection.fold": "%s unrelated messages hidden", "gui.multiLineEditBox.character_limit": "%s/%s", "gui.banned.title.temporary": "Account temporarily suspended", "gui.banned.title.permanent": "Account permanently banned", "gui.banned.description": "%s\n\n%s\n\nLearn more at the following link: %s", "gui.banned.description.reason": "We recently received a report for bad behavior by your account. Our moderators have now reviewed your case and identified it as %s, which goes against the Minecraft Community Standards.", "gui.banned.description.reason_id": "Code: %s", "gui.banned.description.reason_id_message": "Code: %s - %s", "gui.banned.description.unknownreason": "We recently received a report for bad behavior by your account. Our moderators have now reviewed your case and identified that it goes against the Minecraft Community Standards.", "gui.banned.description.temporary.duration": "Your account is temporarily suspended and will be reactivated in %s.", "gui.banned.description.temporary": "%s Until then, you can�t play online or join Realms.", "gui.banned.description.permanent": "Your account is permanently banned, which means you can�t play online or join Realms.", "menu.playerReporting": "Player Reporting", "multiplayer.disconnect.unsigned_chat": "Received chat packet with missing or invalid signature.", "multiplayer.disconnect.too_many_pending_chats": "Too many unacknowledged chat messages", "multiplayer.disconnect.chat_validation_failed": "Chat message validation failure", "multiplayer.unsecureserver.toast.title": "Chat messages can't be verified", "multiplayer.unsecureserver.toast": "Messages sent on this server may be modified and might not reflect the original message", "chat.previewInput": "Press [%s] to preview", "chat.tag.not_secure": "This message is not secure, which means that it might have been modified by the server", "chat.tag.modified": "This message has been modified by the server.", "chat.tag.modified.original": "Original text: %s", "chat.tag.filtered": "This message has been filtered by the server.", "chat.filtered_full": "The server has hidden your message for some players.", "disconnect.loginFailedInfo.userBanned": "You are banned from playing online", "options.chatPreview.live": "While Typing", "options.chatPreview.confirm": "When Sending", "options.chatPreview.tooltip.off": "Any modifications applied to your chat messages by a server will not be previewed and will be treated as insecure.", "options.chatPreview.tooltip.live": "If a server uses Chat Previews: Any modifications applied to your chat messages by a server will be dynamically sent for previewing as the chat message is typed.", "options.chatPreview.tooltip.confirm": "If a server uses Chat Previews: A chat preview is only generated when attempting to send a message that does not have a preview or is waiting for a preview.\nSending the message requires confirmation.", "title.multiplayer.disabled.banned.temporary": "Your account is temporarily suspended from online play", "title.multiplayer.disabled.banned.permanent": "Your account is permanently suspended from online play", "gui.minutes": "%s minute(s)", "gui.hours": "%s hour(s)", "gui.days": "%s day(s)" }, "1.19": { "selectWorld.loading_list": "Loading world list", "flat_world_preset.unknown": "???", "flat_world_preset.minecraft.classic_flat": "Classic Flat", "flat_world_preset.minecraft.tunnelers_dream": "Tunnelers' Dream", "flat_world_preset.minecraft.water_world": "Water World", "flat_world_preset.minecraft.overworld": "Overworld", "flat_world_preset.minecraft.snowy_kingdom": "Snowy Kingdom", "flat_world_preset.minecraft.bottomless_pit": "Bottomless Pit", "flat_world_preset.minecraft.desert": "Desert", "flat_world_preset.minecraft.redstone_ready": "Redstone Ready", "flat_world_preset.minecraft.the_void": "The Void", "generator.minecraft.normal": "Default", "generator.minecraft.flat": "Superflat", "generator.minecraft.large_biomes": "Large Biomes", "generator.minecraft.amplified": "AMPLIFIED", "generator.minecraft.amplified.info": "Notice: Just for fun! Requires a beefy computer.", "generator.minecraft.debug_all_block_states": "Debug Mode", "generator.minecraft.single_biome_surface": "Single Biome", "multiplayer.disconnect.missing_public_key": "Missing profile public key.\nThis server requires secure profiles.", "multiplayer.disconnect.invalid_public_key_signature": "Invalid signature for profile public key.\nTry restarting your game.", "multiplayer.disconnect.invalid_public_key": "Unable to parse profile public key.", "multiplayer.disconnect.out_of_order_chat": "Out-of-order chat packet received. Did your system time change?", "chatPreview.warning.title": "This server uses Chat Preview", "chatPreview.warning.content": "Chat Preview allows the server to see your messages in real time as you type them, even before they�re sent. This is often used to preview your message with styling applied.\n\nChat Preview is on by default, but can be turned off in Chat Settings.", "chatPreview.warning.check": "Do not notify again for this server", "chatPreview.warning.toast.title": "Chat Preview is enabled", "chatPreview.warning.toast": "This server uses Chat Preview and can see your messages as you type them, even before they're sent. You can turn this off in Chat Settings.", "chat.disabled.profile.moreInfo": "Chat not allowed by account settings. Cannot send or view messages.", "chat.preview": "Type to preview", "options.darknessEffectScale": "Darkness Pulsing", "options.darknessEffectScale.tooltip": "Controls how much the Darkness effect pulses when a Warden or Sculk Shrieker gives it to you.", "options.chatPreview": "Chat Preview", "options.chatPreview.tooltip": "Chat Preview allows servers to see your messages as you type, which allows them to style your message. You can still chat if you turn this off.", "options.onlyShowSecureChat": "Only Show Secure Chat", "options.onlyShowSecureChat.tooltip": "Only display messages from other players that can be verified to have been sent by that player, and have not been modified.", "options.directionalAudio": "Directional Audio", "options.directionalAudio.on.tooltip": "Uses HRTF-based directional audio to improve the simulation of 3D sound. Requires HRTF compatible audio hardware, and is best experienced with headphones.", "options.directionalAudio.off.tooltip": "Classic Stereo sound", "block.minecraft.mangrove_planks": "Mangrove Planks", "block.minecraft.mangrove_propagule": "Mangrove Propagule", "block.minecraft.mangrove_door": "Mangrove Door", "block.minecraft.mangrove_wood": "Mangrove Wood", "block.minecraft.mangrove_log": "Mangrove Log", "block.minecraft.mangrove_roots": "Mangrove Roots", "block.minecraft.muddy_mangrove_roots": "Muddy Mangrove Roots", "block.minecraft.stripped_mangrove_log": "Stripped Mangrove Log", "block.minecraft.stripped_mangrove_wood": "Stripped Mangrove Wood", "block.minecraft.mangrove_leaves": "Mangrove Leaves", "block.minecraft.mud_brick_slab": "Mud Brick Slab", "block.minecraft.mangrove_slab": "Mangrove Slab", "block.minecraft.mangrove_stairs": "Mangrove Stairs", "block.minecraft.mangrove_sign": "Mangrove Sign", "block.minecraft.mangrove_wall_sign": "Mangrove Wall Sign", "block.minecraft.mangrove_pressure_plate": "Mangrove Pressure Plate", "block.minecraft.mangrove_button": "Mangrove Button", "block.minecraft.mangrove_fence": "Mangrove Fence", "block.minecraft.mangrove_fence_gate": "Mangrove Fence Gate", "block.minecraft.mangrove_trapdoor": "Mangrove Trapdoor", "block.minecraft.packed_mud": "Packed Mud", "block.minecraft.mud_bricks": "Mud Bricks", "block.minecraft.mud_brick_stairs": "Mud Brick Stairs", "block.minecraft.potted_mangrove_propagule": "Potted Mangrove Propagule", "block.minecraft.mud_brick_wall": "Mud Brick Wall", "block.minecraft.mud": "Mud", "block.minecraft.sculk": "Sculk", "block.minecraft.sculk_catalyst": "Sculk Catalyst", "block.minecraft.sculk_shrieker": "Sculk Shrieker", "block.minecraft.sculk_vein": "Sculk Vein", "block.minecraft.ochre_froglight": "Ochre Froglight", "block.minecraft.verdant_froglight": "Verdant Froglight", "block.minecraft.pearlescent_froglight": "Pearlescent Froglight", "block.minecraft.frogspawn": "Frogspawn", "block.minecraft.reinforced_deepslate": "Reinforced Deepslate", "item.minecraft.tadpole_bucket": "Bucket of Tadpole", "item.minecraft.oak_chest_boat": "Oak Boat with Chest", "item.minecraft.spruce_chest_boat": "Spruce Boat with Chest", "item.minecraft.birch_chest_boat": "Birch Boat with Chest", "item.minecraft.jungle_chest_boat": "Jungle Boat with Chest", "item.minecraft.acacia_chest_boat": "Acacia Boat with Chest", "item.minecraft.dark_oak_chest_boat": "Dark Oak Boat with Chest", "item.minecraft.mangrove_boat": "Mangrove Boat", "item.minecraft.mangrove_chest_boat": "Mangrove Boat with Chest", "item.minecraft.recovery_compass": "Recovery Compass", "item.minecraft.music_disc_5": "Music Disc", "item.minecraft.music_disc_5.desc": "Samuel �berg - 5", "item.minecraft.disc_fragment_5": "Disc Fragment", "item.minecraft.disc_fragment_5.desc": "Music Disc - 5", "item.minecraft.allay_spawn_egg": "Allay Spawn Egg", "item.minecraft.frog_spawn_egg": "Frog Spawn Egg", "item.minecraft.tadpole_spawn_egg": "Tadpole Spawn Egg", "item.minecraft.warden_spawn_egg": "Warden Spawn Egg", "item.minecraft.echo_shard": "Echo Shard", "item.minecraft.goat_horn": "Goat Horn", "instrument.minecraft.ponder_goat_horn": "Ponder", "instrument.minecraft.sing_goat_horn": "Sing", "instrument.minecraft.seek_goat_horn": "Seek", "instrument.minecraft.feel_goat_horn": "Feel", "instrument.minecraft.admire_goat_horn": "Admire", "instrument.minecraft.call_goat_horn": "Call", "instrument.minecraft.yearn_goat_horn": "Yearn", "instrument.minecraft.dream_goat_horn": "Dream", "entity.minecraft.allay": "Allay", "entity.minecraft.chest_boat": "Boat with Chest", "entity.minecraft.frog": "Frog", "entity.minecraft.tadpole": "Tadpole", "entity.minecraft.warden": "Warden", "death.attack.onFire.item": "%1$s was burnt to a crisp whilst fighting %2$s wielding %3$s", "death.attack.witherSkull.item": "%1$s was shot by a skull from %2$s using %3$s", "death.attack.sonic_boom": "%1$s was obliterated by a sonically-charged shriek", "death.attack.sonic_boom.item": "%1$s was obliterated by a sonically-charged shriek whilst trying to escape %2$s wielding %3$s", "death.attack.sonic_boom.player": "%1$s was obliterated by a sonically-charged shriek whilst trying to escape %2$s", "death.attack.sting.item": "%1$s was stung to death by %2$s using %3$s", "effect.minecraft.darkness": "Darkness", "enchantment.minecraft.swift_sneak": "Swift Sneak", "subtitles.block.frogspawn.hatch": "Tadpole hatches", "subtitles.block.sculk.charge": "Sculk bubbles", "subtitles.block.sculk.spread": "Sculk spreads", "subtitles.block.sculk_catalyst.bloom": "Sculk Catalyst blooms", "subtitles.block.sculk_shrieker.shriek": "Sculk Shrieker shrieks", "subtitles.entity.allay.death": "Allay dies", "subtitles.entity.allay.hurt": "Allay hurts", "subtitles.entity.allay.ambient_with_item": "Allay seeks", "subtitles.entity.allay.ambient_without_item": "Allay yearns", "subtitles.entity.allay.item_given": "Allay chortles", "subtitles.entity.allay.item_taken": "Allay allays", "subtitles.entity.allay.item_thrown": "Allay tosses", "subtitles.entity.frog.ambient": "Frog croaks", "subtitles.entity.frog.death": "Frog dies", "subtitles.entity.frog.eat": "Frog eats", "subtitles.entity.frog.hurt": "Frog hurts", "subtitles.entity.frog.lay_spawn": "Frog lays spawn", "subtitles.entity.frog.long_jump": "Frog jumps", "subtitles.entity.goat.horn_break": "Goat Horn breaks off", "subtitles.entity.parrot.imitate.warden": "Parrot whines", "subtitles.entity.tadpole.death": "Tadpole dies", "subtitles.entity.tadpole.flop": "Tadpole flops", "subtitles.entity.tadpole.hurt": "Tadpole hurts", "subtitles.entity.warden.roar": "Warden roars", "subtitles.entity.warden.sniff": "Warden sniffs", "subtitles.entity.warden.emerge": "Warden emerges", "subtitles.entity.warden.dig": "Warden digs", "subtitles.entity.warden.hurt": "Warden hurts", "subtitles.entity.warden.death": "Warden dies", "subtitles.entity.warden.step": "Warden steps", "subtitles.entity.warden.listening": "Warden takes notice", "subtitles.entity.warden.listening_angry": "Warden takes notice angrily", "subtitles.entity.warden.heartbeat": "Warden's heart beats", "subtitles.entity.warden.attack_impact": "Warden lands hit", "subtitles.entity.warden.tendril_clicks": "Warden's tendrils click", "subtitles.entity.warden.angry": "Warden rages", "subtitles.entity.warden.agitated": "Warden groans angrily", "subtitles.entity.warden.ambient": "Warden whines", "subtitles.entity.warden.nearby_close": "Warden approaches", "subtitles.entity.warden.nearby_closer": "Warden advances", "subtitles.entity.warden.nearby_closest": "Warden draws close", "subtitles.entity.warden.sonic_charge": "Warden charges", "subtitles.entity.warden.sonic_boom": "Warden booms", "subtitles.item.bucket.fill_tadpole": "Tadpole captured", "subtitles.item.goat_horn.play": "Goat Horn plays", "advancements.adventure.avoid_vibration.title": "Sneak 100", "advancements.adventure.avoid_vibration.description": "Sneak near a Sculk Sensor or Warden to prevent it from detecting you", "advancements.adventure.kill_mob_near_sculk_catalyst.title": "It Spreads", "advancements.adventure.kill_mob_near_sculk_catalyst.description": "Kill a mob near a Sculk Catalyst", "advancements.husbandry.froglights.title": "With Our Powers Combined!", "advancements.husbandry.froglights.description": "Have all Froglights in your inventory", "advancements.husbandry.tadpole_in_a_bucket.title": "Bukkit Bukkit", "advancements.husbandry.tadpole_in_a_bucket.description": "Catch a Tadpole in a Bucket", "advancements.husbandry.leash_all_frog_variants.title": "When the Squad Hops into Town", "advancements.husbandry.leash_all_frog_variants.description": "Get each Frog variant on a Lead", "advancements.husbandry.allay_deliver_item_to_player.title": "You've Got a Friend in Me", "advancements.husbandry.allay_deliver_item_to_player.description": "Have an Allay deliver items to you", "advancements.husbandry.allay_deliver_cake_to_note_block.title": "Birthday Song", "advancements.husbandry.allay_deliver_cake_to_note_block.description": "Have an Allay drop a Cake at a Note Block", "argument.enum.invalid": "Invalid value \"%s\"", "commands.locate.structure.success": "The nearest %s is at %s (%s blocks away)", "commands.locate.structure.not_found": "Could not find a structure of type \"%s\" nearby", "commands.locate.structure.invalid": "There is no structure with type \"%s\"", "commands.locate.biome.success": "The nearest %s is at %s (%s blocks away)", "commands.locate.biome.not_found": "Could not find a biome of type \"%s\" within reasonable distance", "commands.locate.biome.invalid": "There is no biome with type \"%s\"", "commands.locate.poi.success": "The nearest %s is at %s (%s blocks away)", "commands.locate.poi.not_found": "Could not find a point of interest of type \"%s\" within reasonable distance", "commands.locate.poi.invalid": "There is no point of interest with type \"%s\"", "commands.place.feature.failed": "Failed to place feature", "commands.place.feature.invalid": "There is no feature with type \"%s\"", "commands.place.feature.success": "Placed \"%s\" at %s, %s, %s", "commands.place.jigsaw.failed": "Failed to generate jigsaw", "commands.place.jigsaw.invalid": "There is no template pool with type \"%s\"", "commands.place.jigsaw.success": "Generated jigsaw at %s, %s, %s", "commands.place.structure.failed": "Failed to place structure", "commands.place.structure.invalid": "There is no structure with type \"%s\"", "commands.place.structure.success": "Generated structure \"%s\" at %s, %s, %s", "commands.place.template.failed": "Failed to place template", "commands.place.template.invalid": "There is no template with id \"%s\"", "commands.place.template.success": "Loaded template \"%s\" at %s, %s, %s", "biome.minecraft.deep_dark": "Deep Dark", "biome.minecraft.mangrove_swamp": "Mangrove Swamp", "gamerule.doWardenSpawning": "Spawn Wardens", "outOfMemory.title": "Out of memory!", "outOfMemory.message": "Minecraft has run out of memory.\n\nThis could be caused by a bug in the game or by the Java Virtual Machine not being allocated enough memory.\n\nTo prevent level corruption, the current game has quit. We've tried to free up enough memory to let you go back to the main menu and back to playing, but this may not have worked.\n\nPlease restart the game if you see this message again." }, "1.18": { "selectWorld.conversion.tooltip": "This world must be opened in an older version (like 1.6.4) to be safely converted", "selectWorld.incompatible_series": "Created by an incompatible version", "options.gamma.default": "Default", "options.simulationDistance": "Simulation Distance", "options.prioritizeChunkUpdates": "Chunk Builder", "options.prioritizeChunkUpdates.none": "Threaded", "options.prioritizeChunkUpdates.byPlayer": "Semi Blocking", "options.prioritizeChunkUpdates.nearby": "Fully Blocking", "options.prioritizeChunkUpdates.none.tooltip": "Nearby chunks are compiled in parallel threads. This may result in brief visual holes when blocks are destroyed.", "options.prioritizeChunkUpdates.byPlayer.tooltip": "Some actions within a chunk will recompile the chunk immediately. This includes block placing & destroying.", "options.prioritizeChunkUpdates.nearby.tooltip": "Nearby chunks are always compiled immediately. This may impact game performance when blocks are placed or destroyed.", "options.difficulty.online": "Server Difficulty", "options.audioDevice": "Device", "options.audioDevice.default": "System Default", "options.online": "Online...", "options.online.title": "Online Options", "options.allowServerListing": "Allow Server Listings", "options.allowServerListing.tooltip": "Servers may list online players as part of their public status.\nWith this option off your name will not show up in such lists.", "options.autosaveIndicator": "Autosave Indicator", "options.hideLightningFlashes": "Hide Lightning Flashes", "options.hideLightningFlashes.tooltip": "Prevents lightning bolts from making the sky flash. The bolts themselves will still be visible.", "controls.keybinds": "Key Binds...", "controls.keybinds.title": "Key Binds", "item.minecraft.music_disc_otherside": "Music Disc", "item.minecraft.music_disc_otherside.desc": "Lena Raine - otherside", "subtitles.block.growing_plant.crop": "Plant cropped", "subtitles.item.bundle.drop_contents": "Bundle empties", "subtitles.item.bundle.insert": "Item packed", "subtitles.item.bundle.remove_one": "Item unpacked", "advancements.adventure.fall_from_world_height.title": "Caves & Cliffs", "advancements.adventure.fall_from_world_height.description": "Free fall from the top of the world (build limit) to the bottom of the world and survive", "advancements.adventure.play_jukebox_in_meadows.title": "Sound of Music", "advancements.adventure.play_jukebox_in_meadows.description": "Make the Meadows come alive with the sound of music from a jukebox", "advancements.adventure.trade_at_world_height.title": "Star Trader", "advancements.adventure.trade_at_world_height.description": "Trade with a villager at the build height limit", "advancements.nether.ride_strider_in_overworld_lava.title": "Feels like home", "advancements.nether.ride_strider_in_overworld_lava.description": "Take a Strider for a loooong ride on a lava lake in the Overworld", "commands.jfr.started": "JFR profiling started", "commands.jfr.start.failed": "Failed to start JFR profiling", "commands.jfr.stopped": "JFR profiling stopped and dumped to %s", "commands.jfr.dump.failed": "Failed to dump JFR recording: %s", "commands.worldborder.set.failed.far": "World border cannot be further out than %s blocks", "biome.minecraft.old_growth_birch_forest": "Old Growth Birch Forest", "biome.minecraft.old_growth_pine_taiga": "Old Growth Pine Taiga", "biome.minecraft.old_growth_spruce_taiga": "Old Growth Spruce Taiga", "biome.minecraft.frozen_peaks": "Frozen Peaks", "biome.minecraft.grove": "Grove", "biome.minecraft.jagged_peaks": "Jagged Peaks", "biome.minecraft.meadow": "Meadow", "biome.minecraft.snowy_plains": "Snowy Plains", "biome.minecraft.snowy_slopes": "Snowy Slopes", "biome.minecraft.sparse_jungle": "Sparse Jungle", "biome.minecraft.stony_peaks": "Stony Peaks", "biome.minecraft.stony_shore": "Stony Shore", "biome.minecraft.windswept_forest": "Windswept Forest", "biome.minecraft.windswept_gravelly_hills": "Windswept Gravelly Hills", "biome.minecraft.windswept_hills": "Windswept Hills", "biome.minecraft.windswept_savanna": "Windswept Savanna", "biome.minecraft.wooded_badlands": "Wooded Badlands" }, "1.17": { "gui.socialInteractions.title": "Social Interactions", "gui.socialInteractions.tab_all": "All", "gui.socialInteractions.tab_hidden": "Hidden", "gui.socialInteractions.tab_blocked": "Blocked", "gui.socialInteractions.blocking_hint": "Manage with Microsoft account", "gui.socialInteractions.status_hidden": "Hidden", "gui.socialInteractions.status_blocked": "Blocked", "gui.socialInteractions.status_offline": "Offline", "gui.socialInteractions.status_hidden_offline": "Hidden - Offline", "gui.socialInteractions.status_blocked_offline": "Blocked - Offline", "gui.socialInteractions.server_label.single": "%s - %s player", "gui.socialInteractions.server_label.multiple": "%s - %s players", "gui.socialInteractions.search_hint": "Search...", "gui.socialInteractions.search_empty": "Couldn't find any players with that name", "gui.socialInteractions.empty_hidden": "No players hidden in chat", "gui.socialInteractions.empty_blocked": "No blocked players in chat", "gui.socialInteractions.hide": "Hide in Chat", "gui.socialInteractions.show": "Show in Chat", "gui.socialInteractions.hidden_in_chat": "Chat messages from %s will be hidden", "gui.socialInteractions.shown_in_chat": "Chat messages from %s will be shown", "gui.socialInteractions.tooltip.hide": "Hide messages from %s in chat", "gui.socialInteractions.tooltip.show": "Show messages from %s in chat", "selectWorld.pre_worldheight": "Loading of worlds with extended height is disabled.", "selectWorld.backupQuestion.snapshot": "Do you really want to load this world?", "selectWorld.backupWarning.snapshot": "This world was last played in version %s; you are on version %s. Please make a backup in case you experience world corruptions!", "selectWorld.backupQuestion.downgrade": "Downgrading a world is not supported", "selectWorld.backupWarning.downgrade": "This world was last played in version %s; you are on version %s. Downgrading a world could cause corruption - we cannot guarantee that it will load or work. If you still want to continue, please make a backup!", "multiplayer.requiredTexturePrompt.line1": "This server requires the use of a custom resource pack.", "multiplayer.requiredTexturePrompt.line2": "Rejecting this custom resource pack will disconnect you from this server.", "multiplayer.requiredTexturePrompt.disconnect": "Server requires a custom resource pack", "multiplayer.texturePrompt.failure.line1": "Server resource pack couldn't be applied", "multiplayer.texturePrompt.failure.line2": "Any functionality that requires custom resources might not work as expected", "multiplayer.texturePrompt.serverPrompt": "%s\n\nMessage from server:\n%s", "multiplayer.applyingPack": "Applying resource pack", "multiplayer.status.incompatible": "Incompatible version!", "multiplayer.disconnect.invalid_packet": "Server sent an invalid packet", "multiplayer.disconnect.invalid_player_data": "Invalid player data", "multiplayer.disconnect.incompatible": "Incompatible client! Please use %s", "multiplayer.socialInteractions.not_available": "Social Interactions are only available in Multiplayer worlds", "chat.disabled.options": "Chat disabled in client options", "chat.disabled.launcher": "Chat disabled by launcher option. Cannot send message", "chat.disabled.profile": "Chat not allowed by account settings. Cannot send message", "disconnect.unknownHost": "Unknown host", "disconnect.loginFailedInfo.insufficientPrivileges": "Multiplayer is disabled. Please check your Microsoft account settings.", "options.accessibility.link": "Accessibility Guide", "options.hideMatchedNames": "Hide Matched Names", "options.hideMatchedNames.tooltip": "3rd-party Servers may send chat messages in non-standard formats.\nWith this option on: hidden players will be matched based on chat sender names.", "options.darkMojangStudiosBackgroundColor": "Monochrome Logo", "options.darkMojangStudiosBackgroundColor.tooltip": "Changes the Mojang Studios loading screen background color to black.", "key.socialInteractions": "Social Interactions Screen", "resourcePack.vanilla.description": "The default resources for Minecraft", "dataPack.vanilla.description": "The default data for Minecraft", "block.minecraft.light": "Light", "block.minecraft.deepslate_gold_ore": "Deepslate Gold Ore", "block.minecraft.deepslate_iron_ore": "Deepslate Iron Ore", "block.minecraft.deepslate_coal_ore": "Deepslate Coal Ore", "block.minecraft.deepslate_diamond_ore": "Deepslate Diamond Ore", "block.minecraft.deepslate_redstone_ore": "Deepslate Redstone Ore", "block.minecraft.deepslate_lapis_ore": "Deepslate Lapis Lazuli Ore", "block.minecraft.water_cauldron": "Water Cauldron", "block.minecraft.lava_cauldron": "Lava Cauldron", "block.minecraft.powder_snow_cauldron": "Powder Snow Cauldron", "block.minecraft.deepslate_emerald_ore": "Deepslate Emerald Ore", "block.minecraft.dirt_path": "Dirt Path", "block.minecraft.potted_azalea_bush": "Potted Azalea", "block.minecraft.potted_flowering_azalea_bush": "Potted Flowering Azalea", "block.minecraft.candle": "Candle", "block.minecraft.white_candle": "White Candle", "block.minecraft.orange_candle": "Orange Candle", "block.minecraft.magenta_candle": "Magenta Candle", "block.minecraft.light_blue_candle": "Light Blue Candle", "block.minecraft.yellow_candle": "Yellow Candle", "block.minecraft.lime_candle": "Lime Candle", "block.minecraft.pink_candle": "Pink Candle", "block.minecraft.gray_candle": "Gray Candle", "block.minecraft.light_gray_candle": "Light Gray Candle", "block.minecraft.cyan_candle": "Cyan Candle", "block.minecraft.purple_candle": "Purple Candle", "block.minecraft.blue_candle": "Blue Candle", "block.minecraft.brown_candle": "Brown Candle", "block.minecraft.green_candle": "Green Candle", "block.minecraft.red_candle": "Red Candle", "block.minecraft.black_candle": "Black Candle", "block.minecraft.candle_cake": "Cake with Candle", "block.minecraft.white_candle_cake": "Cake with White Candle", "block.minecraft.orange_candle_cake": "Cake with Orange Candle", "block.minecraft.magenta_candle_cake": "Cake with Magenta Candle", "block.minecraft.light_blue_candle_cake": "Cake with Light Blue Candle", "block.minecraft.yellow_candle_cake": "Cake with Yellow Candle", "block.minecraft.lime_candle_cake": "Cake with Lime Candle", "block.minecraft.pink_candle_cake": "Cake with Pink Candle", "block.minecraft.gray_candle_cake": "Cake with Gray Candle", "block.minecraft.light_gray_candle_cake": "Cake with Light Gray Candle", "block.minecraft.cyan_candle_cake": "Cake with Cyan Candle", "block.minecraft.purple_candle_cake": "Cake with Purple Candle", "block.minecraft.blue_candle_cake": "Cake with Blue Candle", "block.minecraft.brown_candle_cake": "Cake with Brown Candle", "block.minecraft.green_candle_cake": "Cake with Green Candle", "block.minecraft.red_candle_cake": "Cake with Red Candle", "block.minecraft.black_candle_cake": "Cake with Black Candle", "block.minecraft.amethyst_block": "Block of Amethyst", "block.minecraft.small_amethyst_bud": "Small Amethyst Bud", "block.minecraft.medium_amethyst_bud": "Medium Amethyst Bud", "block.minecraft.large_amethyst_bud": "Large Amethyst Bud", "block.minecraft.amethyst_cluster": "Amethyst Cluster", "block.minecraft.budding_amethyst": "Budding Amethyst", "block.minecraft.calcite": "Calcite", "block.minecraft.tuff": "Tuff", "block.minecraft.tinted_glass": "Tinted Glass", "block.minecraft.dripstone_block": "Dripstone Block", "block.minecraft.pointed_dripstone": "Pointed Dripstone", "block.minecraft.copper_ore": "Copper Ore", "block.minecraft.deepslate_copper_ore": "Deepslate Copper Ore", "block.minecraft.copper_block": "Block of Copper", "block.minecraft.exposed_copper": "Exposed Copper", "block.minecraft.weathered_copper": "Weathered Copper", "block.minecraft.oxidized_copper": "Oxidized Copper", "block.minecraft.cut_copper": "Cut Copper", "block.minecraft.exposed_cut_copper": "Exposed Cut Copper", "block.minecraft.weathered_cut_copper": "Weathered Cut Copper", "block.minecraft.oxidized_cut_copper": "Oxidized Cut Copper", "block.minecraft.cut_copper_stairs": "Cut Copper Stairs", "block.minecraft.exposed_cut_copper_stairs": "Exposed Cut Copper Stairs", "block.minecraft.weathered_cut_copper_stairs": "Weathered Cut Copper Stairs", "block.minecraft.oxidized_cut_copper_stairs": "Oxidized Cut Copper Stairs", "block.minecraft.cut_copper_slab": "Cut Copper Slab", "block.minecraft.exposed_cut_copper_slab": "Exposed Cut Copper Slab", "block.minecraft.weathered_cut_copper_slab": "Weathered Cut Copper Slab", "block.minecraft.oxidized_cut_copper_slab": "Oxidized Cut Copper Slab", "block.minecraft.waxed_copper_block": "Waxed Block of Copper", "block.minecraft.waxed_exposed_copper": "Waxed Exposed Copper", "block.minecraft.waxed_weathered_copper": "Waxed Weathered Copper", "block.minecraft.waxed_oxidized_copper": "Waxed Oxidized Copper", "block.minecraft.waxed_cut_copper": "Waxed Cut Copper", "block.minecraft.waxed_exposed_cut_copper": "Waxed Exposed Cut Copper", "block.minecraft.waxed_weathered_cut_copper": "Waxed Weathered Cut Copper", "block.minecraft.waxed_oxidized_cut_copper": "Waxed Oxidized Cut Copper", "block.minecraft.waxed_cut_copper_stairs": "Waxed Cut Copper Stairs", "block.minecraft.waxed_exposed_cut_copper_stairs": "Waxed Exposed Cut Copper Stairs", "block.minecraft.waxed_weathered_cut_copper_stairs": "Waxed Weathered Cut Copper Stairs", "block.minecraft.waxed_oxidized_cut_copper_stairs": "Waxed Oxidized Cut Copper Stairs", "block.minecraft.waxed_cut_copper_slab": "Waxed Cut Copper Slab", "block.minecraft.waxed_exposed_cut_copper_slab": "Waxed Exposed Cut Copper Slab", "block.minecraft.waxed_weathered_cut_copper_slab": "Waxed Weathered Cut Copper Slab", "block.minecraft.waxed_oxidized_cut_copper_slab": "Waxed Oxidized Cut Copper Slab", "block.minecraft.lightning_rod": "Lightning Rod", "block.minecraft.cave_vines": "Cave Vines", "block.minecraft.cave_vines_plant": "Cave Vines Plant", "block.minecraft.spore_blossom": "Spore Blossom", "block.minecraft.azalea": "Azalea", "block.minecraft.flowering_azalea": "Flowering Azalea", "block.minecraft.azalea_leaves": "Azalea Leaves", "block.minecraft.flowering_azalea_leaves": "Flowering Azalea Leaves", "block.minecraft.moss_carpet": "Moss Carpet", "block.minecraft.moss_block": "Moss Block", "block.minecraft.big_dripleaf": "Big Dripleaf", "block.minecraft.big_dripleaf_stem": "Big Dripleaf Stem", "block.minecraft.small_dripleaf": "Small Dripleaf", "block.minecraft.rooted_dirt": "Rooted Dirt", "block.minecraft.hanging_roots": "Hanging Roots", "block.minecraft.powder_snow": "Powder Snow", "block.minecraft.glow_lichen": "Glow Lichen", "block.minecraft.sculk_sensor": "Sculk Sensor", "block.minecraft.deepslate": "Deepslate", "block.minecraft.cobbled_deepslate": "Cobbled Deepslate", "block.minecraft.cobbled_deepslate_slab": "Cobbled Deepslate Slab", "block.minecraft.cobbled_deepslate_stairs": "Cobbled Deepslate Stairs", "block.minecraft.cobbled_deepslate_wall": "Cobbled Deepslate Wall", "block.minecraft.chiseled_deepslate": "Chiseled Deepslate", "block.minecraft.polished_deepslate": "Polished Deepslate", "block.minecraft.polished_deepslate_slab": "Polished Deepslate Slab", "block.minecraft.polished_deepslate_stairs": "Polished Deepslate Stairs", "block.minecraft.polished_deepslate_wall": "Polished Deepslate Wall", "block.minecraft.deepslate_bricks": "Deepslate Bricks", "block.minecraft.deepslate_brick_slab": "Deepslate Brick Slab", "block.minecraft.deepslate_brick_stairs": "Deepslate Brick Stairs", "block.minecraft.deepslate_brick_wall": "Deepslate Brick Wall", "block.minecraft.deepslate_tiles": "Deepslate Tiles", "block.minecraft.deepslate_tile_slab": "Deepslate Tile Slab", "block.minecraft.deepslate_tile_stairs": "Deepslate Tile Stairs", "block.minecraft.deepslate_tile_wall": "Deepslate Tile Wall", "block.minecraft.cracked_deepslate_bricks": "Cracked Deepslate Bricks", "block.minecraft.cracked_deepslate_tiles": "Cracked Deepslate Tiles", "block.minecraft.infested_deepslate": "Infested Deepslate", "block.minecraft.smooth_basalt": "Smooth Basalt", "block.minecraft.raw_iron_block": "Block of Raw Iron", "block.minecraft.raw_copper_block": "Block of Raw Copper", "block.minecraft.raw_gold_block": "Block of Raw Gold", "item.minecraft.bundle": "Bundle", "item.minecraft.bundle.fullness": "%s/%s", "item.minecraft.raw_copper": "Raw Copper", "item.minecraft.raw_iron": "Raw Iron", "item.minecraft.raw_gold": "Raw Gold", "item.minecraft.copper_ingot": "Copper Ingot", "item.minecraft.powder_snow_bucket": "Powder Snow Bucket", "item.minecraft.axolotl_bucket": "Bucket of Axolotl", "item.minecraft.amethyst_shard": "Amethyst Shard", "item.minecraft.spyglass": "Spyglass", "item.minecraft.glow_berries": "Glow Berries", "item.minecraft.axolotl_spawn_egg": "Axolotl Spawn Egg", "item.minecraft.glow_squid_spawn_egg": "Glow Squid Spawn Egg", "item.minecraft.goat_spawn_egg": "Goat Spawn Egg", "item.minecraft.glow_ink_sac": "Glow Ink Sac", "item.minecraft.glow_item_frame": "Glow Item Frame", "entity.minecraft.axolotl": "Axolotl", "entity.minecraft.glow_item_frame": "Glow Item Frame", "entity.minecraft.glow_squid": "Glow Squid", "entity.minecraft.goat": "Goat", "entity.minecraft.marker": "Marker", "death.attack.stalagmite": "%1$s was impaled on a stalagmite", "death.attack.stalagmite.player": "%1$s was impaled on a stalagmite whilst fighting %2$s", "death.attack.fallingStalactite": "%1$s was skewered by a falling stalactite", "death.attack.fallingStalactite.player": "%1$s was skewered by a falling stalactite whilst fighting %2$s", "death.attack.freeze": "%1$s froze to death", "death.attack.freeze.player": "%1$s was frozen to death by %2$s", "stat.minecraft.play_time": "Time Played", "stat.minecraft.total_world_time": "Time with World Open", "advMode.mode": "Mode", "advMode.type": "Type", "advMode.triggering": "Triggering", "advMode.trackOutput": "Track output", "block.minecraft.banner.base.black": "Fully Black Field", "block.minecraft.banner.base.red": "Fully Red Field", "block.minecraft.banner.base.green": "Fully Green Field", "block.minecraft.banner.base.brown": "Fully Brown Field", "block.minecraft.banner.base.blue": "Fully Blue Field", "block.minecraft.banner.base.purple": "Fully Purple Field", "block.minecraft.banner.base.cyan": "Fully Cyan Field", "block.minecraft.banner.base.light_gray": "Fully Light Gray Field", "block.minecraft.banner.base.gray": "Fully Gray Field", "block.minecraft.banner.base.pink": "Fully Pink Field", "block.minecraft.banner.base.lime": "Fully Lime Field", "block.minecraft.banner.base.yellow": "Fully Yellow Field", "block.minecraft.banner.base.light_blue": "Fully Light Blue Field", "block.minecraft.banner.base.magenta": "Fully Magenta Field", "block.minecraft.banner.base.orange": "Fully Orange Field", "block.minecraft.banner.base.white": "Fully White Field", "subtitles.block.amethyst_block.chime": "Amethyst chimes", "subtitles.block.big_dripleaf.tilt_down": "Dripleaf tilts down", "subtitles.block.big_dripleaf.tilt_up": "Dripleaf tilts up", "subtitles.block.candle.crackle": "Candle crackles", "subtitles.block.cake.add_candle": "Cake squishes", "subtitles.item.honeycomb.wax_on": "Wax on", "subtitles.block.pointed_dripstone.land": "Stalactite crashes down", "subtitles.block.pointed_dripstone.drip_lava": "Lava drips", "subtitles.block.pointed_dripstone.drip_water": "Water drips", "subtitles.block.pointed_dripstone.drip_lava_into_cauldron": "Lava drips into Cauldron", "subtitles.block.pointed_dripstone.drip_water_into_cauldron": "Water drips into Cauldron", "subtitles.block.sculk_sensor.clicking": "Sculk Sensor starts clicking", "subtitles.block.sculk_sensor.clicking_stop": "Sculk Sensor stops clicking", "subtitles.block.sweet_berry_bush.pick_berries": "Berries pop", "subtitles.entity.axolotl.attack": "Axolotl attacks", "subtitles.entity.axolotl.death": "Axolotl dies", "subtitles.entity.axolotl.hurt": "Axolotl hurts", "subtitles.entity.axolotl.idle_air": "Axolotl chirps", "subtitles.entity.axolotl.idle_water": "Axolotl chirps", "subtitles.entity.axolotl.splash": "Axolotl splashes", "subtitles.entity.axolotl.swim": "Axolotl swims", "subtitles.entity.glow_item_frame.add_item": "Glow Item Frame fills", "subtitles.entity.glow_item_frame.break": "Glow Item Frame breaks", "subtitles.entity.glow_item_frame.place": "Glow Item Frame placed", "subtitles.entity.glow_item_frame.remove_item": "Glow Item Frame empties", "subtitles.entity.glow_item_frame.rotate_item": "Glow Item Frame clicks", "subtitles.entity.glow_squid.ambient": "Glow Squid swims", "subtitles.entity.glow_squid.death": "Glow Squid dies", "subtitles.entity.glow_squid.hurt": "Glow Squid hurts", "subtitles.entity.glow_squid.squirt": "Glow Squid shoots ink", "subtitles.entity.goat.ambient": "Goat bleats", "subtitles.entity.goat.screaming.ambient": "Goat bellows", "subtitles.entity.goat.death": "Goat dies", "subtitles.entity.goat.eat": "Goat eats", "subtitles.entity.goat.hurt": "Goat hurts", "subtitles.entity.goat.long_jump": "Goat leaps", "subtitles.entity.goat.milk": "Goat gets milked", "subtitles.entity.goat.prepare_ram": "Goat stomps", "subtitles.entity.goat.ram_impact": "Goat rams", "subtitles.entity.goat.step": "Goat steps", "subtitles.entity.player.freeze_hurt": "Player freezes", "subtitles.entity.skeleton.converted_to_stray": "Skeleton converts to Stray", "subtitles.item.axe.scrape": "Axe scrapes", "subtitles.item.axe.wax_off": "Wax off", "subtitles.item.bone_meal.use": "Bone Meal crinkles", "subtitles.item.bucket.fill_axolotl": "Axolotl scooped", "subtitles.item.spyglass.use": "Spyglass expands", "subtitles.item.spyglass.stop_using": "Spyglass retracts", "subtitles.item.ink_sac.use": "Ink Sac splotches", "subtitles.item.glow_ink_sac.use": "Glow Ink Sac splotches", "subtitles.item.dye.use": "Dye stains", "debug.profiling.help": "F3 + L = Start/stop profiling", "debug.profiling.start": "Profiling started for %s seconds. Use F3 + L to stop early", "debug.profiling.stop": "Profiling ended. Saved results to %s", "tutorial.bundleInsert.title": "Use a Bundle", "tutorial.bundleInsert.description": "Right Click to add items", "tutorial.socialInteractions.title": "Social Interactions", "tutorial.socialInteractions.description": "Press %s to open", "advancements.adventure.walk_on_powder_snow_with_leather_boots.title": "Light as a Rabbit", "advancements.adventure.walk_on_powder_snow_with_leather_boots.description": "Walk on powder snow...without sinking in it", "advancements.adventure.lightning_rod_with_villager_no_fire.title": "Surge Protector", "advancements.adventure.lightning_rod_with_villager_no_fire.description": "Protect a villager from an undesired shock without starting a fire", "advancements.adventure.spyglass_at_parrot.title": "Is It a Bird?", "advancements.adventure.spyglass_at_parrot.description": "Look at a parrot through a spyglass", "advancements.adventure.spyglass_at_ghast.title": "Is It a Balloon?", "advancements.adventure.spyglass_at_ghast.description": "Look at a ghast through a spyglass", "advancements.adventure.spyglass_at_dragon.title": "Is It a Plane?", "advancements.adventure.spyglass_at_dragon.description": "Look at the Ender Dragon through a spyglass", "advancements.husbandry.make_a_sign_glow.title": "Glow and Behold!", "advancements.husbandry.make_a_sign_glow.description": "Make the text of a sign glow", "advancements.husbandry.ride_a_boat_with_a_goat.title": "Whatever Floats Your Goat!", "advancements.husbandry.ride_a_boat_with_a_goat.description": "Get in a Boat and float with a Goat", "advancements.husbandry.axolotl_in_a_bucket.title": "The Cutest Predator", "advancements.husbandry.axolotl_in_a_bucket.description": "Catch an axolotl in a bucket", "advancements.husbandry.kill_axolotl_target.title": "The Healing Power of Friendship!", "advancements.husbandry.kill_axolotl_target.description": "Team up with an axolotl and win a fight", "advancements.husbandry.wax_on.title": "Wax On", "advancements.husbandry.wax_on.description": "Apply Honeycomb to a Copper block!", "advancements.husbandry.wax_off.title": "Wax Off", "advancements.husbandry.wax_off.description": "Scrape Wax off of a Copper block!", "commands.debug.function.success.single": "Traced %s commands from function '%s' to output file %s", "commands.debug.function.success.multiple": "Traced %s commands from %s functions to output file %s", "commands.debug.function.noRecursion": "Can't trace from inside of function", "commands.debug.function.traceFailed": "Failed to trace function", "commands.give.failed.toomanyitems": "Can't give more than %s of %s", "commands.perf.started": "Started 10 second performance profiling run (use '/perf stop' to stop early)", "commands.perf.stopped": "Stopped performance profiling after %s seconds and %s ticks (%s ticks per second)", "commands.perf.reportSaved": "Created debug report in %s", "commands.perf.reportFailed": "Failed to create debug report", "commands.perf.notRunning": "The performance profiler hasn't started", "commands.perf.alreadyRunning": "The performance profiler is already started", "commands.item.target.not_a_container": "Target position %s, %s, %s is not a container", "commands.item.source.not_a_container": "Source position %s, %s, %s is not a container", "commands.item.target.no_such_slot": "The target does not have slot %s", "commands.item.source.no_such_slot": "The source does not have slot %s", "commands.item.target.no_changes": "No targets accepted item into slot %s", "commands.item.target.no_changed.known_item": "No targets accepted item %s into slot %s", "commands.item.block.set.success": "Replaced a slot at %s, %s, %s with %s", "commands.item.entity.set.success.single": "Replaced a slot on %s with %s", "commands.item.entity.set.success.multiple": "Replaced a slot on %s entities with %s", "argument.angle.invalid": "Invalid angle", "argument.pos.outofbounds": "That position is outside the allowed boundaries.", "commands.worldborder.set.failed.small": "World border cannot be smaller than 1 block wide", "commands.worldborder.set.failed.big": "World border cannot be bigger than %s blocks wide", "item_modifier.unknown": "Unknown item modifier: %s", "biome.minecraft.dripstone_caves": "Dripstone Caves", "biome.minecraft.lush_caves": "Lush Caves", "gamerule.freezeDamage": "Deal freeze damage", "gamerule.playersSleepingPercentage": "Sleep percentage", "gamerule.playersSleepingPercentage.description": "The percentage of players who must be sleeping to skip the night.", "mirror.none": "|", "mirror.left_right": "← →", "mirror.front_back": "↑ ↓", "sleep.not_possible": "No amount of rest can pass this night", "sleep.players_sleeping": "%s/%s players sleeping", "sleep.skipping_night": "Sleeping through this night" }, "1.16.2": { "multiplayer.disconnect.missing_tags": "Incomplete set of tags received from server.\nPlease contact server operator.", "chat.square_brackets": "[%s]", "disconnect.exceeded_packet_rate": "Kicked for exceeding packet rate limit", "item.minecraft.piglin_brute_spawn_egg": "Piglin Brute Spawn Egg", "entity.minecraft.piglin_brute": "Piglin Brute", "potion.withAmplifier": "%s %s", "potion.withDuration": "%s (%s)", "advancements.sad_label": ":(", "subtitles.entity.parrot.imitate.piglin_brute": "Parrot snorts mightily", "subtitles.entity.piglin_brute.ambient": "Piglin Brute snorts", "subtitles.entity.piglin_brute.angry": "Piglin Brute snorts angrily", "subtitles.entity.piglin_brute.death": "Piglin Brute dies", "subtitles.entity.piglin_brute.hurt": "Piglin Brute hurts", "subtitles.entity.piglin_brute.step": "Piglin Brute steps", "subtitles.entity.piglin_brute.converted_to_zombified": "Piglin Brute converts to Zombified Piglin", "command.context.parse_error": "%s at position %s: %s", "commands.list.nameAndId": "%s (%s)", "argument.angle.incomplete": "Incomplete (expected 1 angle)", "commands.summon.failed.uuid": "Unable to summon entity due to duplicate UUIDs" }, "1.16": { "gui.recipebook.search_hint": "Search...", "selectWorld.edit.export_worldgen_settings": "Export World Generation Settings", "selectWorld.edit.export_worldgen_settings.success": "Exported", "selectWorld.edit.export_worldgen_settings.failure": "Export failed", "selectWorld.locked": "Locked by another running instance of Minecraft", "selectWorld.backupQuestion.experimental": "Worlds using Experimental Settings are not supported", "selectWorld.backupWarning.experimental": "This world uses experimental settings that could stop working at any time. We cannot guarantee it will load or work. Here be dragons!", "selectWorld.access_failure": "Failed to access world", "selectWorld.delete_failure": "Failed to delete world", "selectWorld.data_read": "Reading world data...", "createWorld.preparing": "Preparing for world creation...", "datapackFailure.title": "Errors in currently selected datapacks prevented world from loading.\nYou can either try to load only with vanilla datapack (\"safe mode\") or go back to title screen and fix it manually.", "datapackFailure.safeMode": "Safe Mode", "editGamerule.title": "Edit Game Rules", "editGamerule.default": "Default: %s", "selectWorld.gameRules": "Game Rules", "selectWorld.dataPacks": "Data Packs", "selectWorld.import_worldgen_settings": "Import Settings", "selectWorld.import_worldgen_settings.select_file": "Select settings file (.json)", "selectWorld.import_worldgen_settings.failure": "Error importing settings", "selectWorld.import_worldgen_settings.experimental.title": "Warning! These settings are using experimental features", "selectWorld.import_worldgen_settings.experimental.question": "These settings are experimental and could one day stop working. Do you wish to proceed?", "selectWorld.import_worldgen_settings.deprecated.title": "Warning! These settings are using deprecated features", "selectWorld.import_worldgen_settings.deprecated.question": "Some features used are deprecated and will stop working in the future. Do you wish to proceed?", "generator.large_biomes": "Large Biomes", "generator.custom": "Custom", "generator.single_biome_surface": "Single Biome", "generator.single_biome_caves": "Caves", "generator.single_biome_floating_islands": "Floating Islands", "multiplayer.status.ping": "%s ms", "chat.queue": "[+%s pending lines]", "options.entityDistanceScaling": "Entity Distance", "options.entityDistancePercent": "%s%%", "options.graphics.fabulous.tooltip": "%s graphics uses screen shaders for drawing weather, clouds and particles behind translucent blocks and water.\nThis may severely impact performance for portable devices and 4K displays.", "options.graphics.fabulous": "Fabulous!", "options.graphics.fancy.tooltip": "Fancy graphics balances performance and quality for the majority of machines.\nWeather, clouds and particles may not appear behind translucent blocks or water.", "options.graphics.fast.tooltip": "Fast graphics reduces the amount of visible rain and snow.\nTransparency effects are disabled for various blocks such as tree-leaves.", "options.graphics.warning.title": "Graphics Device Unsupported", "options.graphics.warning.message": "Your graphics device is detected as unsupported for the %s graphics option.\n\nYou may ignore this and continue, however support will not be provided for your device if you choose to use %s graphics.", "options.graphics.warning.renderer": "Renderer detected: [%s]", "options.graphics.warning.vendor": "Vendor detected: [%s]", "options.graphics.warning.version": "OpenGL Version detected: [%s]", "options.graphics.warning.accept": "Continue without support", "options.graphics.warning.cancel": "Take me back", "options.chat.line_spacing": "Line Spacing", "options.chat.delay_none": "Chat Delay: None", "options.chat.delay": "Chat Delay: %s seconds", "title.multiplayer.disabled": "Multiplayer is disabled, please check your launcher settings.", "key.swapOffhand": "Swap Item With Offhand", "pack.available.title": "Available", "pack.selected.title": "Selected", "pack.incompatible": "Incompatible", "pack.incompatible.old": "(Made for an older version of Minecraft)", "pack.incompatible.new": "(Made for a newer version of Minecraft)", "pack.incompatible.confirm.title": "Are you sure you want to load this pack?", "pack.incompatible.confirm.old": "This pack was made for an older version of Minecraft and may no longer work correctly.", "pack.incompatible.confirm.new": "This pack was made for a newer version of Minecraft and may no longer work correctly.", "pack.dropInfo": "Drag and drop files into this window to add packs", "pack.dropConfirm": "Do you want to add following packs to Minecraft?", "pack.copyFailure": "Failed to copy packs", "pack.nameAndSource": "%s (%s)", "pack.openFolder": "Open Pack Folder", "pack.folderInfo": "(Place pack files here)", "dataPack.title": "Select Data Packs", "dataPack.validation.working": "Validating selected data packs...", "dataPack.validation.failed": "Data pack validation failed!", "dataPack.validation.back": "Go Back", "dataPack.validation.reset": "Reset to Default", "block.minecraft.nether_gold_ore": "Nether Gold Ore", "block.minecraft.soul_torch": "Soul Torch", "block.minecraft.soul_wall_torch": "Soul Wall Torch", "block.minecraft.respawn_anchor": "Respawn Anchor", "block.minecraft.spawn.not_valid": "You have no home bed or charged respawn anchor, or it was obstructed", "block.minecraft.set_spawn": "Respawn point set", "block.minecraft.warped_wart_block": "Warped Wart Block", "block.minecraft.warped_stem": "Warped Stem", "block.minecraft.stripped_warped_stem": "Stripped Warped Stem", "block.minecraft.warped_hyphae": "Warped Hyphae", "block.minecraft.stripped_warped_hyphae": "Stripped Warped Hyphae", "block.minecraft.crimson_stem": "Crimson Stem", "block.minecraft.stripped_crimson_stem": "Stripped Crimson Stem", "block.minecraft.crimson_hyphae": "Crimson Hyphae", "block.minecraft.stripped_crimson_hyphae": "Stripped Crimson Hyphae", "block.minecraft.warped_nylium": "Warped Nylium", "block.minecraft.crimson_nylium": "Crimson Nylium", "block.minecraft.warped_fungus": "Warped Fungus", "block.minecraft.crimson_fungus": "Crimson Fungus", "block.minecraft.crimson_roots": "Crimson Roots", "block.minecraft.warped_roots": "Warped Roots", "block.minecraft.nether_sprouts": "Nether Sprouts", "block.minecraft.shroomlight": "Shroomlight", "block.minecraft.weeping_vines": "Weeping Vines", "block.minecraft.weeping_vines_plant": "Weeping Vines Plant", "block.minecraft.twisting_vines": "Twisting Vines", "block.minecraft.twisting_vines_plant": "Twisting Vines Plant", "block.minecraft.soul_soil": "Soul Soil", "block.minecraft.basalt": "Basalt", "block.minecraft.polished_basalt": "Polished Basalt", "block.minecraft.warped_planks": "Warped Planks", "block.minecraft.warped_slab": "Warped Slab", "block.minecraft.warped_pressure_plate": "Warped Pressure Plate", "block.minecraft.warped_fence": "Warped Fence", "block.minecraft.warped_trapdoor": "Warped Trapdoor", "block.minecraft.warped_fence_gate": "Warped Fence Gate", "block.minecraft.warped_stairs": "Warped Stairs", "block.minecraft.warped_button": "Warped Button", "block.minecraft.warped_door": "Warped Door", "block.minecraft.warped_sign": "Warped Sign", "block.minecraft.warped_wall_sign": "Warped Wall Sign", "block.minecraft.crimson_planks": "Crimson Planks", "block.minecraft.crimson_slab": "Crimson Slab", "block.minecraft.crimson_pressure_plate": "Crimson Pressure Plate", "block.minecraft.crimson_fence": "Crimson Fence", "block.minecraft.crimson_trapdoor": "Crimson Trapdoor", "block.minecraft.crimson_fence_gate": "Crimson Fence Gate", "block.minecraft.crimson_stairs": "Crimson Stairs", "block.minecraft.crimson_button": "Crimson Button", "block.minecraft.crimson_door": "Crimson Door", "block.minecraft.crimson_sign": "Crimson Sign", "block.minecraft.crimson_wall_sign": "Crimson Wall Sign", "block.minecraft.soul_fire": "Soul Fire", "block.minecraft.potted_crimson_fungus": "Potted Crimson Fungus", "block.minecraft.potted_warped_fungus": "Potted Warped Fungus", "block.minecraft.potted_crimson_roots": "Potted Crimson Roots", "block.minecraft.potted_warped_roots": "Potted Warped Roots", "block.minecraft.target": "Target", "block.minecraft.soul_lantern": "Soul Lantern", "block.minecraft.soul_campfire": "Soul Campfire", "block.minecraft.lodestone": "Lodestone", "block.minecraft.netherite_block": "Block of Netherite", "block.minecraft.ancient_debris": "Ancient Debris", "block.minecraft.crying_obsidian": "Crying Obsidian", "block.minecraft.blackstone": "Blackstone", "block.minecraft.blackstone_slab": "Blackstone Slab", "block.minecraft.blackstone_stairs": "Blackstone Stairs", "block.minecraft.blackstone_wall": "Blackstone Wall", "block.minecraft.polished_blackstone_bricks": "Polished Blackstone Bricks", "block.minecraft.polished_blackstone_brick_slab": "Polished Blackstone Brick Slab", "block.minecraft.polished_blackstone_brick_stairs": "Polished Blackstone Brick Stairs", "block.minecraft.polished_blackstone_brick_wall": "Polished Blackstone Brick Wall", "block.minecraft.chiseled_polished_blackstone": "Chiseled Polished Blackstone", "block.minecraft.cracked_polished_blackstone_bricks": "Cracked Polished Blackstone Bricks", "block.minecraft.gilded_blackstone": "Gilded Blackstone", "block.minecraft.polished_blackstone": "Polished Blackstone", "block.minecraft.polished_blackstone_wall": "Polished Blackstone Wall", "block.minecraft.polished_blackstone_slab": "Polished Blackstone Slab", "block.minecraft.polished_blackstone_stairs": "Polished Blackstone Stairs", "block.minecraft.polished_blackstone_pressure_plate": "Polished Blackstone Pressure Plate", "block.minecraft.polished_blackstone_button": "Polished Blackstone Button", "block.minecraft.cracked_nether_bricks": "Cracked Nether Bricks", "block.minecraft.chiseled_nether_bricks": "Chiseled Nether Bricks", "block.minecraft.quartz_bricks": "Quartz Bricks", "block.minecraft.chain": "Chain", "item.minecraft.music_disc_pigstep": "Music Disc", "item.minecraft.music_disc_pigstep.desc": "Lena Raine - Pigstep", "item.minecraft.hoglin_spawn_egg": "Hoglin Spawn Egg", "item.minecraft.piglin_spawn_egg": "Piglin Spawn Egg", "item.minecraft.strider_spawn_egg": "Strider Spawn Egg", "item.minecraft.zoglin_spawn_egg": "Zoglin Spawn Egg", "item.minecraft.zombified_piglin_spawn_egg": "Zombified Piglin Spawn Egg", "item.minecraft.piglin_banner_pattern": "Banner Pattern", "item.minecraft.piglin_banner_pattern.desc": "Snout", "item.minecraft.lodestone_compass": "Lodestone Compass", "item.minecraft.netherite_scrap": "Netherite Scrap", "item.minecraft.netherite_ingot": "Netherite Ingot", "item.minecraft.netherite_helmet": "Netherite Helmet", "item.minecraft.netherite_chestplate": "Netherite Chestplate", "item.minecraft.netherite_leggings": "Netherite Leggings", "item.minecraft.netherite_boots": "Netherite Boots", "item.minecraft.netherite_axe": "Netherite Axe", "item.minecraft.netherite_pickaxe": "Netherite Pickaxe", "item.minecraft.netherite_hoe": "Netherite Hoe", "item.minecraft.netherite_shovel": "Netherite Shovel", "item.minecraft.netherite_sword": "Netherite Sword", "item.minecraft.warped_fungus_on_a_stick": "Warped Fungus on a Stick", "container.upgrade": "Upgrade Gear", "jigsaw_block.pool": "Target pool:", "jigsaw_block.name": "Name:", "jigsaw_block.target": "Target name:", "jigsaw_block.levels": "Levels: %s", "jigsaw_block.keep_jigsaws": "Keep Jigsaws: ", "jigsaw_block.generate": "Generate", "jigsaw_block.joint_label": "Joint type:", "jigsaw_block.joint.rollable": "Rollable", "jigsaw_block.joint.aligned": "Aligned", "entity.minecraft.hoglin": "Hoglin", "entity.minecraft.piglin": "Piglin", "entity.minecraft.strider": "Strider", "entity.minecraft.zoglin": "Zoglin", "entity.minecraft.zombified_piglin": "Zombified Piglin", "death.fell.accident.weeping_vines": "%1$s fell off some weeping vines", "death.fell.accident.twisting_vines": "%1$s fell off some twisting vines", "death.fell.accident.scaffolding": "%1$s fell off scaffolding", "death.fell.accident.other_climbable": "%1$s fell while climbing", "death.attack.magic.player": "%1$s was killed by magic whilst trying to escape %2$s", "death.attack.witherSkull": "%1$s was shot by a %2$s's skull", "death.attack.fireworks.item": "%1$s went off with a bang due to a firework fired from %3$s by %2$s", "death.attack.badRespawnPoint.message": "%1$s was killed by %2$s", "death.attack.badRespawnPoint.link": "Intentional Game Design", "enchantment.minecraft.soul_speed": "Soul Speed", "gui.entity_tooltip.type": "Type: %s", "stat.minecraft.target_hit": "Targets Hit", "stat.minecraft.interact_with_smithing_table": "Interactions with Smithing Table", "stat.minecraft.strider_one_cm": "Distance by Strider", "attribute.unknown": "Unknown attribute", "attribute.name.horse.jump_strength": "Horse Jump Strength", "attribute.name.zombie.spawn_reinforcements": "Zombie Reinforcements", "attribute.name.generic.max_health": "Max Health", "attribute.name.generic.follow_range": "Mob Follow Range", "attribute.name.generic.knockback_resistance": "Knockback Resistance", "attribute.name.generic.movement_speed": "Speed", "attribute.name.generic.flying_speed": "Flying Speed", "attribute.name.generic.attack_damage": "Attack Damage", "attribute.name.generic.attack_knockback": "Attack Knockback", "attribute.name.generic.attack_speed": "Attack Speed", "attribute.name.generic.armor_toughness": "Armor Toughness", "block.minecraft.banner.piglin.black": "Black Snout", "block.minecraft.banner.piglin.red": "Red Snout", "block.minecraft.banner.piglin.green": "Green Snout", "block.minecraft.banner.piglin.brown": "Brown Snout", "block.minecraft.banner.piglin.blue": "Blue Snout", "block.minecraft.banner.piglin.purple": "Purple Snout", "block.minecraft.banner.piglin.cyan": "Cyan Snout", "block.minecraft.banner.piglin.light_gray": "Light Gray Snout", "block.minecraft.banner.piglin.gray": "Gray Snout", "block.minecraft.banner.piglin.pink": "Pink Snout", "block.minecraft.banner.piglin.lime": "Lime Snout", "block.minecraft.banner.piglin.yellow": "Yellow Snout", "block.minecraft.banner.piglin.light_blue": "Light Blue Snout", "block.minecraft.banner.piglin.magenta": "Magenta Snout", "block.minecraft.banner.piglin.orange": "Orange Snout", "block.minecraft.banner.piglin.white": "White Snout", "subtitles.block.beacon.activate": "Beacon activates", "subtitles.block.beacon.ambient": "Beacon hums", "subtitles.block.beacon.deactivate": "Beacon deactivates", "subtitles.block.beacon.power_select": "Beacon power selected", "subtitles.block.composter.empty": "Composter emptied", "subtitles.block.composter.fill": "Composter filled", "subtitles.block.composter.ready": "Composter composts", "subtitles.block.conduit.activate": "Conduit activates", "subtitles.block.conduit.ambient": "Conduit pulses", "subtitles.block.conduit.attack.target": "Conduit attacks", "subtitles.block.conduit.deactivate": "Conduit deactivates", "subtitles.block.enchantment_table.use": "Enchanting Table used", "subtitles.block.end_portal.spawn": "End Portal opens", "subtitles.block.end_portal_frame.fill": "Eye of Ender attaches", "subtitles.block.portal.travel": "Portal noise fades", "subtitles.block.portal.trigger": "Portal noise intensifies", "subtitles.block.pumpkin.carve": "Shears carve", "subtitles.block.respawn_anchor.ambient": "Portal whooshes", "subtitles.block.respawn_anchor.charge": "Respawn Anchor is charged", "subtitles.block.respawn_anchor.deplete": "Respawn Anchor depletes", "subtitles.block.respawn_anchor.set_spawn": "Respawn Anchor sets spawn", "subtitles.block.smithing_table.use": "Smithing Table used", "subtitles.entity.boat.paddle_land": "Rowing", "subtitles.entity.boat.paddle_water": "Rowing", "subtitles.entity.cat.beg_for_food": "Cat begs", "subtitles.entity.cat.eat": "Cat eats", "subtitles.entity.cat.hiss": "Cat hisses", "subtitles.entity.cat.purr": "Cat purrs", "subtitles.entity.donkey.eat": "Donkey eats", "subtitles.entity.drowned.ambient_water": "Drowned gurgles", "subtitles.entity.ender_eye.death": "Eye of Ender falls", "subtitles.entity.fishing_bobber.retrieve": "Bobber retrieved", "subtitles.entity.fox.teleport": "Fox teleports", "subtitles.entity.hoglin.ambient": "Hoglin growls", "subtitles.entity.hoglin.angry": "Hoglin growls angrily", "subtitles.entity.hoglin.attack": "Hoglin attacks", "subtitles.entity.hoglin.converted_to_zombified": "Hoglin converts to Zoglin", "subtitles.entity.hoglin.death": "Hoglin dies", "subtitles.entity.hoglin.hurt": "Hoglin hurts", "subtitles.entity.hoglin.retreat": "Hoglin retreats", "subtitles.entity.hoglin.step": "Hoglin steps", "subtitles.entity.mule.angry": "Mule neighs", "subtitles.entity.mule.eat": "Mule eats", "subtitles.entity.parrot.fly": "Parrot flutters", "subtitles.entity.parrot.imitate.hoglin": "Parrot growls", "subtitles.entity.parrot.imitate.piglin": "Parrot snorts", "subtitles.entity.parrot.imitate.zoglin": "Parrot growls", "subtitles.entity.piglin.admiring_item": "Piglin admires item", "subtitles.entity.piglin.ambient": "Piglin snorts", "subtitles.entity.piglin.angry": "Piglin snorts angrily", "subtitles.entity.piglin.celebrate": "Piglin celebrates", "subtitles.entity.piglin.converted_to_zombified": "Piglin converts to Zombified Piglin", "subtitles.entity.piglin.death": "Piglin dies", "subtitles.entity.piglin.hurt": "Piglin hurts", "subtitles.entity.piglin.jealous": "Piglin snorts enviously", "subtitles.entity.piglin.retreat": "Piglin retreats", "subtitles.entity.piglin.step": "Piglin steps", "subtitles.entity.player.attack.crit": "Critical attack", "subtitles.entity.player.attack.knockback": "Knockback attack", "subtitles.entity.player.attack.strong": "Strong attack", "subtitles.entity.player.attack.sweep": "Sweeping attack", "subtitles.entity.player.attack.weak": "Weak attack", "subtitles.entity.player.hurt_drown": "Player drowning", "subtitles.entity.player.hurt_on_fire": "Player burns", "subtitles.entity.strider.death": "Strider dies", "subtitles.entity.strider.eat": "Strider eats", "subtitles.entity.strider.happy": "Strider warbles", "subtitles.entity.strider.hurt": "Strider hurts", "subtitles.entity.strider.idle": "Strider chirps", "subtitles.entity.strider.retreat": "Strider retreats", "subtitles.entity.tropical_fish.death": "Tropical Fish dies", "subtitles.entity.tropical_fish.flop": "Tropical Fish flops", "subtitles.entity.tropical_fish.hurt": "Tropical Fish hurts", "subtitles.entity.wandering_trader.disappeared": "Wandering Trader disappears", "subtitles.entity.wandering_trader.drink_milk": "Wandering Trader drinks milk", "subtitles.entity.wandering_trader.drink_potion": "Wandering Trader drinks potion", "subtitles.entity.wandering_trader.reappeared": "Wandering Trader appears", "subtitles.entity.zoglin.ambient": "Zoglin growls", "subtitles.entity.zoglin.angry": "Zoglin growls angrily", "subtitles.entity.zoglin.attack": "Zoglin attacks", "subtitles.entity.zoglin.death": "Zoglin dies", "subtitles.entity.zoglin.hurt": "Zoglin hurts", "subtitles.entity.zoglin.step": "Zoglin steps", "subtitles.entity.zombie.attack_wooden_door": "Door shakes", "subtitles.entity.zombie.break_wooden_door": "Door breaks", "subtitles.entity.zombie.destroy_egg": "Turtle Egg stomped", "subtitles.entity.zombified_piglin.ambient": "Zombified Piglin grunts", "subtitles.entity.zombified_piglin.angry": "Zombified Piglin grunts angrily", "subtitles.entity.zombified_piglin.death": "Zombified Piglin dies", "subtitles.entity.zombified_piglin.hurt": "Zombified Piglin hurts", "subtitles.item.armor.equip_netherite": "Netherite armor clanks", "subtitles.item.bottle.empty": "Bottle empties", "subtitles.item.bucket.fill_fish": "Fish captured", "subtitles.item.lodestone_compass.lock": "Lodestone Compass locks onto Lodestone", "subtitles.particle.soul_escape": "Soul escapes", "subtitles.ui.cartography_table.take_result": "Map drawn", "subtitles.ui.loom.take_result": "Loom used", "subtitles.ui.stonecutter.take_result": "Stonecutter used", "debug.gamemodes.help": "F3 + F4 = Open game mode switcher", "debug.gamemodes.error": "Unable to open game mode switcher, no permission", "debug.gamemodes.press_f4": "[ F4 ]", "debug.gamemodes.select_next": "%s Next", "advancements.adventure.bullseye.title": "Bullseye", "advancements.adventure.bullseye.description": "Hit the bullseye of a Target block from at least 30 meters away", "advancements.husbandry.netherite_hoe.title": "Serious Dedication", "advancements.husbandry.netherite_hoe.description": "Use a Netherite ingot to upgrade a hoe, and then reevaluate your life choices", "advancements.nether.obtain_ancient_debris.title": "Hidden in the Depths", "advancements.nether.obtain_ancient_debris.description": "Obtain Ancient Debris", "advancements.nether.netherite_armor.title": "Cover Me in Debris", "advancements.nether.netherite_armor.description": "Get a full suit of Netherite armor", "advancements.nether.use_lodestone.title": "Country Lode, Take Me Home", "advancements.nether.use_lodestone.description": "Use a Compass on a Lodestone", "advancements.nether.obtain_crying_obsidian.title": "Who is Cutting Onions?", "advancements.nether.obtain_crying_obsidian.description": "Obtain Crying Obsidian", "advancements.nether.charge_respawn_anchor.title": "Not Quite \"Nine\" Lives", "advancements.nether.charge_respawn_anchor.description": "Charge a Respawn Anchor to the maximum", "advancements.nether.ride_strider.title": "This Boat Has Legs", "advancements.nether.ride_strider.description": "Ride a Strider with a Warped Fungus on a Stick", "advancements.nether.explore_nether.title": "Hot Tourist Destinations", "advancements.nether.explore_nether.description": "Explore all Nether biomes", "advancements.nether.find_bastion.title": "Those Were the Days", "advancements.nether.find_bastion.description": "Enter a Bastion Remnant", "advancements.nether.loot_bastion.title": "War Pigs", "advancements.nether.loot_bastion.description": "Loot a chest in a Bastion Remnant", "advancements.nether.distract_piglin.title": "Oh Shiny", "advancements.nether.distract_piglin.description": "Distract Piglins with gold", "argument.uuid.invalid": "Invalid UUID", "commands.attribute.failed.entity": "%s is not a valid entity for this command", "commands.attribute.failed.no_attribute": "Entity %s has no attribute %s", "commands.attribute.failed.no_modifier": "Attribute %s for entity %s has no modifier %s", "commands.attribute.failed.modifier_already_present": "Modifier %s is already present on attribute %s for entity %s", "commands.attribute.value.get.success": "Value of attribute %s for entity %s is %s", "commands.attribute.base_value.get.success": "Base value of attribute %s for entity %s is %s", "commands.attribute.base_value.set.success": "Base value for attribute %s for entity %s set to %s", "commands.attribute.modifier.add.success": "Added modifier %s to attribute %s for entity %s", "commands.attribute.modifier.remove.success": "Removed modifier %s from attribute %s for entity %s", "commands.attribute.modifier.value.get.success": "Value of modifier %s on attribute %s for entity %s is %s", "commands.locatebiome.success": "The nearest %s is at %s (%s blocks away)", "commands.teleport.invalidPosition": "Invalid position for teleport", "commands.reload.failure": "Reload failed, keeping old data", "commands.datapack.modify.enable": "Enabling data pack %s", "commands.datapack.modify.disable": "Disabling data pack %s", "commands.locatebiome.notFound": "Could not find a biome of type \"%s\" within reasonable distance", "commands.locatebiome.invalid": "There is no biome with type \"%s\"", "commands.summon.invalidPosition": "Invalid position for summon", "biome.minecraft.nether_wastes": "Nether Wastes", "biome.minecraft.soul_sand_valley": "Soul Sand Valley", "biome.minecraft.warped_forest": "Warped Forest", "biome.minecraft.crimson_forest": "Crimson Forest", "biome.minecraft.basalt_deltas": "Basalt Deltas", "gamerule.announceAdvancements": "Announce advancements", "gamerule.commandBlockOutput": "Broadcast command block output", "gamerule.disableElytraMovementCheck": "Disable elytra movement check", "gamerule.disableRaids": "Disable raids", "gamerule.doDaylightCycle": "Advance in-game time", "gamerule.doEntityDrops": "Drop entity equipment", "gamerule.doEntityDrops.description": "Controls drops from minecarts (including inventories), item frames, boats, etc.", "gamerule.doFireTick": "Update fire", "gamerule.doImmediateRespawn": "Respawn immediately", "gamerule.doInsomnia": "Spawn phantoms", "gamerule.doLimitedCrafting": "Require recipe for crafting", "gamerule.doLimitedCrafting.description": "If enabled, players will be able to craft only unlocked recipes", "gamerule.doMobLoot": "Drop mob loot", "gamerule.doMobLoot.description": "Controls resource drops from mobs, including experience orbs", "gamerule.doMobSpawning": "Spawn mobs", "gamerule.doMobSpawning.description": "Some entities might have separate rules", "gamerule.doPatrolSpawning": "Spawn pillager patrols", "gamerule.doTileDrops": "Drop blocks", "gamerule.doTileDrops.description": "Controls resource drops from blocks, including experience orbs", "gamerule.doTraderSpawning": "Spawn wandering traders", "gamerule.doWeatherCycle": "Update weather", "gamerule.drowningDamage": "Deal drowning damage", "gamerule.fallDamage": "Deal fall damage", "gamerule.fireDamage": "Deal fire damage", "gamerule.forgiveDeadPlayers": "Forgive dead players", "gamerule.forgiveDeadPlayers.description": "Angered neutral mobs stop being angry when the targeted player dies nearby.", "gamerule.keepInventory": "Keep inventory after death", "gamerule.logAdminCommands": "Broadcast admin commands", "gamerule.maxCommandChainLength": "Command chain size limit", "gamerule.maxCommandChainLength.description": "Applies to command block chains and functions", "gamerule.maxEntityCramming": "Entity cramming threshold", "gamerule.mobGriefing": "Allow destructive mob actions", "gamerule.naturalRegeneration": "Regenerate health", "gamerule.randomTickSpeed": "Random tick speed rate", "gamerule.reducedDebugInfo": "Reduce debug info", "gamerule.reducedDebugInfo.description": "Limits contents of debug screen", "gamerule.sendCommandFeedback": "Send command feedback", "gamerule.showDeathMessages": "Show death messages", "gamerule.spawnRadius": "Respawn location radius", "gamerule.spectatorsGenerateChunks": "Allow spectators to generate terrain", "gamerule.universalAnger": "Universal anger", "gamerule.universalAnger.description": "Angered neutral mobs attack any nearby player, not just the player that angered them. Works best if forgiveDeadPlayers is disabled.", "gamerule.category.chat": "Chat", "gamerule.category.spawning": "Spawning", "gamerule.category.updates": "World updates", "gamerule.category.drops": "Drops", "gamerule.category.mobs": "Mobs", "gamerule.category.player": "Player", "gamerule.category.misc": "Miscellaneous", "pack.source.builtin": "built-in", "pack.source.world": "world", "pack.source.local": "local", "pack.source.server": "server" }, "1.15": { "narration.suggestion.tooltip": "Selected suggestion %d out of %d: %s (%s)", "narration.suggestion": "Selected suggestion %d out of %d: %s", "chat.copy.click": "Click to copy to Clipboard", "options.biomeBlendRadius.1": "OFF (Fastest)", "options.biomeBlendRadius.3": "3x3 (Fast)", "options.biomeBlendRadius.5": "5x5 (Normal)", "options.biomeBlendRadius.7": "7x7 (High)", "options.biomeBlendRadius.9": "9x9 (Very High)", "options.biomeBlendRadius.11": "11x11 (Extreme)", "options.biomeBlendRadius.13": "13x13 (Showoff)", "options.biomeBlendRadius.15": "15x15 (Maximum)", "options.key.toggle": "Toggle", "options.key.hold": "Hold", "resourcePack.load_fail": "Resource reload failed", "block.minecraft.bed.set_spawn": "Respawn point set", "block.minecraft.beehive": "Beehive", "block.minecraft.bee_nest": "Bee Nest", "block.minecraft.honey_block": "Honey Block", "block.minecraft.honeycomb_block": "Honeycomb Block", "item.minecraft.bee_spawn_egg": "Bee Spawn Egg", "item.minecraft.honey_bottle": "Honey Bottle", "item.minecraft.honeycomb": "Honeycomb", "entity.minecraft.bee": "Bee", "death.attack.sting": "%1$s was stung to death", "death.attack.sting.player": "%1$s was stung to death by %2$s", "stat.minecraft.interact_with_anvil": "Interactions with Anvil", "stat.minecraft.interact_with_grindstone": "Interactions with Grindstone", "subtitles.block.beehive.enter": "Bee enters hive", "subtitles.block.beehive.exit": "Bee leaves hive", "subtitles.block.beehive.drip": "Honey drips", "subtitles.block.beehive.work": "Bees work", "subtitles.block.beehive.shear": "Shears scrape", "subtitles.block.honey_block.slide": "Sliding down a honey block", "subtitles.entity.bee.ambient": "Bee buzzes", "subtitles.entity.bee.death": "Bee dies", "subtitles.entity.bee.hurt": "Bee hurts", "subtitles.entity.bee.loop": "Bee buzzes", "subtitles.entity.bee.loop_aggressive": "Bee buzzes angrily", "subtitles.entity.bee.pollinate": "Bee buzzes happily", "subtitles.entity.bee.sting": "Bee stings", "subtitles.entity.iron_golem.damage": "Iron Golem breaks", "subtitles.entity.iron_golem.repair": "Iron Golem repaired", "subtitles.item.honey_bottle.drink": "Honey gulping", "advancements.adventure.honey_block_slide.title": "Sticky Situation", "advancements.adventure.honey_block_slide.description": "Jump into a Honey Block to break your fall", "advancements.husbandry.safely_harvest_honey.title": "Bee Our Guest", "advancements.husbandry.safely_harvest_honey.description": "Use a Campfire to collect Honey from a Beehive using a Bottle without aggravating the bees", "advancements.husbandry.silk_touch_nest.title": "Total Beelocation", "advancements.husbandry.silk_touch_nest.description": "Move a Bee Nest, with 3 bees inside, using Silk Touch", "argument.entity.options.predicate.description": "Custom predicate", "commands.schedule.cleared.success": "Removed %s schedules with id %s", "commands.schedule.cleared.failure": "No schedules with id %s", "commands.data.storage.modified": "Modified storage %s", "commands.data.storage.query": "Storage %s has the following contents: %s", "commands.data.storage.get": "%s in storage %s after scale factor of %s is %s", "commands.spectate.success.stopped": "No longer spectating an entity", "commands.spectate.success.started": "Now spectating %s", "commands.spectate.not_spectator": "%s is not in spectator mode", "commands.spectate.self": "Cannot spectate yourself", "predicate.unknown": "Unknown predicate: %s" }, "1.14": { "narrator.button.accessibility": "Accessibility", "narrator.button.language": "Language", "narrator.button.difficulty_lock": "Difficulty lock", "narrator.button.difficulty_lock.unlocked": "Unlocked", "narrator.button.difficulty_lock.locked": "Locked", "narrator.screen.title": "Title Screen", "narrator.controls.reset": "Reset %s button", "narrator.controls.bound": "%s is bound to %s", "narrator.controls.unbound": "%s is not bound", "narrator.select": "Selected: %s", "narrator.select.world": "Selected %s, last played: %s, %s, %s, version: %s", "narrator.loading": "Loading: %s", "narrator.loading.done": "Done", "narrator.joining": "Joining", "gui.recipebook.toggleRecipes.blastable": "Showing blastable", "gui.recipebook.toggleRecipes.smokable": "Showing smokable", "gui.narrate.button": "%s button", "gui.narrate.slider": "%s slider", "gui.narrate.editBox": "%s edit box: %s", "menu.sendFeedback": "Give Feedback", "menu.reportBugs": "Report Bugs", "menu.paused": "Game paused", "selectWorld.search": "search for worlds", "selectWorld.edit.backupFailed": "Backup failed", "selectWorld.backupEraseCache": "Erase cached data", "chat.editBox": "chat", "chat.type.team.text": "%s <%s> %s", "chat.type.team.sent": "-> %s <%s> %s", "chat.type.team.hover": "Message Team", "options.mouse_settings": "Mouse Settings...", "options.mouse_settings.title": "Mouse Settings", "options.accessibility.title": "Accessibility Settings...", "options.accessibility.text_background": "Text Background", "options.accessibility.text_background.chat": "Chat", "options.accessibility.text_background.everywhere": "Everywhere", "options.accessibility.text_background_opacity": "Text Background Opacity", "options.discrete_mouse_scroll": "Discrete Scrolling", "options.mouseWheelSensitivity": "Scroll Sensitivity", "options.rawMouseInput": "Raw input", "options.fullscreen.unavailable": "Setting unavailable", "title.oldgl.eol.line1": "Old graphics card detected; this WILL prevent you from", "title.oldgl.eol.line2": "playing future updates as OpenGL 2.0 will be required!", "title.oldgl.deprecation.line1": "Old graphics card detected; this may prevent you from", "title.oldgl.deprecation.line2": "playing in the future as OpenGL 3.2 will be required!", "merchant.current_level": "Trader's current level", "merchant.next_level": "Trader's next level", "merchant.level.1": "Novice", "merchant.level.2": "Apprentice", "merchant.level.3": "Journeyman", "merchant.level.4": "Expert", "merchant.level.5": "Master", "merchant.trades": "Trades", "block.minecraft.cornflower": "Cornflower", "block.minecraft.lily_of_the_valley": "Lily of the Valley", "block.minecraft.wither_rose": "Wither Rose", "block.minecraft.smooth_stone_slab": "Smooth Stone Slab", "block.minecraft.cut_sandstone_slab": "Cut Sandstone Slab", "block.minecraft.cut_red_sandstone_slab": "Cut Red Sandstone Slab", "block.minecraft.oak_sign": "Oak Sign", "block.minecraft.spruce_sign": "Spruce Sign", "block.minecraft.birch_sign": "Birch Sign", "block.minecraft.acacia_sign": "Acacia Sign", "block.minecraft.jungle_sign": "Jungle Sign", "block.minecraft.dark_oak_sign": "Dark Oak Sign", "block.minecraft.oak_wall_sign": "Oak Wall Sign", "block.minecraft.spruce_wall_sign": "Spruce Wall Sign", "block.minecraft.birch_wall_sign": "Birch Wall Sign", "block.minecraft.acacia_wall_sign": "Acacia Wall Sign", "block.minecraft.jungle_wall_sign": "Jungle Wall Sign", "block.minecraft.dark_oak_wall_sign": "Dark Oak Wall Sign", "block.minecraft.scaffolding": "Scaffolding", "block.minecraft.bed.obstructed": "This bed is obstructed", "block.minecraft.potted_cornflower": "Potted Cornflower", "block.minecraft.potted_lily_of_the_valley": "Potted Lily of the Valley", "block.minecraft.potted_wither_rose": "Potted Wither Rose", "block.minecraft.potted_bamboo": "Potted Bamboo", "block.minecraft.loom": "Loom", "block.minecraft.bamboo": "Bamboo", "block.minecraft.bamboo_sapling": "Bamboo Sapling", "block.minecraft.jigsaw": "Jigsaw Block", "block.minecraft.composter": "Composter", "block.minecraft.polished_granite_stairs": "Polished Granite Stairs", "block.minecraft.smooth_red_sandstone_stairs": "Smooth Red Sandstone Stairs", "block.minecraft.mossy_stone_brick_stairs": "Mossy Stone Brick Stairs", "block.minecraft.polished_diorite_stairs": "Polished Diorite Stairs", "block.minecraft.mossy_cobblestone_stairs": "Mossy Cobblestone Stairs", "block.minecraft.end_stone_brick_stairs": "End Stone Brick Stairs", "block.minecraft.stone_stairs": "Stone Stairs", "block.minecraft.smooth_sandstone_stairs": "Smooth Sandstone Stairs", "block.minecraft.smooth_quartz_stairs": "Smooth Quartz Stairs", "block.minecraft.granite_stairs": "Granite Stairs", "block.minecraft.andesite_stairs": "Andesite Stairs", "block.minecraft.red_nether_brick_stairs": "Red Nether Brick Stairs", "block.minecraft.polished_andesite_stairs": "Polished Andesite Stairs", "block.minecraft.diorite_stairs": "Diorite Stairs", "block.minecraft.polished_granite_slab": "Polished Granite Slab", "block.minecraft.smooth_red_sandstone_slab": "Smooth Red Sandstone Slab", "block.minecraft.mossy_stone_brick_slab": "Mossy Stone Brick Slab", "block.minecraft.polished_diorite_slab": "Polished Diorite Slab", "block.minecraft.mossy_cobblestone_slab": "Mossy Cobblestone Slab", "block.minecraft.end_stone_brick_slab": "End Stone Brick Slab", "block.minecraft.smooth_sandstone_slab": "Smooth Sandstone Slab", "block.minecraft.smooth_quartz_slab": "Smooth Quartz Slab", "block.minecraft.granite_slab": "Granite Slab", "block.minecraft.andesite_slab": "Andesite Slab", "block.minecraft.red_nether_brick_slab": "Red Nether Brick Slab", "block.minecraft.polished_andesite_slab": "Polished Andesite Slab", "block.minecraft.diorite_slab": "Diorite Slab", "block.minecraft.brick_wall": "Brick Wall", "block.minecraft.prismarine_wall": "Prismarine Wall", "block.minecraft.red_sandstone_wall": "Red Sandstone Wall", "block.minecraft.mossy_stone_brick_wall": "Mossy Stone Brick Wall", "block.minecraft.granite_wall": "Granite Wall", "block.minecraft.stone_brick_wall": "Stone Brick Wall", "block.minecraft.nether_brick_wall": "Nether Brick Wall", "block.minecraft.andesite_wall": "Andesite Wall", "block.minecraft.red_nether_brick_wall": "Red Nether Brick Wall", "block.minecraft.sandstone_wall": "Sandstone Wall", "block.minecraft.end_stone_brick_wall": "End Stone Brick Wall", "block.minecraft.diorite_wall": "Diorite Wall", "block.minecraft.barrel": "Barrel", "block.minecraft.smoker": "Smoker", "block.minecraft.blast_furnace": "Blast Furnace", "block.minecraft.cartography_table": "Cartography Table", "block.minecraft.fletching_table": "Fletching Table", "block.minecraft.smithing_table": "Smithing Table", "block.minecraft.grindstone": "Grindstone", "block.minecraft.lectern": "Lectern", "block.minecraft.stonecutter": "Stonecutter", "block.minecraft.bell": "Bell", "block.minecraft.ominous_banner": "Ominous Banner", "block.minecraft.lantern": "Lantern", "block.minecraft.sweet_berry_bush": "Sweet Berry Bush", "block.minecraft.campfire": "Campfire", "item.minecraft.red_dye": "Red Dye", "item.minecraft.green_dye": "Green Dye", "item.minecraft.yellow_dye": "Yellow Dye", "item.minecraft.blue_dye": "Blue Dye", "item.minecraft.black_dye": "Black Dye", "item.minecraft.brown_dye": "Brown Dye", "item.minecraft.white_dye": "White Dye", "item.minecraft.cat_spawn_egg": "Cat Spawn Egg", "item.minecraft.ravager_spawn_egg": "Ravager Spawn Egg", "item.minecraft.panda_spawn_egg": "Panda Spawn Egg", "item.minecraft.pillager_spawn_egg": "Pillager Spawn Egg", "item.minecraft.fox_spawn_egg": "Fox Spawn Egg", "item.minecraft.trader_llama_spawn_egg": "Trader Llama Spawn Egg", "item.minecraft.wandering_trader_spawn_egg": "Wandering Trader Spawn Egg", "item.minecraft.leather_horse_armor": "Leather Horse Armor", "item.minecraft.crossbow": "Crossbow", "item.minecraft.crossbow.projectile": "Projectile:", "item.minecraft.suspicious_stew": "Suspicious Stew", "item.minecraft.creeper_banner_pattern": "Banner Pattern", "item.minecraft.skull_banner_pattern": "Banner Pattern", "item.minecraft.flower_banner_pattern": "Banner Pattern", "item.minecraft.mojang_banner_pattern": "Banner Pattern", "item.minecraft.globe_banner_pattern": "Banner Pattern", "item.minecraft.creeper_banner_pattern.desc": "Creeper Charge", "item.minecraft.skull_banner_pattern.desc": "Skull Charge", "item.minecraft.flower_banner_pattern.desc": "Flower Charge", "item.minecraft.mojang_banner_pattern.desc": "Thing", "item.minecraft.globe_banner_pattern.desc": "Globe", "item.minecraft.sweet_berries": "Sweet Berries", "container.smoker": "Smoker", "container.lectern": "Lectern", "container.blast_furnace": "Blast Furnace", "container.barrel": "Barrel", "container.loom": "Loom", "container.grindstone_title": "Repair & Disenchant", "container.cartography_table": "Cartography Table", "container.stonecutter": "Stonecutter", "structure_block.position.x": "relative Position x", "structure_block.position.y": "relative position y", "structure_block.position.z": "relative position z", "structure_block.size.x": "structure size x", "structure_block.size.y": "structure size y", "structure_block.size.z": "structure size z", "structure_block.integrity.integrity": "Structure Integrity", "structure_block.integrity.seed": "Structure Seed", "jigsaw_block.target_pool": "Target pool:", "jigsaw_block.attachement_type": "Attachment type:", "jigsaw_block.final_state": "Turns into:", "filled_map.locked": "Locked", "entity.minecraft.fox": "Fox", "entity.minecraft.ravager": "Ravager", "entity.minecraft.panda": "Panda", "entity.minecraft.pillager": "Pillager", "entity.minecraft.trader_llama": "Trader Llama", "entity.minecraft.villager.mason": "Mason", "entity.minecraft.villager.none": "Villager", "entity.minecraft.villager.toolsmith": "Toolsmith", "entity.minecraft.villager.weaponsmith": "Weaponsmith", "entity.minecraft.wandering_trader": "Wandering Trader", "death.attack.sweetBerryBush": "%1$s was poked to death by a sweet berry bush", "death.attack.sweetBerryBush.player": "%1$s was poked to death by a sweet berry bush whilst trying to escape %2$s", "effect.minecraft.bad_omen": "Bad Omen", "effect.minecraft.hero_of_the_village": "Hero of the Village", "event.minecraft.raid": "Raid", "event.minecraft.raid.raiders_remaining": "Raiders remaining: %s", "event.minecraft.raid.victory": "Victory", "event.minecraft.raid.defeat": "Defeat", "enchantment.minecraft.multishot": "Multishot", "enchantment.minecraft.quick_charge": "Quick Charge", "enchantment.minecraft.piercing": "Piercing", "stat.minecraft.bell_ring": "Bells Rung", "stat.minecraft.interact_with_campfire": "Interactions with Campfire", "stat.minecraft.interact_with_cartography_table": "Interactions with Cartography Table", "stat.minecraft.interact_with_lectern": "Interactions with Lectern", "stat.minecraft.interact_with_loom": "Interactions with Loom", "stat.minecraft.interact_with_blast_furnace": "Interactions with Blast Furnace", "stat.minecraft.interact_with_smoker": "Interactions with Smoker", "stat.minecraft.interact_with_stonecutter": "Interactions with Stonecutter", "stat.minecraft.open_barrel": "Barrels Opened", "stat.minecraft.raid_trigger": "Raids Triggered", "stat.minecraft.raid_win": "Raids Won", "stat.minecraft.ring_bell": "Bells Rung", "block.minecraft.banner.globe.black": "Black Globe", "block.minecraft.banner.globe.red": "Red Globe", "block.minecraft.banner.globe.green": "Green Globe", "block.minecraft.banner.globe.brown": "Brown Globe", "block.minecraft.banner.globe.blue": "Blue Globe", "block.minecraft.banner.globe.purple": "Purple Globe", "block.minecraft.banner.globe.cyan": "Cyan Globe", "block.minecraft.banner.globe.light_gray": "Light Gray Globe", "block.minecraft.banner.globe.gray": "Gray Globe", "block.minecraft.banner.globe.pink": "Pink Globe", "block.minecraft.banner.globe.lime": "Lime Globe", "block.minecraft.banner.globe.yellow": "Yellow Globe", "block.minecraft.banner.globe.light_blue": "Light Blue Globe", "block.minecraft.banner.globe.magenta": "Magenta Globe", "block.minecraft.banner.globe.orange": "Orange Globe", "block.minecraft.banner.globe.white": "White Globe", "subtitles.block.barrel.close": "Barrel closes", "subtitles.block.barrel.open": "Barrel opens", "subtitles.block.bell.use": "Bell rings", "subtitles.block.bell.resonate": "Bell resonates", "subtitles.block.blastfurnace.fire_crackle": "Blast furnace crackles", "subtitles.block.campfire.crackle": "Campfire crackles", "subtitles.block.grindstone.use": "Grindstone used", "subtitles.block.smoker.smoke": "Smoker smokes", "subtitles.entity.parrot.imitate.guardian": "Parrot moans", "subtitles.entity.parrot.imitate.panda": "Parrot pants", "subtitles.entity.parrot.imitate.pillager": "Parrot murmurs", "subtitles.entity.parrot.imitate.ravager": "Parrot grunts", "subtitles.entity.evoker.celebrate": "Evoker cheers", "subtitles.entity.fox.aggro": "Fox angers", "subtitles.entity.fox.ambient": "Fox squeaks", "subtitles.entity.fox.bite": "Fox bites", "subtitles.entity.fox.death": "Fox dies", "subtitles.entity.fox.eat": "Fox eats", "subtitles.entity.fox.hurt": "Fox hurts", "subtitles.entity.fox.screech": "Fox screeches", "subtitles.entity.fox.sleep": "Fox snores", "subtitles.entity.fox.sniff": "Fox sniffs", "subtitles.entity.fox.spit": "Fox spits", "subtitles.entity.ravager.step": "Ravager steps", "subtitles.entity.ravager.stunned": "Ravager stunned", "subtitles.entity.ravager.roar": "Ravager roars", "subtitles.entity.ravager.attack": "Ravager bites", "subtitles.entity.ravager.death": "Ravager dies", "subtitles.entity.ravager.hurt": "Ravager hurts", "subtitles.entity.ravager.ambient": "Ravager grunts", "subtitles.entity.ravager.celebrate": "Ravager cheers", "subtitles.entity.mooshroom.convert": "Mooshroom transforms", "subtitles.entity.mooshroom.eat": "Mooshroom eats", "subtitles.entity.mooshroom.milk": "Mooshroom gets milked", "subtitles.entity.mooshroom.suspicious_milk": "Mooshroom gets milked suspiciously", "subtitles.entity.panda.ambient": "Panda pants", "subtitles.entity.panda.pre_sneeze": "Panda's nose tickles", "subtitles.entity.panda.sneeze": "Panda sneezes", "subtitles.entity.panda.death": "Panda dies", "subtitles.entity.panda.eat": "Panda eats", "subtitles.entity.panda.step": "Panda steps", "subtitles.entity.panda.cant_breed": "Panda bleats", "subtitles.entity.panda.aggressive_ambient": "Panda huffs", "subtitles.entity.panda.worried_ambient": "Panda whimpers", "subtitles.entity.panda.hurt": "Panda hurts", "subtitles.entity.panda.bite": "Panda bites", "subtitles.entity.pillager.ambient": "Pillager murmurs", "subtitles.entity.pillager.celebrate": "Pillager cheers", "subtitles.entity.pillager.death": "Pillager dies", "subtitles.entity.pillager.hurt": "Pillager hurts", "subtitles.entity.villager.celebrate": "Villager cheers", "subtitles.entity.villager.work_armorer": "Armorer works", "subtitles.entity.villager.work_butcher": "Butcher works", "subtitles.entity.villager.work_cartographer": "Cartographer works", "subtitles.entity.villager.work_cleric": "Cleric works", "subtitles.entity.villager.work_farmer": "Farmer works", "subtitles.entity.villager.work_fisherman": "Fisherman works", "subtitles.entity.villager.work_fletcher": "Fletcher works", "subtitles.entity.villager.work_leatherworker": "Leatherworker works", "subtitles.entity.villager.work_librarian": "Librarian works", "subtitles.entity.villager.work_mason": "Mason works", "subtitles.entity.villager.work_shepherd": "Shepherd works", "subtitles.entity.villager.work_toolsmith": "Toolsmith works", "subtitles.entity.villager.work_weaponsmith": "Weaponsmith works", "subtitles.entity.vindicator.celebrate": "Vindicator cheers", "subtitles.entity.wandering_trader.ambient": "Wandering Trader mumbles", "subtitles.entity.wandering_trader.death": "Wandering Trader dies", "subtitles.entity.wandering_trader.hurt": "Wandering Trader hurts", "subtitles.entity.wandering_trader.no": "Wandering Trader disagrees", "subtitles.entity.wandering_trader.trade": "Wandering Trader trades", "subtitles.entity.wandering_trader.yes": "Wandering Trader agrees", "subtitles.entity.witch.celebrate": "Witch cheers", "subtitles.event.raid.horn": "Ominous horn blares", "subtitles.item.crop.plant": "Crop planted", "subtitles.item.crossbow.charge": "Crossbow charges up", "subtitles.item.crossbow.hit": "Arrow hits", "subtitles.item.crossbow.load": "Crossbow loads", "subtitles.item.crossbow.shoot": "Crossbow fires", "subtitles.item.nether_wart.plant": "Crop planted", "subtitles.item.berries.pick": "Berries pop", "subtitles.item.book.page_turn": "Page rustles", "subtitles.item.book.put": "Book thumps", "debug.pause.help": "F3 + Esc = Pause without pause menu (if pausing is possible)", "advancements.adventure.arbalistic.title": "Arbalistic", "advancements.adventure.arbalistic.description": "Kill five unique mobs with one crossbow shot", "advancements.adventure.hero_of_the_village.title": "Hero of the Village", "advancements.adventure.hero_of_the_village.description": "Successfully defend a village from a raid", "advancements.adventure.ol_betsy.title": "Ol' Betsy", "advancements.adventure.ol_betsy.description": "Shoot a crossbow", "advancements.adventure.two_birds_one_arrow.title": "Two Birds, One Arrow", "advancements.adventure.two_birds_one_arrow.description": "Kill two Phantoms with a piercing arrow", "advancements.adventure.voluntary_exile.title": "Voluntary Exile", "advancements.adventure.voluntary_exile.description": "Kill a raid captain.\nMaybe consider staying away from villages for the time being...", "advancements.adventure.whos_the_pillager_now.title": "Who's the Pillager Now?", "advancements.adventure.whos_the_pillager_now.description": "Give a Pillager a taste of their own medicine", "advancements.husbandry.complete_catalogue.title": "A Complete Catalogue", "advancements.husbandry.complete_catalogue.description": "Tame all cat variants!", "commands.debug.reportSaved": "Created debug report in %s", "commands.debug.reportFailed": "Failed to create debug report", "commands.drop.no_held_items": "Entity can't hold any items", "commands.drop.no_loot_table": "Entity %s has no loot table", "commands.drop.success.single": "Dropped %s * %s", "commands.drop.success.single_with_table": "Dropped %s * %s from loot table %s", "commands.drop.success.multiple": "Dropped %s items", "commands.drop.success.multiple_with_table": "Dropped %s items from loot table %s", "commands.schedule.created.function": "Scheduled function '%s' in %s ticks at gametime %s", "commands.schedule.created.tag": "Scheduled tag '%s' in %s ticks at gametime %s", "commands.schedule.same_tick": "Can't schedule for current tick", "arguments.nbtpath.nothing_found": "Found no elements matching %s", "argument.time.invalid_unit": "Invalid unit", "argument.time.invalid_tick_count": "Tick count must be non-negative", "commands.data.modify.expected_list": "Expected list, got: %s", "commands.data.modify.expected_object": "Expected object, got: %s", "commands.data.modify.invalid_index": "Invalid list index: %s", "commands.data.get.multiple": "This argument accepts a single NBT value", "commands.teammsg.failed.noteam": "You must be on a team to message your team", "lectern.take_book": "Take Book", "argument.long.low": "Long must not be less than %s, found %s", "argument.long.big": "Long must not be more than %s, found %s", "parsing.long.invalid": "Invalid long '%s'", "parsing.long.expected": "Expected long", "biome.minecraft.bamboo_jungle": "Bamboo Jungle", "biome.minecraft.bamboo_jungle_hills": "Bamboo Jungle Hills" }, "1.13.1": { "menu.loadingForcedChunks": "Loading forced chunks for dimension %s", "optimizeWorld.stage.structures": "Upgrading structure data...", "resourcePack.broken_assets": "BROKEN ASSETS DETECTED", "book.invalid.tag": "* Invalid book tag *", "block.minecraft.dead_tube_coral": "Dead Tube Coral", "block.minecraft.dead_brain_coral": "Dead Brain Coral", "block.minecraft.dead_bubble_coral": "Dead Bubble Coral", "block.minecraft.dead_fire_coral": "Dead Fire Coral", "block.minecraft.dead_horn_coral": "Dead Horn Coral", "entity.minecraft.tropical_fish.predefined.0": "Anemone", "entity.minecraft.tropical_fish.predefined.1": "Black Tang", "entity.minecraft.tropical_fish.predefined.2": "Blue Tang", "entity.minecraft.tropical_fish.predefined.3": "Butterflyfish", "entity.minecraft.tropical_fish.predefined.4": "Cichlid", "entity.minecraft.tropical_fish.predefined.5": "Clownfish", "entity.minecraft.tropical_fish.predefined.6": "Cotton Candy Betta", "entity.minecraft.tropical_fish.predefined.7": "Dottyback", "entity.minecraft.tropical_fish.predefined.8": "Emperor Red Snapper", "entity.minecraft.tropical_fish.predefined.9": "Goatfish", "entity.minecraft.tropical_fish.predefined.10": "Moorish Idol", "entity.minecraft.tropical_fish.predefined.11": "Ornate Butterflyfish", "entity.minecraft.tropical_fish.predefined.12": "Parrotfish", "entity.minecraft.tropical_fish.predefined.13": "Queen Angelfish", "entity.minecraft.tropical_fish.predefined.14": "Red Cichlid", "entity.minecraft.tropical_fish.predefined.15": "Red Lipped Blenny", "entity.minecraft.tropical_fish.predefined.16": "Red Snapper", "entity.minecraft.tropical_fish.predefined.17": "Threadfin", "entity.minecraft.tropical_fish.predefined.18": "Tomato Clownfish", "entity.minecraft.tropical_fish.predefined.19": "Triggerfish", "entity.minecraft.tropical_fish.predefined.20": "Yellowtail Parrotfish", "entity.minecraft.tropical_fish.predefined.21": "Yellow Tang", "entity.minecraft.tropical_fish.type.flopper": "Flopper", "entity.minecraft.tropical_fish.type.stripey": "Stripey", "entity.minecraft.tropical_fish.type.glitter": "Glitter", "entity.minecraft.tropical_fish.type.blockfish": "Blockfish", "entity.minecraft.tropical_fish.type.betty": "Betty", "entity.minecraft.tropical_fish.type.clayfish": "Clayfish", "entity.minecraft.tropical_fish.type.kob": "Kob", "entity.minecraft.tropical_fish.type.sunstreak": "SunStreak", "entity.minecraft.tropical_fish.type.snooper": "Snooper", "entity.minecraft.tropical_fish.type.dasher": "Dasher", "entity.minecraft.tropical_fish.type.brinely": "Brinely", "entity.minecraft.tropical_fish.type.spotty": "Spotty", "death.attack.even_more_magic": "%1$s was killed by even more magic", "death.attack.message_too_long": "Actually, message was too long to deliver fully. Sorry! Here's stripped version: %s", "stat.minecraft.clean_shulker_box": "Shulker Box Cleaned", "stat.minecraft.damage_dealt_absorbed": "Damage Dealt (Absorbed)", "stat.minecraft.damage_dealt_resisted": "Damage Dealt (Resisted)", "stat.minecraft.damage_blocked_by_shield": "Damage Blocked By Shield", "stat.minecraft.damage_absorbed": "Damage Absorbed", "stat.minecraft.damage_resisted": "Damage Resisted", "commands.forceload.added.failure": "No chunks were marked for force loading", "commands.forceload.added.single": "Marked chunk %s in %s to be force loaded", "commands.forceload.added.multiple": "Marked %s chunks in %s from %s to %s to be force loaded", "commands.forceload.query.success": "Chunk at %s in %s is marked for force loading", "commands.forceload.query.failure": "Chunk at %s in %s is not marked for force loading", "commands.forceload.list.single": "A force loaded chunk was found in %s at: %s", "commands.forceload.list.multiple": "%s force loaded chunks were found in %s at: %s", "commands.forceload.added.none": "No force loaded chunks were found in %s", "commands.forceload.removed.all": "Unmarked all force loaded chunks in %s", "commands.forceload.removed.failure": "No chunks were removed from force loading", "commands.forceload.removed.single": "Unmarked chunk %s in %s for force loading", "commands.forceload.removed.multiple": "Unmarked %s chunks in %s from %s to %s for force loading", "commands.forceload.toobig": "Too many chunks in the specified area (maximum %s, specified %s)", "argument.pos2d.incomplete": "Incomplete (expected 2 coordinates)", "argument.pos3d.incomplete": "Incomplete (expected 3 coordinates)", "argument.dimension.invalid": "Unknown dimension '%s'", "color.minecraft.white": "White", "color.minecraft.orange": "Orange", "color.minecraft.magenta": "Magenta", "color.minecraft.light_blue": "Light Blue", "color.minecraft.yellow": "Yellow", "color.minecraft.lime": "Lime", "color.minecraft.pink": "Pink", "color.minecraft.gray": "Gray", "color.minecraft.light_gray": "Light Gray", "color.minecraft.cyan": "Cyan", "color.minecraft.purple": "Purple", "color.minecraft.blue": "Blue", "color.minecraft.brown": "Brown", "color.minecraft.green": "Green", "color.minecraft.red": "Red", "color.minecraft.black": "Black" }, "1.13": { "gui.ok": "Ok", "gui.proceed": "Proceed", "gui.recipebook.toggleRecipes.smeltable": "Showing smeltable", "menu.savingLevel": "Saving world", "menu.working": "Working...", "menu.savingChunks": "Saving chunks", "menu.preparingSpawn": "Preparing spawn area", "optimizeWorld.confirm.title": "Optimize world", "optimizeWorld.confirm.description": "This will attempt to optimize your world by making sure all data is stored in the most recent game format. This can take a very long time, depending on your world. Once done, your world may play faster but will no longer be compatible with older versions of the game. Are you sure you wish to proceed?", "optimizeWorld.title": "Optimizing World '%s'", "optimizeWorld.stage.counting": "Counting chunks...", "optimizeWorld.stage.upgrading": "Upgrading all chunks...", "optimizeWorld.stage.finished": "Finishing up...", "optimizeWorld.stage.failed": "Failed! :(", "optimizeWorld.info.converted": "Upgraded chunks: %s", "optimizeWorld.info.skipped": "Skipped chunks: %s", "optimizeWorld.info.total": "Total chunks: %s", "selectWorld.edit.backup": "Make Backup", "selectWorld.edit.backupFolder": "Open Backups Folder", "selectWorld.edit.backupCreated": "Backed up: %s", "selectWorld.edit.backupSize": "size: %s MB", "selectWorld.edit.optimize": "Optimize World", "selectWorld.backupQuestion": "Do you really want to load this world?", "selectWorld.backupWarning": "This world was last played in version %s; you are on snapshot %s. Please make a backup in case you experience world corruptions!", "selectWorld.backupQuestion.customized": "Customized worlds are no longer supported", "selectWorld.backupWarning.customized": "Unfortunately, we do not support customized worlds in this version of Minecraft. We can still load this world and keep everything the way it was, but any newly generated terrain will no longer be customized. We're sorry for the inconvenience!", "selectWorld.backupJoinConfirmButton": "Backup and load", "selectWorld.backupJoinSkipButton": "I know what I'm doing!", "selectWorld.tooltip.unsupported": "This world is no longer supported and can only be played in %s", "selectWorld.futureworld.error.title": "An error occured!", "selectWorld.futureworld.error.text": "Something went wrong while trying to load a world from a future version. This was a risky operation to begin with, sorry it didn't work.", "selectWorld.recreate.error.title": "An error occured!", "selectWorld.recreate.error.text": "Something went wrong while trying to recreate a world.", "selectWorld.recreate.customized.title": "Customized worlds are no longer supported", "selectWorld.recreate.customized.text": "Customized worlds are no longer supported in this version of Minecraft. We can try to recreate it with the same seed and properties, but any terrain customizations will be lost. We're sorry for the inconvenience!", "createWorld.customize.buffet.title": "Buffet world customization", "createWorld.customize.buffet.generatortype": "World generator:", "createWorld.customize.buffet.generator": "Please select a generator type", "createWorld.customize.buffet.biome": "Please select a biome", "createWorld.customize.custom.useOceanRuins": "Ocean Ruins", "generator.buffet": "Buffet", "multiplayer.message_not_delivered": "Can't deliver chat message, check server logs", "multiplayer.status.finished": "Finished", "multiplayer.status.request_handled": "Status requst has been handled", "multiplayer.disconnect.banned.reason": "You are banned from this server.\nReason: %s", "multiplayer.disconnect.banned.expiration": "\nYour ban will be removed on %s", "multiplayer.disconnect.banned_ip.reason": "Your IP address is banned from this server.\nReason: %s", "multiplayer.disconnect.banned_ip.expiration": "\nYour ban will be removed on %s", "multiplayer.disconnect.not_whitelisted": "You are not white-listed on this server!", "multiplayer.disconnect.server_full": "The server is full!", "multiplayer.disconnect.name_taken": "That name is already taken", "multiplayer.disconnect.unexpected_query_response": "Unexpected custom data from client", "chat.coordinates": "%s, %s, %s", "chat.coordinates.tooltip": "Click to teleport", "connect.aborted": "Aborted", "connect.negotiating": "Negotiating...", "connect.encrypting": "Encrypting...", "connect.joining": "Joining world...", "options.biomeBlendRadius": "Biome Blend", "options.autoSuggestCommands": "Command Suggestions", "options.fullscreen.resolution": "Fullscreen Resolution", "options.fullscreen.current": "Current", "key.keyboard.unknown": "Not bound", "key.keyboard.apostrophe": "'", "key.keyboard.backslash": "\\", "key.keyboard.backspace": "Backspace", "key.keyboard.comma": ",", "key.keyboard.delete": "Delete", "key.keyboard.end": "End", "key.keyboard.enter": "Enter", "key.keyboard.equal": "=", "key.keyboard.escape": "Escape", "key.keyboard.f1": "F1", "key.keyboard.f2": "F2", "key.keyboard.f3": "F3", "key.keyboard.f4": "F4", "key.keyboard.f5": "F5", "key.keyboard.f6": "F6", "key.keyboard.f7": "F7", "key.keyboard.f8": "F8", "key.keyboard.f9": "F9", "key.keyboard.f10": "F10", "key.keyboard.f11": "F11", "key.keyboard.f12": "F12", "key.keyboard.f13": "F13", "key.keyboard.f14": "F14", "key.keyboard.f15": "F15", "key.keyboard.f16": "F16", "key.keyboard.f17": "F17", "key.keyboard.f18": "F18", "key.keyboard.f19": "F19", "key.keyboard.f20": "F20", "key.keyboard.f21": "F21", "key.keyboard.f22": "F22", "key.keyboard.f23": "F23", "key.keyboard.f24": "F24", "key.keyboard.f25": "F25", "key.keyboard.grave.accent": "`", "key.keyboard.home": "Home", "key.keyboard.insert": "Insert", "key.keyboard.keypad.0": "Keypad 0", "key.keyboard.keypad.1": "Keypad 1", "key.keyboard.keypad.2": "Keypad 2", "key.keyboard.keypad.3": "Keypad 3", "key.keyboard.keypad.4": "Keypad 4", "key.keyboard.keypad.5": "Keypad 5", "key.keyboard.keypad.6": "Keypad 6", "key.keyboard.keypad.7": "Keypad 7", "key.keyboard.keypad.8": "Keypad 8", "key.keyboard.keypad.9": "Keypad 9", "key.keyboard.keypad.add": "Keypad +", "key.keyboard.keypad.decimal": "Keypad Decimal", "key.keyboard.keypad.enter": "Keypad Enter", "key.keyboard.keypad.equal": "Keypad =", "key.keyboard.keypad.multiply": "Keypad *", "key.keyboard.keypad.divide": "Keypad /", "key.keyboard.keypad.subtract": "Keypad -", "key.keyboard.left.bracket": "[", "key.keyboard.right.bracket": "]", "key.keyboard.minus": "-", "key.keyboard.num.lock": "Num Lock", "key.keyboard.caps.lock": "Caps Lock", "key.keyboard.scroll.lock": "Scroll Lock", "key.keyboard.page.down": "Page Down", "key.keyboard.page.up": "Page Up", "key.keyboard.pause": "Pause", "key.keyboard.period": ".", "key.keyboard.left.control": "Left Control", "key.keyboard.right.control": "Right Control", "key.keyboard.left.alt": "Left Alt", "key.keyboard.right.alt": "Right Alt", "key.keyboard.left.shift": "Left Shift", "key.keyboard.right.shift": "Right Shift", "key.keyboard.left.win": "Left Win", "key.keyboard.right.win": "Right Win", "key.keyboard.semicolon": ";", "key.keyboard.slash": "/", "key.keyboard.space": "Space", "key.keyboard.tab": "Tab", "key.keyboard.up": "Up Arrow", "key.keyboard.down": "Down Arrow", "key.keyboard.left": "Left Arrow", "key.keyboard.right": "Right Arrow", "key.keyboard.menu": "Menu", "key.keyboard.print.screen": "Print Screen", "key.keyboard.world.1": "World 1", "key.keyboard.world.2": "World 2", "resourcePack.server.name": "World Specific Resources", "block.minecraft.oak_planks": "Oak Planks", "block.minecraft.spruce_planks": "Spruce Planks", "block.minecraft.birch_planks": "Birch Planks", "block.minecraft.jungle_planks": "Jungle Planks", "block.minecraft.acacia_planks": "Acacia Planks", "block.minecraft.dark_oak_planks": "Dark Oak Planks", "block.minecraft.flowing_water": "Flowing Water", "block.minecraft.flowing_lava": "Flowing Lava", "block.minecraft.cut_sandstone": "Cut Sandstone", "block.minecraft.cut_red_sandstone": "Cut Red Sandstone", "block.minecraft.oak_log": "Oak Log", "block.minecraft.spruce_log": "Spruce Log", "block.minecraft.birch_log": "Birch Log", "block.minecraft.jungle_log": "Jungle Log", "block.minecraft.acacia_log": "Acacia Log", "block.minecraft.dark_oak_log": "Dark Oak Log", "block.minecraft.stripped_oak_log": "Stripped Oak Log", "block.minecraft.stripped_spruce_log": "Stripped Spruce Log", "block.minecraft.stripped_birch_log": "Stripped Birch Log", "block.minecraft.stripped_jungle_log": "Stripped Jungle Log", "block.minecraft.stripped_acacia_log": "Stripped Acacia Log", "block.minecraft.stripped_dark_oak_log": "Stripped Dark Oak Log", "block.minecraft.stripped_oak_wood": "Stripped Oak Wood", "block.minecraft.stripped_spruce_wood": "Stripped Spruce Wood", "block.minecraft.stripped_birch_wood": "Stripped Birch Wood", "block.minecraft.stripped_jungle_wood": "Stripped Jungle Wood", "block.minecraft.stripped_acacia_wood": "Stripped Acacia Wood", "block.minecraft.stripped_dark_oak_wood": "Stripped Dark Oak Wood", "block.minecraft.kelp_plant": "Kelp Plant", "block.minecraft.kelp": "Kelp", "block.minecraft.dried_kelp_block": "Dried Kelp Block", "block.minecraft.tall_grass": "Tall Grass", "block.minecraft.tall_seagrass": "Tall Seagrass", "block.minecraft.seagrass": "Seagrass", "block.minecraft.sea_pickle": "Sea Pickle", "block.minecraft.brown_mushroom": "Brown Mushroom", "block.minecraft.red_mushroom_block": "Red Mushroom Block", "block.minecraft.brown_mushroom_block": "Brown Mushroom Block", "block.minecraft.mushroom_stem": "Mushroom Stem", "block.minecraft.smooth_stone": "Smooth Stone", "block.minecraft.smooth_quartz": "Smooth Quartz", "block.minecraft.petrified_oak_slab": "Petrified Oak Slab", "block.minecraft.brick_slab": "Brick Slab", "block.minecraft.stone_brick_slab": "Stone Brick Slab", "block.minecraft.oak_slab": "Oak Slab", "block.minecraft.spruce_slab": "Spruce Slab", "block.minecraft.birch_slab": "Birch Slab", "block.minecraft.jungle_slab": "Jungle Slab", "block.minecraft.acacia_slab": "Acacia Slab", "block.minecraft.dark_oak_slab": "Dark Oak Slab", "block.minecraft.dark_prismarine_slab": "Dark Prismarine Slab", "block.minecraft.prismarine_slab": "Prismarine Slab", "block.minecraft.prismarine_brick_slab": "Prismarine Brick Slab", "block.minecraft.mossy_cobblestone": "Mossy Cobblestone", "block.minecraft.wall_torch": "Wall Torch", "block.minecraft.spawner": "Spawner", "block.minecraft.oak_stairs": "Oak Stairs", "block.minecraft.spruce_stairs": "Spruce Stairs", "block.minecraft.birch_stairs": "Birch Stairs", "block.minecraft.jungle_stairs": "Jungle Stairs", "block.minecraft.acacia_stairs": "Acacia Stairs", "block.minecraft.dark_oak_stairs": "Dark Oak Stairs", "block.minecraft.dark_prismarine_stairs": "Dark Prismarine Stairs", "block.minecraft.prismarine_stairs": "Prismarine Stairs", "block.minecraft.prismarine_brick_stairs": "Prismarine Brick Stairs", "block.minecraft.wheat": "Wheat Crops", "block.minecraft.sign": "Sign", "block.minecraft.wall_sign": "Wall Sign", "block.minecraft.oak_pressure_plate": "Oak Pressure Plate", "block.minecraft.spruce_pressure_plate": "Spruce Pressure Plate", "block.minecraft.birch_pressure_plate": "Birch Pressure Plate", "block.minecraft.jungle_pressure_plate": "Jungle Pressure Plate", "block.minecraft.acacia_pressure_plate": "Acacia Pressure Plate", "block.minecraft.dark_oak_pressure_plate": "Dark Oak Pressure Plate", "block.minecraft.light_weighted_pressure_plate": "Light Weighted Pressure Plate", "block.minecraft.heavy_weighted_pressure_plate": "Heavy Weighted Pressure Plate", "block.minecraft.redstone_wall_torch": "Redstone Wall Torch", "block.minecraft.stone_button": "Stone Button", "block.minecraft.oak_button": "Oak Button", "block.minecraft.spruce_button": "Spruce Button", "block.minecraft.birch_button": "Birch Button", "block.minecraft.jungle_button": "Jungle Button", "block.minecraft.acacia_button": "Acacia Button", "block.minecraft.dark_oak_button": "Dark Oak Button", "block.minecraft.blue_ice": "Blue Ice", "block.minecraft.sugar_cane": "Sugar Cane", "block.minecraft.attached_pumpkin_stem": "Attached Pumpkin Stem", "block.minecraft.carved_pumpkin": "Carved Pumpkin", "block.minecraft.nether_portal": "Nether Portal", "block.minecraft.bed": "Bed", "block.minecraft.bed.no_sleep": "You can sleep only at night and during thunderstorms", "block.minecraft.bed.too_far_away": "You may not rest now; the bed is too far away", "block.minecraft.bed.not_safe": "You may not rest now; there are monsters nearby", "block.minecraft.oak_trapdoor": "Oak Trapdoor", "block.minecraft.spruce_trapdoor": "Spruce Trapdoor", "block.minecraft.birch_trapdoor": "Birch Trapdoor", "block.minecraft.jungle_trapdoor": "Jungle Trapdoor", "block.minecraft.acacia_trapdoor": "Acacia Trapdoor", "block.minecraft.dark_oak_trapdoor": "Dark Oak Trapdoor", "block.minecraft.infested_stone": "Infested Stone", "block.minecraft.infested_cobblestone": "Infested Cobblestone", "block.minecraft.infested_stone_bricks": "Infested Stone Bricks", "block.minecraft.infested_mossy_stone_bricks": "Infested Mossy Stone Bricks", "block.minecraft.infested_cracked_stone_bricks": "Infested Cracked Stone Bricks", "block.minecraft.infested_chiseled_stone_bricks": "Infested Chiseled Stone Bricks", "block.minecraft.nether_bricks": "Nether Bricks", "block.minecraft.enchanting_table": "Enchanting Table", "block.minecraft.chipped_anvil": "Chipped Anvil", "block.minecraft.damaged_anvil": "Damaged Anvil", "block.minecraft.end_portal_frame": "End Portal Frame", "block.minecraft.daylight_detector": "Daylight Detector", "block.minecraft.quartz_pillar": "Quartz Pillar", "block.minecraft.red_nether_bricks": "Red Nether Bricks", "block.minecraft.turtle_egg": "Turtle Egg", "block.minecraft.two_turtle_eggs": "Two Turtle Eggs", "block.minecraft.three_turtle_eggs": "Three Turtle Eggs", "block.minecraft.four_turtle_eggs": "Four Turtle Eggs", "block.minecraft.banner": "Banner", "block.minecraft.wall_banner": "Wall Banner", "block.minecraft.piston_head": "Piston Head", "block.minecraft.moving_piston": "Moving Piston", "block.minecraft.red_mushroom": "Red Mushroom", "block.minecraft.snow_block": "Snow Block", "block.minecraft.attached_melon_stem": "Attached Melon Stem", "block.minecraft.melon_stem": "Melon Stem", "block.minecraft.potted_oak_sapling": "Potted Oak Sapling", "block.minecraft.potted_spruce_sapling": "Potted Spruce Sapling", "block.minecraft.potted_birch_sapling": "Potted Birch Sapling", "block.minecraft.potted_jungle_sapling": "Potted Jungle Sapling", "block.minecraft.potted_acacia_sapling": "Potted Acacia Sapling", "block.minecraft.potted_dark_oak_sapling": "Potted Dark Oak Sapling", "block.minecraft.potted_fern": "Potted Fern", "block.minecraft.potted_dandelion": "Potted Dandelion", "block.minecraft.potted_poppy": "Potted Poppy", "block.minecraft.potted_blue_orchid": "Potted Blue Orchid", "block.minecraft.potted_allium": "Potted Allium", "block.minecraft.potted_azure_bluet": "Potted Azure Bluet", "block.minecraft.potted_red_tulip": "Potted Red Tulip", "block.minecraft.potted_orange_tulip": "Potted Orange Tulip", "block.minecraft.potted_white_tulip": "Potted White Tulip", "block.minecraft.potted_pink_tulip": "Potted Pink Tulip", "block.minecraft.potted_oxeye_daisy": "Potted Oxeye Daisy", "block.minecraft.potted_red_mushroom": "Potted Red Mushroom", "block.minecraft.potted_brown_mushroom": "Potted Brown Mushroom", "block.minecraft.potted_dead_bush": "Potted Dead Bush", "block.minecraft.potted_cactus": "Potted Cactus", "block.minecraft.skeleton_wall_skull": "Skeleton Wall Skull", "block.minecraft.wither_skeleton_wall_skull": "Wither Skeleton Wall Skull", "block.minecraft.zombie_wall_head": "Zombie Wall Head", "block.minecraft.player_wall_head": "Player Wall Head", "block.minecraft.player_head": "Player Head", "block.minecraft.creeper_wall_head": "Creeper Wall Head", "block.minecraft.dragon_wall_head": "Dragon Wall Head", "block.minecraft.end_gateway": "End Gateway", "block.minecraft.void_air": "Void Air", "block.minecraft.cave_air": "Cave Air", "block.minecraft.bubble_column": "Bubble Column", "block.minecraft.dead_tube_coral_block": "Dead Tube Coral Block", "block.minecraft.dead_brain_coral_block": "Dead Brain Coral Block", "block.minecraft.dead_bubble_coral_block": "Dead Bubble Coral Block", "block.minecraft.dead_fire_coral_block": "Dead Fire Coral Block", "block.minecraft.dead_horn_coral_block": "Dead Horn Coral Block", "block.minecraft.tube_coral_block": "Tube Coral Block", "block.minecraft.brain_coral_block": "Brain Coral Block", "block.minecraft.bubble_coral_block": "Bubble Coral Block", "block.minecraft.fire_coral_block": "Fire Coral Block", "block.minecraft.horn_coral_block": "Horn Coral Block", "block.minecraft.tube_coral": "Tube Coral", "block.minecraft.brain_coral": "Brain Coral", "block.minecraft.bubble_coral": "Bubble Coral", "block.minecraft.fire_coral": "Fire Coral", "block.minecraft.horn_coral": "Horn Coral", "block.minecraft.tube_coral_fan": "Tube Coral Fan", "block.minecraft.brain_coral_fan": "Brain Coral Fan", "block.minecraft.bubble_coral_fan": "Bubble Coral Fan", "block.minecraft.fire_coral_fan": "Fire Coral Fan", "block.minecraft.horn_coral_fan": "Horn Coral Fan", "block.minecraft.dead_tube_coral_fan": "Dead Tube Coral Fan", "block.minecraft.dead_brain_coral_fan": "Dead Brain Coral Fan", "block.minecraft.dead_bubble_coral_fan": "Dead Bubble Coral Fan", "block.minecraft.dead_fire_coral_fan": "Dead Fire Coral Fan", "block.minecraft.dead_horn_coral_fan": "Dead Horn Coral Fan", "block.minecraft.tube_coral_wall_fan": "Tube Coral Wall Fan", "block.minecraft.brain_coral_wall_fan": "Brain Coral Wall Fan", "block.minecraft.bubble_coral_wall_fan": "Bubble Coral Wall Fan", "block.minecraft.fire_coral_wall_fan": "Fire Coral Wall Fan", "block.minecraft.horn_coral_wall_fan": "Horn Coral Wall Fan", "block.minecraft.dead_tube_coral_wall_fan": "Dead Tube Coral Wall Fan", "block.minecraft.dead_brain_coral_wall_fan": "Dead Brain Coral Wall Fan", "block.minecraft.dead_bubble_coral_wall_fan": "Dead Bubble Coral Wall Fan", "block.minecraft.dead_fire_coral_wall_fan": "Dead Fire Coral Wall Fan", "block.minecraft.dead_horn_coral_wall_fan": "Dead Horn Coral Wall Fan", "block.minecraft.conduit": "Conduit", "item.minecraft.dried_kelp": "Dried Kelp", "item.minecraft.wheat_seeds": "Wheat Seeds", "item.minecraft.melon_slice": "Melon Slice", "item.minecraft.chainmail_helmet": "Chainmail Helmet", "item.minecraft.chainmail_chestplate": "Chainmail Chestplate", "item.minecraft.chainmail_leggings": "Chainmail Leggings", "item.minecraft.chainmail_boots": "Chainmail Boots", "item.minecraft.enchanted_golden_apple": "Enchanted Golden Apple", "item.minecraft.sign": "Sign", "item.minecraft.pufferfish_bucket": "Bucket of Pufferfish", "item.minecraft.salmon_bucket": "Bucket of Salmon", "item.minecraft.cod_bucket": "Bucket of Cod", "item.minecraft.tropical_fish_bucket": "Bucket of Tropical Fish", "item.minecraft.milk_bucket": "Milk Bucket", "item.minecraft.clay_ball": "Clay", "item.minecraft.cod": "Raw Cod", "item.minecraft.tropical_fish": "Tropical Fish", "item.minecraft.cooked_cod": "Cooked Cod", "item.minecraft.music_disc_cat": "Music Disc", "item.minecraft.music_disc_blocks": "Music Disc", "item.minecraft.music_disc_chirp": "Music Disc", "item.minecraft.music_disc_far": "Music Disc", "item.minecraft.music_disc_mall": "Music Disc", "item.minecraft.music_disc_mellohi": "Music Disc", "item.minecraft.music_disc_stal": "Music Disc", "item.minecraft.music_disc_strad": "Music Disc", "item.minecraft.music_disc_ward": "Music Disc", "item.minecraft.music_disc_11": "Music Disc", "item.minecraft.music_disc_wait": "Music Disc", "item.minecraft.nether_wart": "Nether Wart", "item.minecraft.cauldron": "Cauldron", "item.minecraft.brewing_stand": "Brewing Stand", "item.minecraft.glistering_melon_slice": "Glistering Melon Slice", "item.minecraft.bat_spawn_egg": "Bat Spawn Egg", "item.minecraft.blaze_spawn_egg": "Blaze Spawn Egg", "item.minecraft.cave_spider_spawn_egg": "Cave Spider Spawn Egg", "item.minecraft.chicken_spawn_egg": "Chicken Spawn Egg", "item.minecraft.cod_spawn_egg": "Cod Spawn Egg", "item.minecraft.cow_spawn_egg": "Cow Spawn Egg", "item.minecraft.creeper_spawn_egg": "Creeper Spawn Egg", "item.minecraft.dolphin_spawn_egg": "Dolphin Spawn Egg", "item.minecraft.donkey_spawn_egg": "Donkey Spawn Egg", "item.minecraft.drowned_spawn_egg": "Drowned Spawn Egg", "item.minecraft.elder_guardian_spawn_egg": "Elder Guardian Spawn Egg", "item.minecraft.enderman_spawn_egg": "Enderman Spawn Egg", "item.minecraft.endermite_spawn_egg": "Endermite Spawn Egg", "item.minecraft.evoker_spawn_egg": "Evoker Spawn Egg", "item.minecraft.ghast_spawn_egg": "Ghast Spawn Egg", "item.minecraft.guardian_spawn_egg": "Guardian Spawn Egg", "item.minecraft.horse_spawn_egg": "Horse Spawn Egg", "item.minecraft.husk_spawn_egg": "Husk Spawn Egg", "item.minecraft.llama_spawn_egg": "Llama Spawn Egg", "item.minecraft.magma_cube_spawn_egg": "Magma Cube Spawn Egg", "item.minecraft.mooshroom_spawn_egg": "Mooshroom Spawn Egg", "item.minecraft.mule_spawn_egg": "Mule Spawn Egg", "item.minecraft.ocelot_spawn_egg": "Ocelot Spawn Egg", "item.minecraft.parrot_spawn_egg": "Parrot Spawn Egg", "item.minecraft.pig_spawn_egg": "Pig Spawn Egg", "item.minecraft.phantom_spawn_egg": "Phantom Spawn Egg", "item.minecraft.polar_bear_spawn_egg": "Polar Bear Spawn Egg", "item.minecraft.pufferfish_spawn_egg": "Pufferfish Spawn Egg", "item.minecraft.rabbit_spawn_egg": "Rabbit Spawn Egg", "item.minecraft.salmon_spawn_egg": "Salmon Spawn Egg", "item.minecraft.sheep_spawn_egg": "Sheep Spawn Egg", "item.minecraft.shulker_spawn_egg": "Shulker Spawn Egg", "item.minecraft.silverfish_spawn_egg": "Silverfish Spawn Egg", "item.minecraft.skeleton_spawn_egg": "Skeleton Spawn Egg", "item.minecraft.skeleton_horse_spawn_egg": "Skeleton Horse Spawn Egg", "item.minecraft.slime_spawn_egg": "Slime Spawn Egg", "item.minecraft.spider_spawn_egg": "Spider Spawn Egg", "item.minecraft.squid_spawn_egg": "Squid Spawn Egg", "item.minecraft.stray_spawn_egg": "Stray Spawn Egg", "item.minecraft.tropical_fish_spawn_egg": "Tropical Fish Spawn Egg", "item.minecraft.turtle_spawn_egg": "Turtle Spawn Egg", "item.minecraft.vex_spawn_egg": "Vex Spawn Egg", "item.minecraft.villager_spawn_egg": "Villager Spawn Egg", "item.minecraft.vindicator_spawn_egg": "Vindicator Spawn Egg", "item.minecraft.witch_spawn_egg": "Witch Spawn Egg", "item.minecraft.wither_skeleton_spawn_egg": "Wither Skeleton Spawn Egg", "item.minecraft.wolf_spawn_egg": "Wolf Spawn Egg", "item.minecraft.zombie_spawn_egg": "Zombie Spawn Egg", "item.minecraft.zombie_horse_spawn_egg": "Zombie Horse Spawn Egg", "item.minecraft.zombie_pigman_spawn_egg": "Zombie Pigman Spawn Egg", "item.minecraft.zombie_villager_spawn_egg": "Zombie Villager Spawn Egg", "item.minecraft.flower_pot": "Flower Pot", "item.minecraft.skeleton_skull": "Skeleton Skull", "item.minecraft.wither_skeleton_skull": "Wither Skeleton Skull", "item.minecraft.zombie_head": "Zombie Head", "item.minecraft.creeper_head": "Creeper Head", "item.minecraft.dragon_head": "Dragon Head", "item.minecraft.golden_horse_armor": "Golden Horse Armor", "item.minecraft.debug_stick": "Debug Stick", "item.minecraft.debug_stick.empty": "%s has no properties", "item.minecraft.debug_stick.update": "\"%s\" to %s", "item.minecraft.debug_stick.select": "selected \"%s\" (%s)", "item.minecraft.trident": "Trident", "item.minecraft.scute": "Scute", "item.minecraft.turtle_helmet": "Turtle Shell", "item.minecraft.phantom_membrane": "Phantom Membrane", "item.minecraft.nautilus_shell": "Nautilus Shell", "item.minecraft.heart_of_the_sea": "Heart of the Sea", "structure_block.invalid_structure_name": "Invalid structure name '%s'", "filled_map.buried_treasure": "Buried Treasure Map", "filled_map.id": "Id #%s", "entity.minecraft.area_effect_cloud": "Area Effect Cloud", "entity.minecraft.armor_stand": "Armor Stand", "entity.minecraft.arrow": "Arrow", "entity.minecraft.chest_minecart": "Minecart with Chest", "entity.minecraft.command_block_minecart": "Minecart with Command Block", "entity.minecraft.cod": "Cod", "entity.minecraft.dolphin": "Dolphin", "entity.minecraft.drowned": "Drowned", "entity.minecraft.egg": "Thrown Egg", "entity.minecraft.end_crystal": "End Crystal", "entity.minecraft.ender_pearl": "Thrown Ender Pearl", "entity.minecraft.evoker_fangs": "Evoker Fangs", "entity.minecraft.eye_of_ender": "Eye of Ender", "entity.minecraft.firework_rocket": "Firework Rocket", "entity.minecraft.fishing_bobber": "Fishing Bobber", "entity.minecraft.furnace_minecart": "Minecart with Furnace", "entity.minecraft.hopper_minecart": "Minecart with Hopper", "entity.minecraft.item_frame": "Item Frame", "entity.minecraft.leash_knot": "Leash Knot", "entity.minecraft.lightning_bolt": "Lightning Bolt", "entity.minecraft.llama_spit": "Llama Spit", "entity.minecraft.minecart": "Minecart", "entity.minecraft.painting": "Painting", "entity.minecraft.phantom": "Phantom", "entity.minecraft.player": "Player", "entity.minecraft.potion": "Potion", "entity.minecraft.pufferfish": "Pufferfish", "entity.minecraft.salmon": "Salmon", "entity.minecraft.shulker_bullet": "Shulker Bullet", "entity.minecraft.snowball": "Snowball", "entity.minecraft.spawner_minecart": "Minecart with Spawner", "entity.minecraft.spectral_arrow": "Spectral Arrow", "entity.minecraft.tnt": "Primed TNT", "entity.minecraft.tnt_minecart": "Minecart with TNT", "entity.minecraft.trident": "Trident", "entity.minecraft.tropical_fish": "Tropical Fish", "entity.minecraft.turtle": "Turtle", "entity.minecraft.villager.tool_smith": "Tool Smith", "entity.minecraft.villager.weapon_smith": "Weapon Smith", "entity.minecraft.wither_skull": "Wither Skull", "entity.minecraft.experience_bottle": "Thrown Bottle o' Enchanting", "death.attack.lightningBolt.player": "%1$s was struck by lightning whilst fighting %2$s", "death.attack.inWall.player": "%1$s suffocated in a wall whilst fighting %2$s", "death.attack.cramming.player": "%1$s was squashed by %2$s", "death.attack.starve.player": "%1$s starved to death whilst fighting %2$s", "death.attack.generic.player": "%1$s died because of %2$s", "death.attack.explosion.player.item": "%1$s was blown up by %2$s using %3$s", "death.attack.wither.player": "%1$s withered away whilst fighting %2$s", "death.attack.anvil.player": "%1$s was squashed by a falling anvil whilst fighting %2$s", "death.attack.fallingBlock.player": "%1$s was squashed by a falling block whilst fighting %2$s", "death.attack.thorns.item": "%1$s was killed by %3$s trying to hurt %2$s", "death.attack.trident": "%1$s was impaled by %2$s", "death.attack.trident.item": "%1$s was impaled by %2$s with %3$s", "death.attack.fall.player": "%1$s hit the ground too hard whilst trying to escape %2$s", "death.attack.outOfWorld.player": "%1$s didn't want to live in the same world as %2$s", "death.attack.dragonBreath.player": "%1$s was roasted in dragon breath by %2$s", "death.attack.flyIntoWall.player": "%1$s experienced kinetic energy whilst trying to escape %2$s", "death.attack.fireworks.player": "%1$s went off with a bang whilst fighting %2$s", "death.attack.netherBed.message": "%1$s was killed by %2$s", "death.attack.netherBed.link": "Intentional Game Design", "effect.minecraft.wither": "Wither", "effect.minecraft.saturation": "Saturation", "effect.minecraft.slow_falling": "Slow Falling", "effect.minecraft.conduit_power": "Conduit Power", "effect.minecraft.dolphins_grace": "Dolphin's Grace", "item.minecraft.tipped_arrow.effect.mundane": "Tipped Arrow", "item.minecraft.tipped_arrow.effect.thick": "Tipped Arrow", "item.minecraft.tipped_arrow.effect.awkward": "Tipped Arrow", "item.minecraft.tipped_arrow.effect.turtle_master": "Arrow of the Turtle Master", "item.minecraft.tipped_arrow.effect.slow_falling": "Arrow of Slow Falling", "item.minecraft.potion.effect.turtle_master": "Potion of the Turtle Master", "item.minecraft.potion.effect.slow_falling": "Potion of Slow Falling", "item.minecraft.splash_potion.effect.turtle_master": "Splash Potion of the Turtle Master", "item.minecraft.splash_potion.effect.slow_falling": "Splash Potion of Slow Falling", "item.minecraft.lingering_potion.effect.turtle_master": "Lingering Potion of the Turtle Master", "item.minecraft.lingering_potion.effect.slow_falling": "Lingering Potion of Slow Falling", "enchantment.minecraft.loyalty": "Loyalty", "enchantment.minecraft.impaling": "Impaling", "enchantment.minecraft.riptide": "Riptide", "enchantment.minecraft.channeling": "Channeling", "stat_type.minecraft.broken": "Times Broken", "stat.minecraft.walk_under_water_one_cm": "Distance Walked under Water", "stat.minecraft.play_record": "Music Discs Played", "stat.minecraft.walk_on_water_one_cm": "Distance Walked on Water", "stat.minecraft.time_since_rest": "Since Last Rest", "subtitles.block.bubble_column.bubble_pop": "Bubbles pop", "subtitles.block.bubble_column.upwards_ambient": "Bubbles flow", "subtitles.block.bubble_column.upwards_inside": "Bubbles woosh", "subtitles.block.bubble_column.whirlpool_ambient": "Bubbles whirl", "subtitles.block.bubble_column.whirlpool_inside": "Bubbles zoom", "subtitles.entity.fishing_bobber.splash": "Fishing Bobber splashes", "subtitles.entity.parrot.imitate.drowned": "Parrot gurgles", "subtitles.entity.parrot.imitate.illusioner": "Parrot murmurs", "subtitles.entity.parrot.imitate.phantom": "Parrot screeches", "subtitles.entity.cod.death": "Cod dies", "subtitles.entity.cod.flop": "Cod flops", "subtitles.entity.cod.hurt": "Cod hurts", "subtitles.entity.dolphin.ambient": "Dolphin chirps", "subtitles.entity.dolphin.ambient_water": "Dolphin whistles", "subtitles.entity.dolphin.attack": "Dolphin attacks", "subtitles.entity.dolphin.death": "Dolphin dies", "subtitles.entity.dolphin.eat": "Dolphin eats", "subtitles.entity.dolphin.hurt": "Dolphin hurts", "subtitles.entity.dolphin.jump": "Dolphin jumps", "subtitles.entity.dolphin.play": "Dolphin plays", "subtitles.entity.dolphin.splash": "Dolphin splashes", "subtitles.entity.dolphin.swim": "Dolphin swims", "subtitles.entity.drowned.ambient": "Drowned gurgles", "subtitles.entity.drowned.death": "Drowned dies", "subtitles.entity.drowned.hurt": "Drowned hurts", "subtitles.entity.drowned.shoot": "Drowned throws Trident", "subtitles.entity.drowned.step": "Drowned steps", "subtitles.entity.drowned.swim": "Drowned swims", "subtitles.entity.husk.converted_to_zombie": "Husk converted to Zombie", "subtitles.entity.phantom.ambient": "Phantom screeches", "subtitles.entity.phantom.bite": "Phantom bites", "subtitles.entity.phantom.death": "Phantom dies", "subtitles.entity.phantom.flap": "Phantom flaps", "subtitles.entity.phantom.hurt": "Phantom hurts", "subtitles.entity.phantom.swoop": "Phantom swoops", "subtitles.entity.puffer_fish.blow_out": "Pufferfish deflates", "subtitles.entity.puffer_fish.blow_up": "Pufferfish inflates", "subtitles.entity.puffer_fish.death": "Pufferfish dies", "subtitles.entity.puffer_fish.flop": "Pufferfish flops", "subtitles.entity.puffer_fish.hurt": "Pufferfish hurts", "subtitles.entity.puffer_fish.sting": "Pufferfish stings", "subtitles.entity.salmon.death": "Salmon dies", "subtitles.entity.salmon.flop": "Salmon flops", "subtitles.entity.salmon.hurt": "Salmon hurts", "subtitles.entity.skeleton_horse.swim": "Skeleton Horse swims", "subtitles.entity.squid.squirt": "Squid shoots ink", "subtitles.entity.turtle.ambient_land": "Turtle chirps", "subtitles.entity.turtle.lay_egg": "Turtle lays egg", "subtitles.entity.turtle.egg_hatch": "Turtle egg hatches", "subtitles.entity.turtle.egg_crack": "Turtle egg cracks", "subtitles.entity.turtle.egg_break": "Turtle egg breaks", "subtitles.entity.turtle.hurt": "Turtle hurts", "subtitles.entity.turtle.hurt_baby": "Turtle baby hurts", "subtitles.entity.turtle.death": "Turtle dies", "subtitles.entity.turtle.death_baby": "Turtle baby dies", "subtitles.entity.turtle.swim": "Turtle swims", "subtitles.entity.turtle.shamble": "Turtle shambles", "subtitles.entity.turtle.shamble_baby": "Turtle baby shambles", "subtitles.entity.zombie.converted_to_drowned": "Zombie converted to Drowned", "subtitles.item.axe.strip": "Debarking log", "subtitles.item.armor.equip_turtle": "Turtle shell thunks", "subtitles.item.trident.hit": "Trident stabs", "subtitles.item.trident.hit_ground": "Trident vibrates", "subtitles.item.trident.return": "Trident returns", "subtitles.item.trident.riptide": "Trident zooms", "subtitles.item.trident.throw": "Trident clangs", "subtitles.item.trident.thunder": "Trident thunder cracks", "debug.reload_chunks.help": "F3 + A = Reload chunks", "debug.show_hitboxes.help": "F3 + B = Show hitboxes", "debug.clear_chat.help": "F3 + D = Clear chat", "debug.cycle_renderdistance.help": "F3 + F = Cycle render distance (Shift to invert)", "debug.chunk_boundaries.help": "F3 + G = Show chunk boundaries", "debug.advanced_tooltips.help": "F3 + H = Advanced tooltips", "debug.creative_spectator.help": "F3 + N = Cycle creative <-> spectator", "debug.pause_focus.help": "F3 + P = Pause on lost focus", "debug.help.help": "F3 + Q = Show this list", "debug.reload_resourcepacks.help": "F3 + T = Reload resource packs", "debug.copy_location.help": "F3 + C = Copy location as /tp command, hold F3 + C to crash the game", "debug.inspect.help": "F3 + I = Copy entity or block data to clipboard", "debug.copy_location.message": "Copied location to clipboard", "debug.inspect.server.block": "Copied server-side block data to clipboard", "debug.inspect.server.entity": "Copied server-side entity data to clipboard", "debug.inspect.client.block": "Copied client-side block data to clipboard", "debug.inspect.client.entity": "Copied client-side entity data to clipboard", "debug.crash.message": "F3 + C is held down. This will crash the game unless released.", "debug.crash.warning": "Crashing in %s...", "advancements.adventure.very_very_frightening.title": "Very Very Frightening", "advancements.adventure.very_very_frightening.description": "Strike a Villager with lightning", "advancements.adventure.throw_trident.title": "A Throwaway Joke", "advancements.adventure.throw_trident.description": "Throw a trident at something.\nNote: Throwing away your only weapon is not a good idea.", "advancements.husbandry.fishy_business.title": "Fishy Business", "advancements.husbandry.fishy_business.description": "Catch a fish", "advancements.husbandry.tactical_fishing.title": "Tactical Fishing", "advancements.husbandry.tactical_fishing.description": "Catch a fish... without a fishing rod!", "team.visibility.always": "Always", "team.visibility.never": "Never", "team.visibility.hideForOtherTeams": "Hide for other teams", "team.visibility.hideForOwnTeam": "Hide for own team", "team.collision.always": "Always", "team.collision.never": "Never", "team.collision.pushOtherTeams": "Push other teams", "team.collision.pushOwnTeam": "Push own team", "argument.entity.selector.nearestPlayer": "Nearest player", "argument.entity.selector.randomPlayer": "Random player", "argument.entity.selector.allPlayers": "All players", "argument.entity.selector.allEntities": "All entities", "argument.entity.selector.self": "Current entity", "argument.entity.options.name.description": "Entity name", "argument.entity.options.distance.description": "Distance to entity", "argument.entity.options.level.description": "Experience level", "argument.entity.options.x.description": "x position", "argument.entity.options.y.description": "y position", "argument.entity.options.z.description": "z position", "argument.entity.options.dx.description": "Entities between x and x + dx", "argument.entity.options.dy.description": "Entities between y and y + dy", "argument.entity.options.dz.description": "Entities between z and z + dz", "argument.entity.options.x_rotation.description": "Entity's x rotation", "argument.entity.options.y_rotation.description": "Entity's y rotation", "argument.entity.options.limit.description": "Maximum number of entities to return", "argument.entity.options.sort.description": "Sort the entities", "argument.entity.options.gamemode.description": "Players with gamemode", "argument.entity.options.team.description": "Entities on team", "argument.entity.options.type.description": "Entities of type", "argument.entity.options.tag.description": "Entities with tag", "argument.entity.options.nbt.description": "Entities with NBT", "argument.entity.options.scores.description": "Entities with scores", "argument.entity.options.advancements.description": "Players with advancements", "command.failed": "An unexpected error occurred trying to execute that command", "command.context.here": "<--[HERE]", "commands.advancement.grant.one.to.one.success": "Granted the advancement %s to %s", "commands.advancement.grant.one.to.one.failure": "Couldn't grant advancement %s to %s as they already have it", "commands.advancement.grant.one.to.many.success": "Granted the advancement %s to %s players", "commands.advancement.grant.one.to.many.failure": "Couldn't grant advancement %s to %s players as they already have it", "commands.advancement.grant.many.to.one.success": "Granted %s advancements to %s", "commands.advancement.grant.many.to.one.failure": "Couldn't grant %s advancements to %s as they already have them", "commands.advancement.grant.many.to.many.success": "Granted %s advancements to %s players", "commands.advancement.grant.many.to.many.failure": "Couldn't grant %s advancements to %s players as they already have them", "commands.advancement.grant.criterion.to.one.success": "Granted criterion '%s' of advancement %s to %s", "commands.advancement.grant.criterion.to.one.failure": "Couldn't grant criterion '%s' of advancement %s to %s as they already have it", "commands.advancement.grant.criterion.to.many.success": "Granted criterion '%s' of advancement %s to %s players", "commands.advancement.grant.criterion.to.many.failure": "Couldn't grant criterion '%s' of advancement %s to %s players as they already have it", "commands.advancement.revoke.one.to.one.success": "Revoked the advancement %s from %s", "commands.advancement.revoke.one.to.one.failure": "Couldn't revoke advancement %s from %s as they don't have it", "commands.advancement.revoke.one.to.many.success": "Revoked the advancement %s from %s players", "commands.advancement.revoke.one.to.many.failure": "Couldn't revoke advancement %s from %s players as they don't have it", "commands.advancement.revoke.many.to.one.success": "Revoked %s advancements from %s", "commands.advancement.revoke.many.to.one.failure": "Couldn't revoke %s advancements from %s as they don't have them", "commands.advancement.revoke.many.to.many.success": "Revoked %s advancements from %s players", "commands.advancement.revoke.many.to.many.failure": "Couldn't revoke %s advancements from %s players as they don't have them", "commands.advancement.revoke.criterion.to.one.success": "Revoked criterion '%s' of advancement %s from %s", "commands.advancement.revoke.criterion.to.one.failure": "Couldn't revoke criterion '%s' of advancement %s from %s as they don't have it", "commands.advancement.revoke.criterion.to.many.success": "Revoked criterion '%s' of advancement %s from %s players", "commands.advancement.revoke.criterion.to.many.failure": "Couldn't revoke criterion '%s' of advancement %s from %s players as they don't have it", "commands.clear.success.single": "Removed %s items from player %s", "commands.clear.success.multiple": "Removed %s items from %s players", "commands.clear.test.single": "Found %s matching items on player %s", "commands.clear.test.multiple": "Found %s matching items on %s players", "commands.debug.stopped": "Stopped debug profiling after %s seconds and %s ticks (%s ticks per second)", "commands.difficulty.query": "The difficulty is %s", "commands.effect.give.success.single": "Applied effect %s to %s", "commands.effect.give.success.multiple": "Applied effect %s to %s targets", "commands.effect.clear.everything.success.single": "Removed every effect from %s", "commands.effect.clear.everything.success.multiple": "Removed every effect from %s targets", "commands.effect.clear.specific.success.single": "Removed effect %s from %s", "commands.effect.clear.specific.success.multiple": "Removed effect %s from %s targets", "commands.enchant.success.single": "Applied enchantment %s to %s's item", "commands.enchant.success.multiple": "Applied enchantment %s to %s entities", "commands.experience.add.points.success.single": "Gave %s experience points to %s", "commands.experience.add.points.success.multiple": "Gave %s experience points to %s players", "commands.experience.add.levels.success.single": "Gave %s experience levels to %s", "commands.experience.add.levels.success.multiple": "Gave %s experience levels to %s players", "commands.experience.set.points.success.single": "Set %s experience points on %s", "commands.experience.set.points.success.multiple": "Set %s experience points on %s players", "commands.experience.set.levels.success.single": "Set %s experience levels on %s", "commands.experience.set.levels.success.multiple": "Set %s experience levels on %s players", "commands.experience.query.points": "%s has %s experience points", "commands.experience.query.levels": "%s has %s experience levels", "commands.function.success.single": "Executed %s commands from function '%s'", "commands.function.success.multiple": "Executed %s commands from %s functions", "commands.give.success.single": "Gave %s %s to %s", "commands.give.success.multiple": "Gave %s %s to %s players", "commands.playsound.success.single": "Played sound %s to %s", "commands.playsound.success.multiple": "Played sound %s to %s players", "commands.publish.success": "Multiplayer game is now hosted on port %s", "commands.list.players": "There are %s of a max %s players online: %s", "commands.kill.success.multiple": "Killed %s entities", "commands.pardon.success": "Unbanned %s", "commands.gamerule.query": "Gamerule %s is currently set to: %s", "commands.gamerule.set": "Gamerule %s is now set to: %s", "commands.save.saving": "Saving the game (this may take a moment!)", "commands.banlist.none": "There are no bans", "commands.banlist.list": "There are %s bans:", "commands.banlist.entry": "%s was banned by %s: %s", "commands.bossbar.create.success": "Created custom bossbar %s", "commands.bossbar.remove.success": "Removed custom bossbar %s", "commands.bossbar.list.bars.none": "There are no custom bossbars active", "commands.bossbar.list.bars.some": "There are %s custom bossbars active: %s", "commands.bossbar.set.players.success.none": "Custom bossbar %s no longer has any players", "commands.bossbar.set.players.success.some": "Custom bossbar %s now has %s players: %s", "commands.bossbar.set.name.success": "Custom bossbar %s has been renamed", "commands.bossbar.set.color.success": "Custom bossbar %s has changed color", "commands.bossbar.set.style.success": "Custom bossbar %s has changed style", "commands.bossbar.set.value.success": "Custom bossbar %s has changed value to %s", "commands.bossbar.set.max.success": "Custom bossbar %s has changed maximum to %s", "commands.bossbar.set.visible.success.visible": "Custom bossbar %s is now visible", "commands.bossbar.set.visible.success.hidden": "Custom bossbar %s is now hidden", "commands.bossbar.get.value": "Custom bossbar %s has a value of %s", "commands.bossbar.get.max": "Custom bossbar %s has a maximum of %s", "commands.bossbar.get.visible.visible": "Custom bossbar %s is currently shown", "commands.bossbar.get.visible.hidden": "Custom bossbar %s is currently hidden", "commands.bossbar.get.players.none": "Custom bossbar %s has no players currently online", "commands.bossbar.get.players.some": "Custom bossbar %s has %s players currently online: %s", "commands.recipe.give.success.single": "Unlocked %s recipes for %s", "commands.recipe.give.success.multiple": "Unlocked %s recipes for %s players", "commands.recipe.take.success.single": "Took %s recipes from %s", "commands.recipe.take.success.multiple": "Took %s recipes from %s players", "commands.whitelist.none": "There are no whitelisted players", "commands.weather.set.clear": "Set the weather to clear", "commands.weather.set.rain": "Set the weather to rain", "commands.weather.set.thunder": "Set the weather to rain & thunder", "commands.spawnpoint.success.single": "Set spawn point to %s, %s, %s for %s", "commands.spawnpoint.success.multiple": "Set spawn point to %s, %s, %s for %s players", "commands.stopsound.success.source.sound": "Stopped sound '%s' on source '%s'", "commands.stopsound.success.source.any": "Stopped all '%s' sounds", "commands.stopsound.success.sourceless.sound": "Stopped sound '%s'", "commands.stopsound.success.sourceless.any": "Stopped all sounds", "commands.spreadplayers.success.entities": "Spread %s players around %s, %s with an average distance of %s blocks apart", "commands.setblock.success": "Changed the block at %s, %s, %s", "commands.banip.info": "This ban affects %s players: %s", "commands.pardonip.success": "Unbanned IP %s", "commands.teleport.success.entity.multiple": "Teleported %s entities to %s", "commands.teleport.success.location.multiple": "Teleported %s entities to %s, %s, %s", "commands.title.cleared.single": "Cleared titles for %s", "commands.title.cleared.multiple": "Cleared titles for %s players", "commands.title.reset.single": "Reset title options for %s", "commands.title.reset.multiple": "Reset title options for %s players", "commands.title.show.title.single": "Showing new title for %s", "commands.title.show.title.multiple": "Showing new title for %s players", "commands.title.show.subtitle.single": "Showing new subtitle for %s", "commands.title.show.subtitle.multiple": "Showing new subtitle for %s players", "commands.title.show.actionbar.single": "Showing new actionbar title for %s", "commands.title.show.actionbar.multiple": "Showing new actionbar title for %s players", "commands.title.times.single": "Changed title display times for %s", "commands.title.times.multiple": "Changed title display times for %s players", "commands.worldborder.set.grow": "Growing the world border to %s blocks wide over %s seconds", "commands.worldborder.set.shrink": "Shrinking the world border to %s blocks wide over %s seconds", "commands.worldborder.set.immediate": "Set the world border to %s blocks wide", "commands.worldborder.get": "The world border is currently %s blocks wide", "commands.replaceitem.block.success": "Replaced a slot at %s, %s, %s with %s", "commands.replaceitem.entity.success.single": "Replaced a slot on %s with %s", "commands.replaceitem.entity.success.multiple": "Replaced a slot on %s entities with %s", "commands.tag.add.success.single": "Added tag '%s' to %s", "commands.tag.add.success.multiple": "Added tag '%s' to %s entities", "commands.tag.remove.success.single": "Removed tag '%s' from %s", "commands.tag.remove.success.multiple": "Removed tag '%s' from %s entities", "commands.tag.list.single.empty": "%s has no tags", "commands.tag.list.single.success": "%s has %s tags: %s", "commands.tag.list.multiple.empty": "There are no tags on the %s entities", "commands.tag.list.multiple.success": "The %s entities have %s total tags: %s", "commands.team.list.members.empty": "There are no members on team %s", "commands.team.list.members.success": "Team %s has %s members: %s", "commands.team.list.teams.empty": "There are no teams", "commands.team.list.teams.success": "There are %s teams: %s", "commands.team.add.success": "Created team %s", "commands.team.empty.success": "Removed %s members from team %s", "commands.team.option.color.success": "Updated the color for team %s to %s", "commands.team.option.name.success": "Updated team %s name", "commands.team.option.friendlyfire.enabled": "Enabled friendly fire for team %s", "commands.team.option.friendlyfire.disabled": "Disabled friendly fire for team %s", "commands.team.option.seeFriendlyInvisibles.enabled": "Team %s can now see invisible teammates", "commands.team.option.seeFriendlyInvisibles.disabled": "Team %s can no longer see invisible teammates", "commands.team.option.nametagVisibility.success": "Nametag visibility for team %s is now \"%s\"", "commands.team.option.deathMessageVisibility.success": "Death message visibility for team %s is now \"%s\"", "commands.team.option.collisionRule.success": "Collision rule for team %s is now \"%s\"", "commands.team.option.prefix.success": "Team prefix set to %s", "commands.team.option.suffix.success": "Team suffix set to %s", "commands.team.join.success.single": "Added %s to team %s", "commands.team.join.success.multiple": "Added %s members to team %s", "commands.team.leave.success.single": "Removed %s from any team", "commands.team.leave.success.multiple": "Removed %s members from any team", "commands.trigger.simple.success": "Triggered %s", "commands.trigger.add.success": "Triggered %s (added %s to value)", "commands.trigger.set.success": "Triggered %s (set value to %s)", "commands.scoreboard.objectives.list.success": "There are %s objectives: %s", "commands.scoreboard.objectives.display.cleared": "Cleared any objectives in display slot %s", "commands.scoreboard.objectives.display.set": "Set display slot %s to show objective %s", "commands.scoreboard.objectives.modify.displayname": "Changed objective %s display name to %s", "commands.scoreboard.objectives.modify.rendertype": "Changed objective %s render type", "commands.scoreboard.players.list.success": "There are %s tracked entities: %s", "commands.scoreboard.players.list.entity.empty": "%s has no scores to show", "commands.scoreboard.players.list.entity.success": "%s has %s scores:", "commands.scoreboard.players.list.entity.entry": "%s: %s", "commands.scoreboard.players.set.success.single": "Set %s for %s to %s", "commands.scoreboard.players.set.success.multiple": "Set %s for %s entities to %s", "commands.scoreboard.players.add.success.single": "Added %s to %s for %s (now %s)", "commands.scoreboard.players.add.success.multiple": "Added %s to %s for %s entities", "commands.scoreboard.players.remove.success.single": "Removed %s from %s for %s (now %s)", "commands.scoreboard.players.remove.success.multiple": "Removed %s from %s for %s entities", "commands.scoreboard.players.reset.all.single": "Reset all scores for %s", "commands.scoreboard.players.reset.all.multiple": "Reset all scores for %s entities", "commands.scoreboard.players.reset.specific.single": "Reset %s for %s", "commands.scoreboard.players.reset.specific.multiple": "Reset %s for %s entities", "commands.scoreboard.players.enable.success.multiple": "Enabled trigger %s for %s entities", "commands.scoreboard.players.operation.success.single": "Set %s for %s to %s", "commands.scoreboard.players.operation.success.multiple": "Updated %s for %s entities", "commands.scoreboard.players.get.success": "%s has %s %s", "commands.data.entity.modified": "Modified entity data of %s", "commands.data.entity.query": "%s has the following entity data: %s", "commands.data.entity.get": "%s on %s after scale factor of %s is %s", "commands.data.block.modified": "Modified block data of %s, %s, %s", "commands.data.block.query": "%s, %s, %s has the following block data: %s", "commands.data.block.get": "%s on block %s, %s, %s after scale factor of %s is %s", "commands.datapack.list.enabled.success": "There are %s data packs enabled: %s", "commands.datapack.list.enabled.none": "There are no data packs enabled", "commands.datapack.list.available.success": "There are %s data packs available: %s", "commands.datapack.list.available.none": "There are no more data packs available", "commands.datapack.enable.success": "Enabled data pack %s", "commands.datapack.disable.success": "Disabled data pack %s", "argument.range.empty": "Expected value or range of values", "argument.range.ints": "Only whole numbers allowed, not decimals", "argument.range.swapped": "Min cannot be bigger than max", "permissions.requires.player": "A player is required to run this command here", "permissions.requires.entity": "An entity is required to run this command here", "argument.entity.toomany": "Only one entity is allowed, but the provided selector allows more than one", "argument.player.toomany": "Only one player is allowed, but the provided selector allows more than one", "argument.player.entities": "Only players may be affected by this command, but the provided selector includes entities", "argument.entity.notfound.entity": "No entity was found", "argument.entity.notfound.player": "No player was found", "argument.player.unknown": "That player does not exist", "arguments.nbtpath.node.invalid": "Invalid NBT path, expected element name or index", "arguments.operation.invalid": "Invalid operation", "arguments.operation.div0": "Cannot divide by zero", "argument.scoreHolder.empty": "No relevant score holders could be found", "argument.block.tag.disallowed": "Tags aren't allowed here, only actual blocks", "argument.block.property.unclosed": "Expected closing ] for block state properties", "argument.pos.unloaded": "That position is not loaded", "argument.pos.outofworld": "That position is out of this world!", "argument.rotation.incomplete": "Incomplete (expected 2 coordinates)", "arguments.swizzle.invalid": "Invalid swizzle, expected combination of 'x', 'y' and 'z'", "argument.vec2.incomplete": "Incomplete (expected 2 coordinates)", "argument.pos.incomplete": "Incomplete (expected 3 coordinates)", "argument.pos.mixed": "Cannot mix world & local coordinates (everything must either use ^ or not)", "argument.pos.missing.double": "Expected a coordinate", "argument.pos.missing.int": "Expected a block position", "argument.item.tag.disallowed": "Tags aren't allowed here, only actual items", "argument.entity.invalid": "Invalid name or UUID", "argument.entity.selector.missing": "Missing selector type", "argument.entity.selector.not_allowed": "Selector not allowed", "argument.entity.options.unterminated": "Expected end of options", "argument.entity.options.distance.negative": "Distance cannot be negative", "argument.entity.options.level.negative": "Level shouldn't be negative", "argument.entity.options.limit.toosmall": "Limit must be at least 1", "argument.nbt.trailing": "Unexpected trailing data", "argument.nbt.expected.key": "Expected key", "argument.nbt.expected.value": "Expected value", "argument.id.invalid": "Invalid ID", "commands.banip.failed": "Nothing changed. That IP is already banned", "commands.bossbar.set.players.unchanged": "Nothing changed. Those players are already on the bossbar with nobody to add or remove", "commands.bossbar.set.name.unchanged": "Nothing changed. That's already the name of this bossbar", "commands.bossbar.set.color.unchanged": "Nothing changed. That's already the color of this bossbar", "commands.bossbar.set.style.unchanged": "Nothing changed. That's already the style of this bossbar", "commands.bossbar.set.value.unchanged": "Nothing changed. That's already the value of this bossbar", "commands.bossbar.set.max.unchanged": "Nothing changed. That's already the max of this bossbar", "commands.bossbar.set.visibility.unchanged.hidden": "Nothing changed. The bossbar is already hidden", "commands.bossbar.set.visibility.unchanged.visible": "Nothing changed. The bossbar is already visible", "commands.clone.overlap": "The source and destination areas cannot overlap", "commands.debug.notRunning": "The debug profiler hasn't started", "commands.debug.alreadyRunning": "The debug profiler is already started", "commands.effect.give.failed": "Unable to apply this effect (target is either immune to effects, or has something stronger)", "commands.effect.clear.everything.failed": "Target has no effects to remove", "commands.effect.clear.specific.failed": "Target doesn't have the requested effect", "commands.enchant.failed": "Nothing changed. Targets either have no item in their hands or the enchantment could not be applied", "commands.experience.set.points.invalid": "Cannot set experience points above the maximum points for the player's current level", "commands.help.failed": "Unknown command or insufficient permissions", "commands.locate.failed": "Could not find that structure nearby", "commands.pardon.failed": "Nothing changed. The player isn't banned", "commands.pardonip.invalid": "Invalid IP address", "commands.pardonip.failed": "Nothing changed. That IP isn't banned", "commands.particle.failed": "The particle was not visible for anybody", "commands.playsound.failed": "The sound is too far away to be heard", "commands.recipe.give.failed": "No new recipes were learned", "commands.recipe.take.failed": "No recipes could be forgotten", "commands.replaceitem.block.failed": "The target block is not a container", "commands.replaceitem.slot.inapplicable": "The target does not have slot %s", "commands.replaceitem.entity.failed": "Could not put %s in slot %s", "commands.scoreboard.objectives.add.duplicate": "An objective already exists by that name", "commands.scoreboard.objectives.display.alreadyEmpty": "Nothing changed. That display slot is already empty", "commands.scoreboard.objectives.display.alreadySet": "Nothing changed. That display slot is already showing that objective", "commands.scoreboard.players.enable.failed": "Nothing changed. That trigger is already enabled", "commands.scoreboard.players.enable.invalid": "Enable only works on trigger-objectives", "commands.tag.add.failed": "Target either already has the tag or has too many tags", "commands.tag.remove.failed": "Target does not have this tag", "commands.team.add.duplicate": "A team already exists by that name", "commands.team.empty.unchanged": "Nothing changed. That team is already empty", "commands.team.option.color.unchanged": "Nothing changed. That team already has that color", "commands.team.option.name.unchanged": "Nothing changed. That team already has that name", "commands.team.option.friendlyfire.alreadyEnabled": "Nothing changed. Friendly fire is already enabled for that team", "commands.team.option.friendlyfire.alreadyDisabled": "Nothing changed. Friendly fire is already disabled for that team", "commands.team.option.seeFriendlyInvisibles.alreadyEnabled": "Nothing changed. That team can already see invisible teammates", "commands.team.option.seeFriendlyInvisibles.alreadyDisabled": "Nothing changed. That team already can't see invisible teammates", "commands.team.option.nametagVisibility.unchanged": "Nothing changed. Nametag visibility is already that value", "commands.team.option.deathMessageVisibility.unchanged": "Nothing changed. Death message visibility is already that value", "commands.team.option.collisionRule.unchanged": "Nothing changed. Collision rule is already that value", "commands.trigger.failed.unprimed": "You cannot trigger this objective yet", "commands.trigger.failed.invalid": "You can only trigger objectives that are 'trigger' type", "commands.whitelist.alreadyOn": "Whitelist is already turned on", "commands.whitelist.alreadyOff": "Whitelist is already turned off", "commands.worldborder.center.failed": "Nothing changed. The world border is already centered there", "commands.worldborder.set.failed.nochange": "Nothing changed. The world border is already that size", "commands.worldborder.set.failed.small.": "World border cannot be smaller than 1 block wide", "commands.worldborder.set.failed.big.": "World border cannot be bigger than 60,000,000 blocks wide", "commands.worldborder.warning.time.failed": "Nothing changed. The world border warning is already that amount of time", "commands.worldborder.warning.distance.failed": "Nothing changed. The world border warning is already that distance", "commands.worldborder.damage.buffer.failed": "Nothing changed. The world border damage buffer is already that distance", "commands.worldborder.damage.amount.failed": "Nothing changed. The world border damage is already that amount", "commands.data.block.invalid": "The target block is not a block entity", "commands.data.merge.failed": "Nothing changed, the specified properties already have these values", "commands.data.entity.invalid": "Unable to modify player data", "argument.color.invalid": "Unknown color '%s'", "argument.component.invalid": "Invalid chat component: %s", "argument.anchor.invalid": "Invalid entity anchor position %s", "enchantment.unknown": "Unknown enchantment: %s", "effect.effectNotFound": "Unknown effect: %s", "argument.nbt.invalid": "Invalid NBT: %s", "arguments.nbtpath.child.invalid": "Can't access child '%s', either doesn't exist or parent isn't a compound", "arguments.nbtpath.element.invalid": "Can't access element %s, either doesn't exist or parent isn't a list", "arguments.objective.notFound": "Unknown scoreboard objective '%s'", "arguments.objective.readonly": "Scoreboard objective '%s' is read-only", "commands.scoreboard.objectives.add.longName": "Objective names cannot be longer than %s characters", "argument.criteria.invalid": "Unknown criteria '%s'", "particle.notFound": "Unknown particle: %s", "argument.id.unknown": "Unknown ID: %s", "advancement.advancementNotFound": "Unknown advancement: %s", "recipe.notFound": "Unknown recipe: %s", "entity.notFound": "Unknown entity: %s", "argument.scoreboardDisplaySlot.invalid": "Unknown display slot '%s'", "slot.unknown": "Unknown slot '%s'", "team.notFound": "Unknown team '%s'", "arguments.block.tag.unknown": "Unknown block tag '%s'", "argument.block.id.invalid": "Unknown block type '%s'", "argument.block.property.unknown": "Block %s does not have property '%s'", "argument.block.property.duplicate": "Property '%s' can only be set once for block %s", "argument.block.property.invalid": "Block %s does not accept '%s' for %s property", "argument.block.property.novalue": "Expected value for property '%s' on block %s", "arguments.function.tag.unknown": "Unknown function tag '%s'", "arguments.function.unknown": "Unknown function %s", "arguments.item.overstacked": "%s can only stack up to %s", "argument.item.id.invalid": "Unknown item '%s'", "arguments.item.tag.unknown": "Unknown item tag '%s'", "argument.entity.selector.unknown": "Unknown selector type '%s'", "argument.entity.options.valueless": "Expected value for option '%s'", "argument.entity.options.unknown": "Unknown option '%s'", "argument.entity.options.inapplicable": "Option '%s' isn't applicable here", "argument.entity.options.sort.irreversible": "Invalid or unknown sort type '%s'", "argument.entity.options.mode.invalid": "Invalid or unknown game mode '%s'", "argument.entity.options.type.invalid": "Invalid or unknown entity type '%s'", "argument.nbt.list.mixed": "Can't insert %s into list of %s", "argument.nbt.array.mixed": "Can't insert %s into %s", "argument.nbt.array.invalid": "Invalid array type '%s'", "commands.bossbar.create.failed": "A bossbar already exists with the ID '%s'", "commands.bossbar.unknown": "No bossbar exists with the ID '%s'", "clear.failed.single": "No items were found on player %s", "clear.failed.multiple": "No items were found on %s players", "commands.clone.toobig": "Too many blocks in the specified area (maximum %s, specified %s)", "commands.datapack.unknown": "Unknown data pack '%s'", "commands.datapack.enable.failed": "Pack '%s' is already enabled!", "commands.datapack.disable.failed": "Pack '%s' is not enabled!", "commands.difficulty.failure": "The difficulty did not change; it is already set to %s", "commands.enchant.failed.entity": "%s is not a valid entity for this command", "commands.enchant.failed.itemless": "%s is not holding any item", "commands.enchant.failed.incompatible": "%s cannot support that enchantment", "commands.enchant.failed.level": "%s is higher than the maximum level of %s supported by that enchantment", "commands.execute.blocks.toobig": "Too many blocks in the specified area (maximum %s, specified %s)", "commands.execute.conditional.pass": "Test passed", "commands.execute.conditional.pass_count": "Test passed, count: %s", "commands.execute.conditional.fail": "Test failed", "commands.execute.conditional.fail_count": "Test failed, count: %s", "commands.fill.toobig": "Too many blocks in the specified area (maximum %s, specified %s)", "commands.publish.alreadyPublished": "Multiplayer game is already hosted on port %s", "commands.scoreboard.players.get.null": "Can't get value of %s for %s; none is set", "commands.spreadplayers.failed.teams": "Could not spread %s teams around %s, %s (too many entities for space - try using spread of at most %s)", "commands.spreadplayers.failed.entities": "Could not spread %s entities around %s, %s (too many entities for space - try using spread of at most %s)", "commands.team.add.longName": "Team names cannot be longer than %s characters", "commands.data.get.invalid": "Can't get %s; only numeric tags are allowed", "commands.data.get.unknown": "Can't get %s; tag doesn't exist", "argument.double.low": "Double must not be less than %s, found %s", "argument.double.big": "Double must not be more than %s, found %s", "argument.float.low": "Float must not be less than %s, found %s", "argument.float.big": "Float must not be more than %s, found %s", "argument.integer.low": "Integer must not be less than %s, found %s", "argument.integer.big": "Integer must not be more than %s, found %s", "argument.literal.incorrect": "Expected literal %s", "parsing.quote.expected.start": "Expected quote to start a string", "parsing.quote.expected.end": "Unclosed quoted string", "parsing.quote.escape": "Invalid escape sequence '\\%s' in quoted string", "parsing.bool.invalid": "Invalid boolean, expected 'true' or 'false' but found '%s'", "parsing.int.invalid": "Invalid integer '%s'", "parsing.int.expected": "Expected integer", "command.exception": "Could not parse command: %s", "parsing.double.invalid": "Invalid double '%s'", "parsing.double.expected": "Expected double", "parsing.float.invalid": "Invalid float '%s'", "parsing.float.expected": "Expected float", "parsing.bool.expected": "Expected boolean", "parsing.expected": "Expected '%s'", "command.unknown.command": "Unknown command", "command.unknown.argument": "Incorrect argument for command", "command.expected.separator": "Expected whitespace to end one argument, but found trailing data", "biome.minecraft.beach": "Beach", "biome.minecraft.birch_forest": "Birch Forest", "biome.minecraft.birch_forest_hills": "Birch Forest Hills", "biome.minecraft.snowy_beach": "Snowy Beach", "biome.minecraft.cold_ocean": "Cold Ocean", "biome.minecraft.deep_cold_ocean": "Deep Cold Ocean", "biome.minecraft.deep_frozen_ocean": "Deep Frozen Ocean", "biome.minecraft.deep_lukewarm_ocean": "Deep Lukewarm Ocean", "biome.minecraft.deep_ocean": "Deep Ocean", "biome.minecraft.deep_warm_ocean": "Deep Warm Ocean", "biome.minecraft.desert": "Desert", "biome.minecraft.desert_hills": "Desert Hills", "biome.minecraft.mountains": "Mountains", "biome.minecraft.wooded_mountains": "Wooded Mountains", "biome.minecraft.forest": "Forest", "biome.minecraft.wooded_hills": "Wooded Hills", "biome.minecraft.frozen_ocean": "Frozen Ocean", "biome.minecraft.frozen_river": "Frozen River", "biome.minecraft.nether": "Nether", "biome.minecraft.snowy_tundra": "Snowy Tundra", "biome.minecraft.snowy_mountains": "Snowy Mountains", "biome.minecraft.jungle_edge": "Jungle Edge", "biome.minecraft.jungle_hills": "Jungle Hills", "biome.minecraft.jungle": "Jungle", "biome.minecraft.lukewarm_ocean": "Lukewarm Ocean", "biome.minecraft.badlands_plateau": "Badlands Plateau", "biome.minecraft.badlands": "Badlands", "biome.minecraft.wooded_badlands_plateau": "Wooded Badlands Plateau", "biome.minecraft.mushroom_fields": "Mushroom Fields", "biome.minecraft.mushroom_field_shore": "Mushroom Field Shore", "biome.minecraft.tall_birch_hills": "Tall Birch Hills", "biome.minecraft.tall_birch_forest": "Tall Birch Forest", "biome.minecraft.desert_lakes": "Desert Lakes", "biome.minecraft.gravelly_mountains": "Gravelly Mountains", "biome.minecraft.modified_gravelly_mountains": "Gravelly Mountains+", "biome.minecraft.flower_forest": "Flower Forest", "biome.minecraft.ice_spikes": "Ice Spikes", "biome.minecraft.modified_jungle_edge": "Modified Jungle Edge", "biome.minecraft.modified_jungle": "Modified Jungle", "biome.minecraft.modified_badlands_plateau": "Modified Badlands Plateau", "biome.minecraft.eroded_badlands": "Eroded Badlands", "biome.minecraft.modified_wooded_badlands_plateau": "Modified Wooded Badlands Plateau", "biome.minecraft.sunflower_plains": "Sunflower Plains", "biome.minecraft.giant_spruce_taiga_hills": "Giant Spruce Taiga Hills", "biome.minecraft.giant_spruce_taiga": "Giant Spruce Taiga", "biome.minecraft.dark_forest_hills": "Dark Forest Hills", "biome.minecraft.shattered_savanna": "Shattered Savanna", "biome.minecraft.shattered_savanna_plateau": "Shattered Savanna Plateau", "biome.minecraft.swamp_hills": "Swamp Hills", "biome.minecraft.snowy_taiga_mountains": "Snowy Taiga Mountains", "biome.minecraft.taiga_mountains": "Taiga Mountains", "biome.minecraft.ocean": "Ocean", "biome.minecraft.plains": "Plains", "biome.minecraft.giant_tree_taiga_hills": "Giant Tree Taiga Hills", "biome.minecraft.giant_tree_taiga": "Giant Tree Taiga", "biome.minecraft.river": "River", "biome.minecraft.dark_forest": "Dark Forest", "biome.minecraft.savanna_plateau": "Savanna Plateau", "biome.minecraft.savanna": "Savanna", "biome.minecraft.end_barrens": "End Barrens", "biome.minecraft.end_highlands": "End Highlands", "biome.minecraft.small_end_islands": "Small End Islands", "biome.minecraft.end_midlands": "End Midlands", "biome.minecraft.the_end": "The End", "biome.minecraft.mountain_edge": "Mountain Edge", "biome.minecraft.stone_shore": "Stone Shore", "biome.minecraft.swamp": "Swamp", "biome.minecraft.snowy_taiga": "Snowy Taiga", "biome.minecraft.snowy_taiga_hills": "Snowy Taiga Hills", "biome.minecraft.taiga_hills": "Taiga Hills", "biome.minecraft.taiga": "Taiga", "biome.minecraft.the_void": "The Void", "biome.minecraft.warm_ocean": "Warm Ocean", "generator.minecraft.surface": "Surface", "generator.minecraft.caves": "Caves", "generator.minecraft.floating_islands": "Floating Islands", "realms.missing.module.error.text": "Realms could not be opened right now, please try again later", "realms.missing.snapshot.error.text": "Realms is currently not supported in snapshots" }, "1.12": { "potion.potency.3": "enchantment.level.4", "potion.potency.4": "enchantment.level.5", "potion.potency.5": "enchantment.level.6", "gui.recipebook.moreRecipes": "Right Click for more", "gui.recipebook.toggleRecipes.all": "Showing all", "gui.recipebook.toggleRecipes.craftable": "Showing craftable", "multiplayer.status.and_more": "... and %s more ...", "multiplayer.status.cancelled": "Cancelled", "multiplayer.status.cannot_connect": "Can't connect to server", "multiplayer.status.cannot_resolve": "Can't resolve hostname", "multiplayer.status.client_out_of_date": "Client out of date!", "multiplayer.status.no_connection": "(no connection)", "multiplayer.status.old": "Old", "multiplayer.status.pinging": "Pinging...", "multiplayer.status.server_out_of_date": "Server out of date!", "multiplayer.status.unknown": "???", "multiplayer.status.unrequested": "Received unrequested status", "multiplayer.disconnect.authservers_down": "Authentication servers are down. Please try again later, sorry!", "multiplayer.disconnect.banned": "You are banned from this server.", "multiplayer.disconnect.duplicate_login": "You logged in from another location", "multiplayer.disconnect.flying": "Flying is not enabled on this server", "multiplayer.disconnect.generic": "Disconnected", "multiplayer.disconnect.idling": "You have been idle for too long!", "multiplayer.disconnect.illegal_characters": "Illegal characters in chat", "multiplayer.disconnect.invalid_entity_attacked": "Attempting to attack an invalid entity", "multiplayer.disconnect.invalid_player_movement": "Invalid move player packet received", "multiplayer.disconnect.invalid_vehicle_movement": "Invalid move vehicle packet received", "multiplayer.disconnect.ip_banned": "You have been IP banned.", "multiplayer.disconnect.kicked": "Kicked by an operator.", "multiplayer.disconnect.outdated_client": "Outdated client! Please use %s", "multiplayer.disconnect.outdated_server": "Outdated server! I'm still on %s", "multiplayer.disconnect.server_shutdown": "Server closed", "multiplayer.disconnect.slow_login": "Took too long to log in", "multiplayer.disconnect.unverified_username": "Failed to verify username!", "chat.type.text.narrate": "%s says %s", "chat.type.advancement.task": "%s has made the advancement %s", "chat.type.advancement.challenge": "%s has completed the challenge %s", "chat.type.advancement.goal": "%s has reached the goal %s", "options.clouds.fancy": "Fancy", "options.clouds.fast": "Fast", "options.narrator": "Narrator", "options.narrator.off": "Off", "options.narrator.all": "Narrates all", "options.narrator.chat": "Narrates chat", "options.narrator.system": "Narrates system", "options.narrator.notavailable": "Not available", "narrator.toast.disabled": "Narrator Disabled", "narrator.toast.enabled": "Narrator Enabled", "key.mouse.left": "Left Click", "key.mouse.middle": "Middle Click", "key.mouse.right": "Right Click", "key.saveToolbarActivator": "Save Toolbar Activator", "key.loadToolbarActivator": "Load Toolbar Activator", "key.advancements": "Advancements", "key.categories.creative": "Creative Mode", "tile.glazedTerracottaWhite.name": "White Glazed Terracotta", "tile.glazedTerracottaOrange.name": "Orange Glazed Terracotta", "tile.glazedTerracottaMagenta.name": "Magenta Glazed Terracotta", "tile.glazedTerracottaLightBlue.name": "Light Blue Glazed Terracotta", "tile.glazedTerracottaYellow.name": "Yellow Glazed Terracotta", "tile.glazedTerracottaLime.name": "Lime Glazed Terracotta", "tile.glazedTerracottaPink.name": "Pink Glazed Terracotta", "tile.glazedTerracottaGray.name": "Gray Glazed Terracotta", "tile.glazedTerracottaSilver.name": "Light Gray Glazed Terracotta", "tile.glazedTerracottaCyan.name": "Cyan Glazed Terracotta", "tile.glazedTerracottaPurple.name": "Purple Glazed Terracotta", "tile.glazedTerracottaBlue.name": "Blue Glazed Terracotta", "tile.glazedTerracottaBrown.name": "Brown Glazed Terracotta", "tile.glazedTerracottaGreen.name": "Green Glazed Terracotta", "tile.glazedTerracottaRed.name": "Red Glazed Terracotta", "tile.glazedTerracottaBlack.name": "Black Glazed Terracotta", "tile.concrete.black.name": "Black Concrete", "tile.concrete.red.name": "Red Concrete", "tile.concrete.green.name": "Green Concrete", "tile.concrete.brown.name": "Brown Concrete", "tile.concrete.blue.name": "Blue Concrete", "tile.concrete.purple.name": "Purple Concrete", "tile.concrete.cyan.name": "Cyan Concrete", "tile.concrete.silver.name": "Light Gray Concrete", "tile.concrete.gray.name": "Gray Concrete", "tile.concrete.pink.name": "Pink Concrete", "tile.concrete.lime.name": "Lime Concrete", "tile.concrete.yellow.name": "Yellow Concrete", "tile.concrete.lightBlue.name": "Light Blue Concrete", "tile.concrete.magenta.name": "Magenta Concrete", "tile.concrete.orange.name": "Orange Concrete", "tile.concrete.white.name": "White Concrete", "tile.concretePowder.black.name": "Black Concrete Powder", "tile.concretePowder.red.name": "Red Concrete Powder", "tile.concretePowder.green.name": "Green Concrete Powder", "tile.concretePowder.brown.name": "Brown Concrete Powder", "tile.concretePowder.blue.name": "Blue Concrete Powder", "tile.concretePowder.purple.name": "Purple Concrete Powder", "tile.concretePowder.cyan.name": "Cyan Concrete Powder", "tile.concretePowder.silver.name": "Light Gray Concrete Powder", "tile.concretePowder.gray.name": "Gray Concrete Powder", "tile.concretePowder.pink.name": "Pink Concrete Powder", "tile.concretePowder.lime.name": "Lime Concrete Powder", "tile.concretePowder.yellow.name": "Yellow Concrete Powder", "tile.concretePowder.lightBlue.name": "Light Blue Concrete Powder", "tile.concretePowder.magenta.name": "Magenta Concrete Powder", "tile.concretePowder.orange.name": "Orange Concrete Powder", "tile.concretePowder.white.name": "White Concrete Powder", "item.bed.black.name": "Black Bed", "item.bed.red.name": "Red Bed", "item.bed.green.name": "Green Bed", "item.bed.brown.name": "Brown Bed", "item.bed.blue.name": "Blue Bed", "item.bed.purple.name": "Purple Bed", "item.bed.cyan.name": "Cyan Bed", "item.bed.silver.name": "Light Gray Bed", "item.bed.gray.name": "Gray Bed", "item.bed.pink.name": "Pink Bed", "item.bed.lime.name": "Lime Bed", "item.bed.yellow.name": "Yellow Bed", "item.bed.lightBlue.name": "Light Blue Bed", "item.bed.magenta.name": "Magenta Bed", "item.bed.orange.name": "Orange Bed", "item.bed.white.name": "White Bed", "item.knowledgeBook.name": "Knowledge Book", "container.enchant.level.requirement": "Level requirement: %s", "filled_map.unknown": "Unknown Map", "filled_map.level": "(Level %s/%s)", "filled_map.scale": "Scaling at 1:%s", "entity.Parrot.name": "Parrot", "entity.IllusionIllager.name": "Illusioner", "gui.advancements": "Advancements", "advancements.empty": "There doesn't seem to be anything here...", "advancements.toast.task": "Advancement Made!", "advancements.toast.challenge": "Challenge Complete!", "advancements.toast.goal": "Goal Reached!", "recipe.toast.title": "New Recipes Unlocked!", "recipe.toast.description": "Check your recipe book", "commands.advancement.usage": "/advancement ", "commands.advancement.advancementNotFound": "No advancement was found by the name '%1$s'", "commands.advancement.criterionNotFound": "The advancement '%1$s' does not contain the criterion '%2$s'", "commands.reload.usage": "/reload", "commands.reload.success": "Successfully reloaded loot tables, advancements and functions", "commands.function.usage": "/function [if |unless ]", "commands.function.unknown": "Unknown function '%s'", "commands.function.success": "Executed %2$s command(s) from function '%1$s'", "commands.function.skipped": "Skipped execution of function '%1$s'", "commands.advancement.grant.usage": "/advancement grant ", "commands.advancement.grant.only.usage": "/advancement grant only [criterion]", "commands.advancement.grant.only.failed": "Couldn't grant the advancement '%1$s' to %2$s because they already have it", "commands.advancement.grant.only.success": "Granted the entire advancement '%1$s' to %2$s", "commands.advancement.grant.criterion.failed": "Couldn't grant the criterion '%3$s' of advancement '%1$s' to %2$s because they already have it", "commands.advancement.grant.criterion.success": "Granted the criterion '%3$s' of advancement '%1$s' to %2$s", "commands.advancement.grant.until.usage": "/advancement grant until ", "commands.advancement.grant.until.failed": "Couldn't grant the advancement '%1$s' or its ancestors to %2$s because they already have them all", "commands.advancement.grant.until.success": "Granted '%1$s' and all ancestors (%3$s total granted) to %2$s", "commands.advancement.grant.from.usage": "/advancement grant from ", "commands.advancement.grant.from.failed": "Couldn't grant the advancement '%1$s' or its descendants to %2$s because they already have them all", "commands.advancement.grant.from.success": "Granted '%1$s' and all descendants (%3$s total granted) to %2$s", "commands.advancement.grant.through.usage": "/advancement grant through ", "commands.advancement.grant.through.failed": "Couldn't grant the advancement '%1$s', its ancestors or its descendants to %2$s because they already have them all", "commands.advancement.grant.through.success": "Granted '%1$s', all ancestors and all descendants (%3$s total granted) to %2$s", "commands.advancement.grant.everything.usage": "/advancement grant everything", "commands.advancement.grant.everything.failed": "Couldn't grant any advancements to %1$s because they already have them all", "commands.advancement.grant.everything.success": "Granted every advancement (%2$s total granted) to %1$s", "commands.advancement.revoke.usage": "/advancement revoke ", "commands.advancement.revoke.only.usage": "/advancement revoke only [criterion]", "commands.advancement.revoke.only.failed": "Couldn't revoke the advancement '%1$s' from %2$s because they haven't started it", "commands.advancement.revoke.only.success": "Revoked the entire advancement '%1$s' from %2$s", "commands.advancement.revoke.criterion.failed": "Couldn't revoke the criterion '%3$s' of advancement '%1$s' from %2$s because they haven't started it", "commands.advancement.revoke.criterion.success": "Revoked the criterion '%3$s' of advancement '%1$s' from %2$s", "commands.advancement.revoke.until.usage": "/advancement revoke until ", "commands.advancement.revoke.until.failed": "Couldn't revoke the advancement '%1$s' or its ancestors from %2$s because they haven't started any", "commands.advancement.revoke.until.success": "Revoked '%1$s' and all ancestors (%3$s total revoked) from %2$s", "commands.advancement.revoke.from.usage": "/advancement revoke from ", "commands.advancement.revoke.from.failed": "Couldn't revoke the advancement '%1$s' or its descendants from %2$s because they haven't started any", "commands.advancement.revoke.from.success": "Revoked '%1$s' and all descendants (%3$s total revoked) from %2$s", "commands.advancement.revoke.through.usage": "/advancement revoke through ", "commands.advancement.revoke.through.failed": "Couldn't revoke the advancement '%1$s', its ancestors or its descendants from %2$s because they haven't started any", "commands.advancement.revoke.through.success": "Revoked '%1$s', all ancestors and all descendants (%3$s total revoked) from %2$s", "commands.advancement.revoke.everything.usage": "/advancement revoke everything", "commands.advancement.revoke.everything.failed": "Couldn't revoke any advancements to %1$s because they haven't started any", "commands.advancement.revoke.everything.success": "Revoked every advancement (%2$s total revoked) from %1$s", "commands.advancement.test.usage": "/advancement test [criterion]", "commands.advancement.test.criterion.success": "Player %1$s has completed criterion '%3$s' of advancement '%2$s'", "commands.advancement.test.criterion.notDone": "Player %1$s has not completed criterion '%3$s' of advancement '%2$s'", "commands.advancement.test.advancement.success": "Player %1$s has completed advancement '%2$s'", "commands.advancement.test.advancement.notDone": "Player %1$s has not completed advancement '%2$s'", "commands.recipe.usage": "/recipe [player] ", "commands.recipe.alreadyHave": "Player %s already has a recipe for %s", "commands.recipe.dontHave": "Player %s doesn't have the recipe for %s", "commands.recipe.give.success.all": "Successfully given all recipes to %s", "commands.recipe.give.success.one": "Successfully given %s the recipe for %s", "commands.recipe.take.success.all": "Successfully taken all recipes from %s", "commands.recipe.take.success.one": "Successfully removed the recipe for %s from %s", "commands.recipe.unknownrecipe": "%s is an unknown recipe", "commands.recipe.unsupported": "%s is an unsupported recipe", "itemGroup.hotbar": "Saved Toolbars", "inventory.hotbarSaved": "Item toolbar saved (restore with %1$s+%2$s)", "inventory.hotbarInfo": "Save toolbar with %1$s+%2$s", "advMode.self": "Use \"@s\" to target the executing entity", "subtitles.entity.parrot.ambient": "Parrot talks", "subtitles.entity.parrot.death": "Parrot dies", "subtitles.entity.parrot.eats": "Parrot eats", "subtitles.entity.parrot.hurts": "Parrot hurts", "subtitles.entity.parrot.imitate.blaze": "Parrot breathes", "subtitles.entity.parrot.imitate.cave_spider": "Parrot hisses", "subtitles.entity.parrot.imitate.creeper": "Parrot hisses", "subtitles.entity.parrot.imitate.elder_guardian": "Parrot flaps", "subtitles.entity.parrot.imitate.enderdragon": "Parrot roars", "subtitles.entity.parrot.imitate.enderman": "Parrot vwoops", "subtitles.entity.parrot.imitate.endermite": "Parrot scuttles", "subtitles.entity.parrot.imitate.evocation_illager": "Parrot murmurs", "subtitles.entity.parrot.imitate.ghast": "Parrot cries", "subtitles.entity.parrot.imitate.husk": "Parrot groans", "subtitles.entity.parrot.imitate.illusion_illager": "Parrot murmurs", "subtitles.entity.parrot.imitate.magmacube": "Parrot squishes", "subtitles.entity.parrot.imitate.polar_bear": "Parrot groans", "subtitles.entity.parrot.imitate.shulker": "Parrot lurks", "subtitles.entity.parrot.imitate.silverfish": "Parrot hisses", "subtitles.entity.parrot.imitate.skeleton": "Parrot rattles", "subtitles.entity.parrot.imitate.slime": "Parrot squishes", "subtitles.entity.parrot.imitate.spider": "Parrot hisses", "subtitles.entity.parrot.imitate.stray": "Parrot rattles", "subtitles.entity.parrot.imitate.vex": "Parrot vexes", "subtitles.entity.parrot.imitate.vindication_illager": "Parrot mutters", "subtitles.entity.parrot.imitate.witch": "Parrot giggles", "subtitles.entity.parrot.imitate.wither": "Parrot angers", "subtitles.entity.parrot.imitate.wither_skeleton": "Parrot rattles", "subtitles.entity.parrot.imitate.wolf": "Parrot pants", "subtitles.entity.parrot.imitate.zombie": "Parrot groans", "subtitles.entity.parrot.imitate.zombie_pigman": "Parrot grunts", "subtitles.entity.parrot.imitate.zombie_villager": "Parrot groans", "subtitles.entity.illusion_illager.ambient": "Illusioner murmurs", "subtitles.entity.illusion_illager.cast_spell": "Illusioner casts spell", "subtitles.entity.illusion_illager.death": "Illusioner dies", "subtitles.entity.illusion_illager.hurt": "Illusioner hurts", "subtitles.entity.illusion_illager.mirror_move": "Illusioner displaces", "subtitles.entity.illusion_illager.prepare_blindness": "Illusioner prepares blindness", "subtitles.entity.illusion_illager.prepare_mirror": "Illusioner prepares mirror image", "tutorial.move.title": "Move with %s, %s, %s and %s", "tutorial.move.description": "Jump with %s", "tutorial.look.title": "Look around", "tutorial.look.description": "Use your mouse to turn", "tutorial.find_tree.title": "Find a tree", "tutorial.find_tree.description": "Punch it to collect wood", "tutorial.punch_tree.title": "Destroy the tree", "tutorial.punch_tree.description": "Hold down %s", "tutorial.open_inventory.title": "Open your inventory", "tutorial.open_inventory.description": "Press %s", "tutorial.craft_planks.title": "Craft wooden planks", "tutorial.craft_planks.description": "The recipe book can help", "advancements.adventure.adventuring_time.title": "Adventuring Time", "advancements.adventure.adventuring_time.description": "Discover every biome", "advancements.adventure.kill_all_mobs.title": "Monsters Hunted", "advancements.adventure.kill_all_mobs.description": "Kill one of every hostile monster", "advancements.adventure.kill_a_mob.title": "Monster Hunter", "advancements.adventure.kill_a_mob.description": "Kill any hostile monster", "advancements.adventure.root.title": "Adventure", "advancements.adventure.root.description": "Adventure, exploration and combat", "advancements.adventure.shoot_arrow.title": "Take Aim", "advancements.adventure.shoot_arrow.description": "Shoot something with a bow and arrow", "advancements.adventure.sleep_in_bed.title": "Sweet dreams", "advancements.adventure.sleep_in_bed.description": "Change your respawn point", "advancements.adventure.sniper_duel.title": "Sniper duel", "advancements.adventure.sniper_duel.description": "Kill a skeleton with an arrow from more than 50 meters", "advancements.adventure.trade.title": "What a Deal!", "advancements.adventure.trade.description": "Successfully trade with a Villager", "advancements.adventure.summon_iron_golem.title": "Hired Help", "advancements.adventure.summon_iron_golem.description": "Summon an Iron Golem to help defend a village", "advancements.adventure.totem_of_undying.title": "Postmortal", "advancements.adventure.totem_of_undying.description": "Use a Totem of Undying to cheat death", "advancements.husbandry.root.title": "Husbandry", "advancements.husbandry.root.description": "The world is full of friends and food", "advancements.husbandry.breed_an_animal.title": "The Parrots and the Bats", "advancements.husbandry.breed_an_animal.description": "Breed two animals together", "advancements.husbandry.breed_all_animals.title": "Two by Two", "advancements.husbandry.breed_all_animals.description": "Breed all the animals!", "advancements.husbandry.tame_an_animal.title": "Best Friends Forever", "advancements.husbandry.tame_an_animal.description": "Tame an animal", "advancements.husbandry.plant_seed.title": "A Seedy Place", "advancements.husbandry.plant_seed.description": "Plant a seed and watch it grow", "advancements.husbandry.break_diamond_hoe.title": "Serious Dedication", "advancements.husbandry.break_diamond_hoe.description": "Completely use up a diamond hoe, and then reevaluate your life choices", "advancements.husbandry.balanced_diet.title": "A Balanced Diet", "advancements.husbandry.balanced_diet.description": "Eat everything that is edible, even if it's not good for you", "advancements.end.dragon_breath.title": "You Need a Mint", "advancements.end.dragon_breath.description": "Collect dragon's breath in a glass bottle", "advancements.end.dragon_egg.title": "The Next Generation", "advancements.end.dragon_egg.description": "Hold the Dragon Egg", "advancements.end.elytra.title": "Sky's the Limit", "advancements.end.elytra.description": "Find an Elytra", "advancements.end.enter_end_gateway.title": "Remote Getaway", "advancements.end.enter_end_gateway.description": "Escape the island", "advancements.end.find_end_city.title": "The City at the End of the Game", "advancements.end.find_end_city.description": "Go on in, what could happen?", "advancements.end.kill_dragon.title": "Free the End", "advancements.end.kill_dragon.description": "Good luck", "advancements.end.levitate.title": "Great View From Up Here", "advancements.end.levitate.description": "Levitate up 50 blocks from the attacks of a Shulker", "advancements.end.respawn_dragon.title": "The End... Again...", "advancements.end.respawn_dragon.description": "Respawn the ender dragon", "advancements.end.root.title": "The End", "advancements.end.root.description": "Or the beginning?", "advancements.nether.brew_potion.title": "Local Brewery", "advancements.nether.brew_potion.description": "Brew a potion", "advancements.nether.all_potions.title": "A Furious Cocktail", "advancements.nether.all_potions.description": "Have every potion effect applied at the same time", "advancements.nether.all_effects.title": "How Did We Get Here?", "advancements.nether.all_effects.description": "Have every effect applied at the same time", "advancements.nether.create_beacon.title": "Bring Home the Beacon", "advancements.nether.create_beacon.description": "Construct and place a Beacon", "advancements.nether.create_full_beacon.title": "Beaconator", "advancements.nether.create_full_beacon.description": "Bring a beacon to full power", "advancements.nether.find_fortress.title": "A Terrible Fortress", "advancements.nether.find_fortress.description": "Break your way into a Nether Fortress", "advancements.nether.get_wither_skull.title": "Spooky Scary Skeleton", "advancements.nether.get_wither_skull.description": "Obtain a wither skeleton's skull", "advancements.nether.obtain_blaze_rod.title": "Into Fire", "advancements.nether.obtain_blaze_rod.description": "Relieve a Blaze of its rod", "advancements.nether.return_to_sender.title": "Return to Sender", "advancements.nether.return_to_sender.description": "Destroy a Ghast with a fireball", "advancements.nether.root.title": "Nether", "advancements.nether.root.description": "Bring summer clothes", "advancements.nether.summon_wither.title": "Withering Heights", "advancements.nether.summon_wither.description": "Summon the Wither", "advancements.nether.fast_travel.title": "Subspace Bubble", "advancements.nether.fast_travel.description": "Use the Nether to travel 7km in the Overworld", "advancements.nether.uneasy_alliance.title": "Uneasy Alliance", "advancements.nether.uneasy_alliance.description": "Rescue a Ghast from the Nether, bring it safely home to the Overworld... and then kill it.", "advancements.story.cure_zombie_villager.title": "Zombie Doctor", "advancements.story.cure_zombie_villager.description": "Weaken and then cure a zombie villager", "advancements.story.deflect_arrow.title": "Not Today, Thank You", "advancements.story.deflect_arrow.description": "Deflect an arrow with a shield", "advancements.story.enchant_item.title": "Enchanter", "advancements.story.enchant_item.description": "Enchant an item at an Enchanting Table", "advancements.story.enter_the_end.title": "The End?", "advancements.story.enter_the_end.description": "Enter the End Portal", "advancements.story.enter_the_nether.title": "We Need to Go Deeper", "advancements.story.enter_the_nether.description": "Build, light and enter a Nether Portal", "advancements.story.follow_ender_eye.title": "Eye Spy", "advancements.story.follow_ender_eye.description": "Follow an Ender Eye", "advancements.story.form_obsidian.title": "Ice Bucket Challenge", "advancements.story.form_obsidian.description": "Form and mine a block of Obsidian", "advancements.story.iron_tools.title": "Isn't It Iron Pick", "advancements.story.iron_tools.description": "Upgrade your pickaxe", "advancements.story.lava_bucket.title": "Hot Stuff", "advancements.story.lava_bucket.description": "Fill a bucket with lava", "advancements.story.mine_diamond.title": "Diamonds!", "advancements.story.mine_diamond.description": "Acquire diamonds", "advancements.story.mine_stone.title": "Stone Age", "advancements.story.mine_stone.description": "Mine stone with your new pickaxe", "advancements.story.obtain_armor.title": "Suit Up", "advancements.story.obtain_armor.description": "Protect yourself with a piece of iron armor", "advancements.story.root.title": "Minecraft", "advancements.story.root.description": "The heart and story of the game", "advancements.story.shiny_gear.title": "Cover Me With Diamonds", "advancements.story.shiny_gear.description": "Diamond armor saves lives", "advancements.story.smelt_iron.title": "Acquire Hardware", "advancements.story.smelt_iron.description": "Smelt an iron ingot", "advancements.story.upgrade_tools.title": "Getting an Upgrade", "advancements.story.upgrade_tools.description": "Construct a better pickaxe" }, "1.11": { "selectWorld.unable_to_load": "Unable to load worlds", "selectWorld.load_folder_access": "Unable to read or access folder where game worlds are saved!", "createWorld.customize.preset.classic_flat": "Classic Flat", "createWorld.customize.preset.tunnelers_dream": "Tunnelers' Dream", "createWorld.customize.preset.water_world": "Water World", "createWorld.customize.preset.overworld": "Overworld", "createWorld.customize.preset.snowy_kingdom": "Snowy Kingdom", "createWorld.customize.preset.bottomless_pit": "Bottomless Pit", "createWorld.customize.preset.desert": "Desert", "createWorld.customize.preset.redstone_ready": "Redstone Ready", "createWorld.customize.preset.the_void": "The Void", "createWorld.customize.custom.useMansions": "Woodland Mansions", "spectatorMenu.previous_page": "Previous Page", "spectatorMenu.next_page": "Next Page", "spectatorMenu.close": "Close Menu", "spectatorMenu.teleport": "Teleport to Player", "spectatorMenu.teleport.prompt": "Select a player to teleport to", "spectatorMenu.team_teleport": "Teleport to Team Member", "spectatorMenu.team_teleport.prompt": "Select a team to teleport to", "spectatorMenu.root.prompt": "Press a key to select a command, and again to use it.", "options.chunks": "%s chunks", "options.framerate": "%s fps", "title.oldjava1": "Old java detected; this will prevent you from playing", "title.oldjava2": "in the future as Java 8 will be required.", "tile.air.name": "Air", "tile.bed.tooFarAway": "You may not rest now, the bed is too far away", "tile.observer.name": "Observer", "tile.shulkerBoxWhite.name": "White Shulker Box", "tile.shulkerBoxOrange.name": "Orange Shulker Box", "tile.shulkerBoxMagenta.name": "Magenta Shulker Box", "tile.shulkerBoxLightBlue.name": "Light Blue Shulker Box", "tile.shulkerBoxYellow.name": "Yellow Shulker Box", "tile.shulkerBoxLime.name": "Lime Shulker Box", "tile.shulkerBoxPink.name": "Pink Shulker Box", "tile.shulkerBoxGray.name": "Gray Shulker Box", "tile.shulkerBoxSilver.name": "Light Gray Shulker Box", "tile.shulkerBoxCyan.name": "Cyan Shulker Box", "tile.shulkerBoxPurple.name": "Purple Shulker Box", "tile.shulkerBoxBlue.name": "Blue Shulker Box", "tile.shulkerBoxBrown.name": "Brown Shulker Box", "tile.shulkerBoxGreen.name": "Green Shulker Box", "tile.shulkerBoxRed.name": "Red Shulker Box", "tile.shulkerBoxBlack.name": "Black Shulker Box", "item.splash_potion.name": "Splash Potion", "item.lingering_potion.name": "Lingering Potion", "container.shulkerBox": "Shulker Box", "container.shulkerBox.more": "and %s more...", "item.color": "Color: %s", "item.nbt_tags": "NBT: %s tag(s)", "item.durability": "Durability: %s / %s", "filled_map.mansion": "Woodland Explorer Map", "filled_map.monument": "Ocean Explorer Map", "entity.ZombieVillager.name": "Zombie Villager", "entity.ElderGuardian.name": "Elder Guardian", "entity.EvocationIllager.name": "Evoker", "entity.Vex.name": "Vex", "entity.VindicationIllager.name": "Vindicator", "entity.Villager.nitwit": "Nitwit", "entity.Villager.cartographer": "Cartographer", "entity.Horse.name": "Horse", "entity.Llama.name": "Llama", "death.attack.cramming": "%1$s was squished too much", "death.attack.fireworks": "%1$s went off with a bang", "enchantment.sweeping": "Sweeping Edge", "enchantment.binding_curse": "Curse of Binding", "enchantment.vanishing_curse": "Curse of Vanishing", "stat.shulkerBoxOpened": "Shulker Boxes Opened", "commands.generic.blockstate.invalid": "'%s' is not a state for block %s", "commands.generic.selector_argument": "Invalid selector argument: '%s'", "commands.generic.player.unspecified": "You must specify which player you wish to perform this action on.", "commands.save.flushStart": "Flushing all saves...", "commands.save.flushEnd": "Flushing completed", "commands.scoreboard.players.tag.tagError": "Players tag command failed, reason: %s", "commands.spreadplayers.noop": "No players found to spread", "commands.locate.usage": "/locate ", "commands.locate.success": "Located %s at %s (y?) %s", "commands.locate.failure": "Unable to locate any %s feature", "subtitles.block.shulker_box.close": "Shulker closes", "subtitles.block.shulker_box.open": "Shulker opens", "subtitles.entity.elder_guardian.ambient.land": "Elder Guardian flaps", "subtitles.entity.elder_guardian.ambient": "Elder Guardian moans", "subtitles.entity.elder_guardian.attack": "Elder Guardian shoots", "subtitles.entity.elder_guardian.curse": "Elder Guardian curses", "subtitles.entity.elder_guardian.death": "Elder Guardian dies", "subtitles.entity.elder_guardian.flop": "Elder Guardian flops", "subtitles.entity.elder_guardian.hurt": "Elder Guardian hurts", "subtitles.entity.evocation_fangs.attack": "Fangs snap", "subtitles.entity.evocation_illager.ambient": "Evoker murmurs", "subtitles.entity.evocation_illager.cast_spell": "Evoker casts spell", "subtitles.entity.evocation_illager.death": "Evoker dies", "subtitles.entity.evocation_illager.hurt": "Evoker hurts", "subtitles.entity.evocation_illager.prepare_attack": "Evoker prepares attack", "subtitles.entity.evocation_illager.prepare_summon": "Evoker prepares summoning", "subtitles.entity.evocation_illager.prepare_wololo": "Evoker prepares charming", "subtitles.entity.llama.ambient": "Llama bleats", "subtitles.entity.llama.angry": "Llama bleats angry", "subtitles.entity.llama.chest": "Llama Chest equips", "subtitles.entity.llama.death": "Llama dies", "subtitles.entity.llama.eat": "Llama eats", "subtitles.entity.llama.hurt": "Llama hurts", "subtitles.entity.llama.spit": "Llama spits", "subtitles.entity.llama.step": "Llama steps", "subtitles.entity.llama.swag": "Llama is decorated", "subtitles.entity.mule.chest": "Mule Chest equips", "subtitles.entity.vex.ambient": "Vex vexes", "subtitles.entity.vex.charge": "Vex shrieks", "subtitles.entity.vex.death": "Vex dies", "subtitles.entity.vex.hurt": "Vex hurts", "subtitles.entity.vindication_illager.ambient": "Vindicator mutters", "subtitles.entity.vindication_illager.death": "Vindicator dies", "subtitles.entity.vindication_illager.hurt": "Vindicator hurts", "subtitles.entity.zombie_villager.ambient": "Zombie Villager groans", "subtitles.entity.zombie_villager.death": "Zombie Villager dies", "subtitles.entity.zombie_villager.hurt": "Zombie Villager hurts", "subtitles.item.armor.equip_elytra": "Elytra rustles", "subtitles.item.totem.use": "Totem activates", "debug.reload_chunks.help": "F3 + A ", "debug.show_hitboxes.help": "F3 + B ", "debug.clear_chat.help": "F3 + D ", "debug.cycle_renderdistance.help": "F3 + F ", "debug.chunk_boundaries.help": "F3 + G ", "debug.advanced_tooltips.help": "F3 + H ", "debug.creative_spectator.help": "F3 + N ", "debug.pause_focus.help": "F3 + P ", "debug.help.help": "F3 + Q ", "debug.reload_resourcepacks.help": "F3 + T ", "debug.reload_chunks.message": "Reloading all chunks", "debug.show_hitboxes.on": "Hitboxes: shown", "debug.show_hitboxes.off": "Hitboxes: hidden", "debug.cycle_renderdistance.message": "Render Distance: %s", "debug.chunk_boundaries.on": "Chunk borders: shown", "debug.chunk_boundaries.off": "Chunk borders: hidden", "debug.advanced_tooltips.on": "Advanced tooltips: shown", "debug.advanced_tooltips.off": "Advanced tooltips: hidden", "debug.creative_spectator.error": "Unable to switch gamemode, no permission", "debug.pause_focus.on": "Pause on lost focus: enabled", "debug.pause_focus.off": "Pause on lost focus: disabled", "debug.help.message": "Key bindings:", "debug.reload_resourcepacks.message": "Reloaded resource packs", "resourcepack.downloading": "Downloading Resource Pack", "resourcepack.requesting": "Making Request...", "resourcepack.progress": "Downloading file (%s MB)..." }, "1.10": { "options.autoJump": "Auto-jump", "tile.magma.name": "Magma Block", "tile.netherWartBlock.name": "Nether Wart Block", "tile.redNetherBrick.name": "Red Nether Brick", "tile.boneBlock.name": "Bone Block", "tile.structureVoid.name": "Structure Void", "structure_block.save_success": "Structure saved as '%s'", "structure_block.save_failure": "Unable to save structure '%s'", "structure_block.load_success": "Structure loaded from '%s'", "structure_block.load_prepare": "Structure '%s' position prepared", "structure_block.load_not_found": "Structure '%s' is not available", "structure_block.size_success": "Size successfully detected for '%s'", "structure_block.size_failure": "Unable to detect structure size, add corners with matching structure names", "structure_block.mode.save": "[S]", "structure_block.mode.load": "[L]", "structure_block.mode.data": "[D]", "structure_block.mode.corner": "[C]", "structure_block.hover.save": "Save: %s", "structure_block.hover.load": "Load: %s", "structure_block.hover.data": "Data: %s", "structure_block.hover.corner": "Corner: %s", "structure_block.mode_info.save": "Save mode - write to file", "structure_block.mode_info.load": "Load mode - load from file", "structure_block.mode_info.data": "Data mode - game logic marker", "structure_block.mode_info.corner": "Corner mode - placement and size marker", "structure_block.structure_name": "Structure Name", "structure_block.custom_data": "Custom Data Tag Name", "structure_block.position": "Relative Position", "structure_block.size": "Structure Size", "structure_block.integrity": "Structure Integrity and Seed", "structure_block.include_entities": "Include entities:", "structure_block.detect_size": "Detect structure size and position:", "structure_block.button.detect_size": "DETECT", "structure_block.button.save": "SAVE", "structure_block.button.load": "LOAD", "structure_block.show_air": "Show invisible blocks:", "structure_block.show_boundingbox": "Show bounding box:", "entity.WitherSkeleton.name": "Wither Skeleton", "entity.Stray.name": "Stray", "entity.Husk.name": "Husk", "entity.PolarBear.name": "Polar Bear", "entity.Donkey.name": "Donkey", "entity.Mule.name": "Mule", "entity.SkeletonHorse.name": "Skeleton Horse", "entity.ZombieHorse.name": "Zombie Horse", "death.attack.hotFloor": "%1$s discovered floor was lava", "death.attack.hotFloor.player": "%1$s walked into danger zone due to %2$s", "commands.teleport.usage": "/teleport [ ]", "commands.teleport.success.coordinates": "Teleported %s to %s, %s, %s", "subtitles.entity.husk.ambient": "Husk groans", "subtitles.entity.husk.death": "Husk dies", "subtitles.entity.husk.hurt": "Husk hurts", "subtitles.entity.polar_bear.ambient": "Polar Bear groans", "subtitles.entity.polar_bear.baby_ambient": "Polar Bear hums", "subtitles.entity.polar_bear.death": "Polar Bear dies", "subtitles.entity.polar_bear.hurt": "Polar Bear hurts", "subtitles.entity.polar_bear.warning": "Polar Bear roars", "subtitles.entity.stray.ambient": "Stray rattles", "subtitles.entity.stray.death": "Stray dies", "subtitles.entity.stray.hurt": "Stray hurts", "subtitles.entity.wither_skeleton.ambient": "Wither Skeleton rattles", "subtitles.entity.wither_skeleton.death": "Wither Skeleton dies", "subtitles.entity.wither_skeleton.hurt": "Wither Skeleton hurts" }, "1.9.3": { "commands.stopsound.usage": "/stopsound [source] [sound]", "commands.stopsound.unknownSoundSource": "Source %s is unknown", "commands.stopsound.success.individualSound": "Stopped sound '%s' with source '%s' for %s", "commands.stopsound.success.soundSource": "Stopped source '%s' for %s", "commands.stopsound.success.all": "Stopped all sounds for %s" }, "1.9.1": { "entity.MinecartHopper.name": "Minecart with Hopper", "entity.MinecartChest.name": "Minecart with Chest", "attribute.name.generic.armorToughness": "Armor Toughness" } } ================================================ FILE: fabric/build.gradle.kts ================================================ dependencies { compileOnlyApi(projects.viabackwardsCommon) compileOnly(libs.fabricLoader) compileOnly(libs.log4j) } ================================================ FILE: fabric/src/main/java/com/viaversion/viabackwards/ViaFabricAddon.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards; import com.viaversion.viabackwards.api.ViaBackwardsPlatform; import com.viaversion.viabackwards.fabric.util.LoggerWrapper; import java.io.File; import java.nio.file.Path; import java.util.logging.Logger; import net.fabricmc.loader.api.FabricLoader; import org.apache.logging.log4j.LogManager; public class ViaFabricAddon implements ViaBackwardsPlatform, Runnable { private final Logger logger = new LoggerWrapper(LogManager.getLogger("ViaBackwards")); private File configDir; @Override public void run() { Path configDirPath = FabricLoader.getInstance().getConfigDir().resolve("ViaBackwards"); configDir = configDirPath.toFile(); this.init(new File(getDataFolder(), "config.yml")); this.enable(); } @Override public void disable() { // Not possible } @Override public File getDataFolder() { return configDir; } @Override public Logger getLogger() { return logger; } } ================================================ FILE: fabric/src/main/java/com/viaversion/viabackwards/fabric/util/LoggerWrapper.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards.fabric.util; import java.text.MessageFormat; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; public class LoggerWrapper extends Logger { private final org.apache.logging.log4j.Logger base; public LoggerWrapper(org.apache.logging.log4j.Logger logger) { super("logger", null); this.base = logger; } public void log(LogRecord record) { this.log(record.getLevel(), record.getMessage()); } public void log(Level level, String msg) { if (level == Level.FINE) { this.base.debug(msg); } else if (level == Level.WARNING) { this.base.warn(msg); } else if (level == Level.SEVERE) { this.base.error(msg); } else if (level == Level.INFO) { this.base.info(msg); } else { this.base.trace(msg); } } public void log(Level level, String msg, Object param1) { if (level == Level.FINE) { this.base.debug(msg, param1); } else if (level == Level.WARNING) { this.base.warn(msg, param1); } else if (level == Level.SEVERE) { this.base.error(msg, param1); } else if (level == Level.INFO) { this.base.info(msg, param1); } else { this.base.trace(msg, param1); } } public void log(Level level, String msg, Object[] params) { log(level, MessageFormat.format(msg, params)); } public void log(Level level, String msg, Throwable params) { if (level == Level.FINE) { this.base.debug(msg, params); } else if (level == Level.WARNING) { this.base.warn(msg, params); } else if (level == Level.SEVERE) { this.base.error(msg, params); } else if (level == Level.INFO) { this.base.info(msg, params); } else { this.base.trace(msg, params); } } } ================================================ FILE: fabric/src/main/resources/fabric.mod.json ================================================ { "schemaVersion": 1, "id": "viabackwards", "name": "ViaBackwards", "version": "${version}", "description": "${description}", "license": "GPL-3.0", "contact": { "homepage": "https://viaversion.com/backwards", "issues": "https://github.com/ViaVersion/ViaBackwards/issues", "sources": "https://github.com/ViaVersion/ViaBackwards" }, "icon": "assets/viabackwards/textures/squarelogo.png", "environment": "*", "authors": [ "Matsv", "kennytv", "Gerrygames", "creeper123123321", "ForceUpdate1", "EnZaXD" ], "entrypoints": { "viafabric:via_api_initialized": [ "com.viaversion.viabackwards.ViaFabricAddon" ] }, "depends": { "viafabric": ">=0.4.14" }, "custom": { "modmenu:api": true, "modmenu": { "badges": [ "library" ], "parent": "viafabric" } } } ================================================ FILE: gradle/libs.versions.toml ================================================ metadata.format.version = "1.1" [versions] # ViaVersion viaver = "5.9.0-SNAPSHOT" # Common provided netty = "4.0.20.Final" guava = "17.0" log4j = "2.8.1" checkerQual = "3.53.1" # Platforms paper = "1.16.5-R0.1-SNAPSHOT" velocity = "3.4.0" fabricLoader = "0.11.6" viaProxy = "[3.0.0,4.0.0)" [libraries] viaver = { group = "com.viaversion", name = "viaversion", version.ref = "viaver" } netty = { group = "io.netty", name = "netty-all", version.ref = "netty" } guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } log4j = { group = "org.apache.logging.log4j", name = "log4j-api", version.ref = "log4j" } checkerQual = { group = "org.checkerframework", name = "checker-qual", version.ref = "checkerQual" } paper = { group = "com.destroystokyo.paper", name = "paper-api", version.ref = "paper" } velocity = { group = "com.velocitypowered", name = "velocity-api", version.ref = "velocity" } fabricLoader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabricLoader" } viaProxy = { group = "net.raphimc", name = "ViaProxy", version.ref = "viaProxy" } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionSha256Sum=2ab2958f2a1e51120c326cad6f385153bb11ee93b3c216c5fccebfdfbb7ec6cb distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle.properties ================================================ projectVersion=5.9.0-SNAPSHOT # Smile emoji (note that modrinth may not have added the version on release yet) mcVersions=26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 1.21.8, 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-26.1.1 velocityVersion=3.4-3.5 org.gradle.configureondemand=true org.gradle.parallel=true org.gradle.configuration-cache=true ================================================ FILE: gradlew ================================================ #!/bin/sh # # Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # SPDX-License-Identifier: Apache-2.0 # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java if ! command -v java >/dev/null 2>&1 then die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. if ! command -v xargs >/dev/null 2>&1 then die "xargs is not available" fi # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @rem SPDX-License-Identifier: Apache-2.0 @rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute echo. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! set EXIT_CODE=%ERRORLEVEL% if %EXIT_CODE% equ 0 set EXIT_CODE=1 if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: settings.gradle.kts ================================================ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") dependencyResolutionManagement { repositories { mavenLocal() maven("https://repo.viaversion.com") maven("https://repo.papermc.io/repository/maven-public/") mavenCentral() } repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) } pluginManagement { plugins { id("com.gradleup.shadow") version "9.4.1" id("net.kyori.blossom") version "2.2.0" id("org.jetbrains.gradle.plugin.idea-ext") version "1.4.1" // A nice no-conflict comment for patching in downgrading } } plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "viabackwards-parent" includeBuild("build-logic") setupViaSubproject("common") setupViaSubproject("bukkit") setupViaSubproject("velocity") setupViaSubproject("sponge") setupViaSubproject("fabric") setupSubproject("viabackwards") { projectDir = file("universal") } fun setupViaSubproject(name: String) { setupSubproject("viabackwards-$name") { projectDir = file(name) } } inline fun setupSubproject(name: String, block: ProjectDescriptor.() -> Unit) { include(name) project(":$name").apply(block) } ================================================ FILE: sponge/build.gradle.kts ================================================ ================================================ FILE: sponge/src/main/resources/META-INF/sponge_plugins.json ================================================ { "loader": { "name": "java_plain", "version": "1.0" }, "license": "GNU GPLv3", "global": { "version": "${version}", "links": { "source": "https://github.com/ViaVersion/ViaBackwards", "issues": "https://github.com/ViaVersion/ViaBackwards/issues" }, "contributors": [ { "name": "Matsv", "description": "Maintainer" }, { "name": "kennytv", "description": "Maintainer" }, { "name": "Gerrygames", "description": "Contributor" }, { "name": "creeper123123321", "description": "Contributor" }, { "name": "ForceUpdate1", "description": "Contributor" }, { "name": "EnZaXD", "description": "Maintainer" } ], "dependencies": [ { "id": "spongeapi", "version": "8.0.0" }, { "id": "viasponge", "version": "1.1.0" }, { "id": "viaversion", "version": "[5.8.1-SNAPSHOT,)" } ] }, "plugins": [ { "id": "viabackwards", "name": "ViaBackwards", "entrypoint": "com.viaversion.sponge.util.DummyEntrypoint", "description": "${description}" } ] } ================================================ FILE: universal/build.gradle.kts ================================================ plugins { id("io.papermc.hangar-publish-plugin") version "0.1.4" id("com.modrinth.minotaur") version "2.+" // A nice no-conflict comment for patching in downgrading } dependencies { api(projects.viabackwardsCommon) api(projects.viabackwardsBukkit) api(projects.viabackwardsVelocity) api(projects.viabackwardsFabric) api(projects.viabackwardsSponge) } tasks { shadowJar { manifest { attributes["paperweight-mappings-namespace"] = "mojang" } archiveFileName.set("ViaBackwards-${project.version}.jar") destinationDirectory.set(rootProject.projectDir.resolve("build/libs")) } } val branch = rootProject.branchName() val baseVersion = project.version as String val isRelease = !baseVersion.contains('-') val isMainBranch = branch == "master" if (!isRelease || isMainBranch) { // Only publish releases from the main branch val suffixedVersion = if (isRelease) baseVersion else baseVersion + "+" + System.getenv("GITHUB_RUN_NUMBER") val changelogContent = if (isRelease) { "See [GitHub](https://github.com/ViaVersion/ViaBackwards) for release notes." } else { val commitHash = rootProject.latestCommitHash() "[$commitHash](https://github.com/ViaVersion/ViaBackwards/commit/$commitHash) ${rootProject.latestCommitMessage()}" } modrinth { // val snapshotVersion = rootProject.parseMinecraftSnapshotVersion(project.version as String) val mcVersions: List = (property("mcVersions") as String) .split(",") .map { it.trim() } //.let { if (snapshotVersion != null) it + snapshotVersion else it } // We're usually too fast for modrinth token.set(System.getenv("MODRINTH_TOKEN")) projectId.set("viabackwards") versionType.set(if (isRelease) "release" else if (isMainBranch) "beta" else "alpha") versionNumber.set(suffixedVersion) versionName.set(suffixedVersion) changelog.set(changelogContent) uploadFile.set(tasks.shadowJar.flatMap { it.archiveFile }) gameVersions.set(mcVersions) loaders.add("fabric") loaders.add("paper") loaders.add("folia") loaders.add("velocity") autoAddDependsOn.set(false) detectLoaders.set(false) dependencies { required.project("viaversion") optional.project("viafabric") optional.project("viarewind") } } tasks.modrinth { notCompatibleWithConfigurationCache("") } hangarPublish { publications.register("plugin") { version = suffixedVersion id = "ViaBackwards" channel = if (isRelease) "Release" else if (isMainBranch) "Snapshot" else "Alpha" changelog = changelogContent apiKey = System.getenv("HANGAR_TOKEN") platforms { paper { jar = tasks.shadowJar.flatMap { it.archiveFile } platformVersions = listOf(property("mcVersionRange") as String) dependencies { hangar("ViaVersion") { required = true } hangar("ViaRewind") { required = false } } } velocity { jar = tasks.shadowJar.flatMap { it.archiveFile } platformVersions = listOf(property("velocityVersion") as String) dependencies { hangar("ViaVersion") { required = true } hangar("ViaRewind") { required = false } } } } } } tasks.named("publishPluginPublicationToHangar") { notCompatibleWithConfigurationCache("") } } ================================================ FILE: velocity/build.gradle.kts ================================================ dependencies { compileOnlyApi(projects.viabackwardsCommon) compileOnly(libs.velocity) { // Requires Java 17 exclude("com.velocitypowered", "velocity-brigadier") } annotationProcessor(libs.velocity) } ================================================ FILE: velocity/src/main/java/com/viaversion/viabackwards/VelocityPlugin.java ================================================ /* * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards * Copyright (C) 2016-2026 ViaVersion and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.viaversion.viabackwards; import com.google.inject.Inject; import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.plugin.Dependency; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.viaversion.viabackwards.api.ViaBackwardsPlatform; import com.viaversion.viabackwards.utils.VersionInfo; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.velocity.util.LoggerWrapper; import java.io.File; import java.nio.file.Path; import java.util.logging.Logger; @Plugin(id = "viabackwards", name = "ViaBackwards", version = VersionInfo.VERSION, authors = {"Matsv", "kennytv", "Gerrygames", "creeper123123321", "ForceUpdate1", "EnZaXD"}, description = "Allows the connection of older clients to newer server versions for Minecraft servers.", dependencies = {@Dependency(id = "viaversion")} ) public class VelocityPlugin implements ViaBackwardsPlatform { private Logger logger; @Inject private org.slf4j.Logger loggerSlf4j; @Inject @DataDirectory private Path configPath; @SuppressWarnings("deprecation") @Subscribe(order = PostOrder.LATE) public void onProxyStart(ProxyInitializeEvent event) { this.logger = new LoggerWrapper(loggerSlf4j); Via.getManager().addEnableListener(() -> this.init(new File(getDataFolder(), "config.yml"))); Via.getManager().addPostEnableListener(this::enable); } @Override public void disable() { // Not possible } @Override public File getDataFolder() { return configPath.toFile(); } @Override public Logger getLogger() { return logger; } }